1717 */
1818package org .apache .beam .sdk .io .delta ;
1919
20- import static org .junit .Assert .assertNotNull ;
21-
2220import com .google .cloud .storage .Blob ;
23- import com .google .cloud .storage .BlobId ;
24- import com .google .cloud .storage .BlobInfo ;
2521import com .google .cloud .storage .Storage ;
2622import com .google .cloud .storage .StorageOptions ;
27- import java .nio .charset .StandardCharsets ;
23+ import io .delta .kernel .DataWriteContext ;
24+ import io .delta .kernel .Operation ;
25+ import io .delta .kernel .Table ;
26+ import io .delta .kernel .Transaction ;
27+ import io .delta .kernel .TransactionBuilder ;
28+ import io .delta .kernel .TransactionCommitResult ;
29+ import io .delta .kernel .data .ColumnVector ;
30+ import io .delta .kernel .data .ColumnarBatch ;
31+ import io .delta .kernel .data .FilteredColumnarBatch ;
32+ import io .delta .kernel .defaults .engine .DefaultEngine ;
33+ import io .delta .kernel .defaults .internal .data .DefaultColumnarBatch ;
34+ import io .delta .kernel .engine .Engine ;
35+ import io .delta .kernel .types .DataType ;
36+ import io .delta .kernel .types .IntegerType ;
37+ import io .delta .kernel .types .StringType ;
38+ import io .delta .kernel .types .StructType ;
39+ import io .delta .kernel .utils .CloseableIterable ;
40+ import io .delta .kernel .utils .CloseableIterator ;
41+ import io .delta .kernel .utils .DataFileStatus ;
42+ import java .util .Collections ;
2843import java .util .HashMap ;
2944import java .util .List ;
3045import java .util .Map ;
46+ import java .util .Optional ;
3147import java .util .stream .Collectors ;
3248import java .util .stream .IntStream ;
33- import org .apache .avro .generic .GenericRecord ;
34- import org .apache .beam .runners .direct .DirectOptions ;
35- import org .apache .beam .runners .direct .DirectRunner ;
36- import org .apache .beam .sdk .Pipeline ;
37- import org .apache .beam .sdk .extensions .avro .coders .AvroCoder ;
38- import org .apache .beam .sdk .extensions .avro .schemas .utils .AvroUtils ;
39- import org .apache .beam .sdk .io .Compression ;
40- import org .apache .beam .sdk .io .FileIO ;
41- import org .apache .beam .sdk .io .parquet .ParquetIO ;
4249import org .apache .beam .sdk .managed .Managed ;
43- import org .apache .beam .sdk .options .PipelineOptionsFactory ;
4450import org .apache .beam .sdk .schemas .Schema ;
4551import org .apache .beam .sdk .testing .PAssert ;
4652import org .apache .beam .sdk .testing .TestPipeline ;
47- import org .apache .beam .sdk .transforms .Create ;
48- import org .apache .beam .sdk .transforms .MapElements ;
49- import org .apache .beam .sdk .transforms .windowing .BoundedWindow ;
50- import org .apache .beam .sdk .transforms .windowing .PaneInfo ;
5153import org .apache .beam .sdk .values .PCollection ;
5254import org .apache .beam .sdk .values .Row ;
53- import org .apache .beam .sdk .values .TypeDescriptor ;
5455import org .apache .beam .vendor .guava .v32_1_2_jre .com .google .common .collect .ImmutableMap ;
56+ import org .apache .hadoop .conf .Configuration ;
5557import org .junit .After ;
5658import org .junit .Before ;
5759import org .junit .Rule ;
@@ -91,8 +93,8 @@ public void setup() throws Exception {
9193
9294 String tempLocation = readPipeline .getOptions ().getTempLocation ();
9395 if (tempLocation != null && tempLocation .startsWith ("gs://" )) {
94- org .apache .beam .sdk .extensions .gcp .util .gcsfs .GcsPath gcsPath = org . apache . beam . sdk . extensions . gcp . util . gcsfs . GcsPath
95- .fromUri (tempLocation );
96+ org .apache .beam .sdk .extensions .gcp .util .gcsfs .GcsPath gcsPath =
97+ org . apache . beam . sdk . extensions . gcp . util . gcsfs . GcsPath .fromUri (tempLocation );
9698 bucket = gcsPath .getBucket ();
9799 repoPrefix = gcsPath .getObject () + "/delta_io_it/" + testName .getMethodName () + "-" + salt ;
98100 } else {
@@ -103,51 +105,119 @@ public void setup() throws Exception {
103105
104106 LOG .info ("Generating Delta Lake repository at {}" , repoPath );
105107
106- // 1. Write Parquet file using a direct local pipeline
107- DirectOptions setupOptions = PipelineOptionsFactory .as (DirectOptions .class );
108- setupOptions .setRunner (DirectRunner .class );
109- setupOptions .setBlockOnRun (true );
110- Pipeline setupPipeline = Pipeline .create (setupOptions );
111-
112- org .apache .avro .Schema avroSchema = AvroUtils .toAvroSchema (ROW_SCHEMA );
113- setupPipeline
114- .apply (Create .of (TEST_ROWS ).withRowSchema (ROW_SCHEMA ))
115- .apply (
116- MapElements .into (TypeDescriptor .of (GenericRecord .class ))
117- .via (AvroUtils .getRowToGenericRecordFunction (avroSchema )))
118- .setCoder (AvroCoder .of (avroSchema ))
119- .apply (
120- FileIO .<GenericRecord >write ()
121- .via (ParquetIO .sink (avroSchema ))
122- .to (repoPath + "/" )
123- .withNaming (
124- (BoundedWindow window ,
125- PaneInfo paneInfo ,
126- int numShards ,
127- int shardIndex ,
128- Compression compression ) -> "part-00000.parquet" ));
129- setupPipeline .run ().waitUntilFinish ();
130-
131- // 2. Find written Parquet file to inspect its size
132- BlobId parquetBlobId = BlobId .of (bucket , repoPrefix + "/part-00000.parquet" );
133- Blob parquetBlob = storage .get (parquetBlobId );
134- assertNotNull ("Parquet file not found on GCS: " + parquetBlobId , parquetBlob );
135- long fileSize = parquetBlob .getSize ();
136-
137- // 3. Create the Delta log commit file
138- String commitContent =
139- "{\" protocol\" :{\" minReaderVersion\" :1,\" minWriterVersion\" :2}}\n "
140- + "{\" metaData\" :{\" id\" :\" "
141- + salt
142- + "\" ,\" format\" :{\" provider\" :\" parquet\" ,\" options\" :{}},\" schemaString\" :\" {\\ \" type\\ \" :\\ \" struct\\ \" ,\\ \" fields\\ \" :[{\\ \" name\\ \" :\\ \" id\\ \" ,\\ \" type\\ \" :\\ \" integer\\ \" ,\\ \" nullable\\ \" :true,\\ \" metadata\\ \" :{}},{\\ \" name\\ \" :\\ \" name\\ \" ,\\ \" type\\ \" :\\ \" string\\ \" ,\\ \" nullable\\ \" :true,\\ \" metadata\\ \" :{}}]}\" ,\" partitionColumns\" :[],\" configuration\" :{},\" createdAt\" :123456789}}\n "
143- + "{\" add\" :{\" path\" :\" part-00000.parquet\" ,\" partitionValues\" :{},\" size\" :"
144- + fileSize
145- + ",\" modificationTime\" :123456789,\" dataChange\" :true}}" ;
146-
147- BlobId commitBlobId = BlobId .of (bucket , repoPrefix + "/_delta_log/00000000000000000000.json" );
148- BlobInfo commitBlobInfo =
149- BlobInfo .newBuilder (commitBlobId ).setContentType ("application/json" ).build ();
150- storage .create (commitBlobInfo , commitContent .getBytes (StandardCharsets .UTF_8 ));
108+ Configuration configuration = new Configuration ();
109+ configuration .set ("fs.gs.impl" , "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem" );
110+ configuration .set (
111+ "fs.AbstractFileSystem.gs.impl" , "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS" );
112+ configuration .set ("fs.gs.auth.type" , "APPLICATION_DEFAULT" );
113+ String project =
114+ readPipeline
115+ .getOptions ()
116+ .as (org .apache .beam .sdk .extensions .gcp .options .GcpOptions .class )
117+ .getProject ();
118+ if (project != null ) {
119+ configuration .set ("fs.gs.project.id" , project );
120+ }
121+
122+ Engine engine = DefaultEngine .create (configuration );
123+ Table table = Table .forPath (engine , repoPath );
124+
125+ StructType deltaSchema =
126+ new StructType ().add ("id" , IntegerType .INTEGER ).add ("name" , StringType .STRING );
127+
128+ TransactionBuilder txnBuilder =
129+ table .createTransactionBuilder (engine , "DeltaIOIT" , Operation .CREATE_TABLE );
130+ txnBuilder = txnBuilder .withSchema (engine , deltaSchema );
131+ Transaction txn = txnBuilder .build (engine );
132+ io .delta .kernel .data .Row txnState = txn .getTransactionState (engine );
133+
134+ ColumnVector idVector =
135+ new ColumnVector () {
136+ @ Override
137+ public DataType getDataType () {
138+ return IntegerType .INTEGER ;
139+ }
140+
141+ @ Override
142+ public int getSize () {
143+ return TEST_ROWS .size ();
144+ }
145+
146+ @ Override
147+ public void close () {}
148+
149+ @ Override
150+ public boolean isNullAt (int rowId ) {
151+ return TEST_ROWS .get (rowId ).getValue ("id" ) == null ;
152+ }
153+
154+ @ Override
155+ public int getInt (int rowId ) {
156+ return TEST_ROWS .get (rowId ).getInt32 ("id" );
157+ }
158+ };
159+
160+ ColumnVector nameVector =
161+ new ColumnVector () {
162+ @ Override
163+ public DataType getDataType () {
164+ return StringType .STRING ;
165+ }
166+
167+ @ Override
168+ public int getSize () {
169+ return TEST_ROWS .size ();
170+ }
171+
172+ @ Override
173+ public void close () {}
174+
175+ @ Override
176+ public boolean isNullAt (int rowId ) {
177+ return TEST_ROWS .get (rowId ).getValue ("name" ) == null ;
178+ }
179+
180+ @ Override
181+ public String getString (int rowId ) {
182+ return TEST_ROWS .get (rowId ).getString ("name" );
183+ }
184+ };
185+
186+ ColumnVector [] vectors = new ColumnVector [] {idVector , nameVector };
187+ ColumnarBatch columnarBatch = new DefaultColumnarBatch (TEST_ROWS .size (), deltaSchema , vectors );
188+ FilteredColumnarBatch filteredBatch =
189+ new FilteredColumnarBatch (columnarBatch , Optional .empty ());
190+
191+ CloseableIterator <FilteredColumnarBatch > data =
192+ io .delta .kernel .internal .util .Utils .toCloseableIterator (
193+ Collections .singletonList (filteredBatch ).iterator ());
194+
195+ CloseableIterator <FilteredColumnarBatch > physicalData =
196+ Transaction .transformLogicalData (engine , txnState , data , Collections .emptyMap ());
197+
198+ DataWriteContext writeContext =
199+ Transaction .getWriteContext (engine , txnState , Collections .emptyMap ());
200+
201+ CloseableIterator <DataFileStatus > dataFiles =
202+ engine
203+ .getParquetHandler ()
204+ .writeParquetFiles (
205+ writeContext .getTargetDirectory (),
206+ physicalData ,
207+ writeContext .getStatisticsColumns ());
208+
209+ CloseableIterator <io .delta .kernel .data .Row > dataActions =
210+ Transaction .generateAppendActions (engine , txnState , dataFiles , writeContext );
211+
212+ CloseableIterable <io .delta .kernel .data .Row > dataActionsIterable =
213+ CloseableIterable .inMemoryIterable (dataActions );
214+
215+ TransactionCommitResult commitResult = txn .commit (engine , dataActionsIterable );
216+
217+ if (commitResult .getVersion () < 0 ) {
218+ throw new RuntimeException ("Table creation/write failed" );
219+ }
220+
151221 LOG .info ("Successfully generated Delta Lake repository" );
152222 }
153223
@@ -172,10 +242,11 @@ public void testReadDeltaLakeTable() {
172242 hadoopConfig .put ("fs.gs.impl" , "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem" );
173243 hadoopConfig .put (
174244 "fs.AbstractFileSystem.gs.impl" , "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS" );
175- String project = readPipeline
176- .getOptions ()
177- .as (org .apache .beam .sdk .extensions .gcp .options .GcpOptions .class )
178- .getProject ();
245+ String project =
246+ readPipeline
247+ .getOptions ()
248+ .as (org .apache .beam .sdk .extensions .gcp .options .GcpOptions .class )
249+ .getProject ();
179250 if (project != null ) {
180251 hadoopConfig .put ("fs.gs.project.id" , project );
181252 }
0 commit comments