8080import org .apache .hudi .common .model .HoodieAvroPayload ;
8181import org .apache .hudi .common .model .HoodieRecord ;
8282import org .apache .hudi .common .model .HoodieTableType ;
83+ import org .apache .hudi .common .table .HoodieTableVersion ;
8384import org .apache .hudi .common .table .timeline .HoodieInstant ;
8485
8586import org .apache .iceberg .Snapshot ;
101102import org .apache .xtable .conversion .TargetTable ;
102103import org .apache .xtable .delta .DeltaConversionSourceProvider ;
103104import org .apache .xtable .hudi .HudiConversionSourceProvider ;
105+ import org .apache .xtable .hudi .HudiTargetConfig ;
104106import org .apache .xtable .hudi .HudiTestUtil ;
105107import org .apache .xtable .iceberg .IcebergConversionSourceProvider ;
106108import org .apache .xtable .iceberg .TestIcebergDataHelper ;
@@ -153,7 +155,16 @@ private static Stream<Arguments> generateTestParametersForFormatsSyncModesAndPar
153155 for (String sourceFormat : Arrays .asList (HUDI , DELTA , ICEBERG , PAIMON )) {
154156 for (SyncMode syncMode : SyncMode .values ()) {
155157 for (boolean isPartitioned : new boolean [] {true , false }) {
156- arguments .add (Arguments .of (sourceFormat , syncMode , isPartitioned ));
158+ if (sourceFormat .equals (HUDI )) {
159+ // Hudi is the source here (not a target), so the Hudi target version does not apply.
160+ arguments .add (Arguments .of (sourceFormat , syncMode , isPartitioned , null ));
161+ } else {
162+ // Hudi is one of the targets; exercise both supported target versions.
163+ arguments .add (
164+ Arguments .of (sourceFormat , syncMode , isPartitioned , HoodieTableVersion .SIX ));
165+ arguments .add (
166+ Arguments .of (sourceFormat , syncMode , isPartitioned , HoodieTableVersion .NINE ));
167+ }
157168 }
158169 }
159170 }
@@ -225,7 +236,10 @@ private ConversionSourceProvider<?> getConversionSourceProvider(String sourceTab
225236 @ ParameterizedTest
226237 @ MethodSource ("generateTestParametersForFormatsSyncModesAndPartitioning" )
227238 public void testVariousOperations (
228- String sourceTableFormat , SyncMode syncMode , boolean isPartitioned ) {
239+ String sourceTableFormat ,
240+ SyncMode syncMode ,
241+ boolean isPartitioned ,
242+ HoodieTableVersion hudiTargetVersion ) {
229243 String tableName = getTableName ();
230244 List <String > targetTableFormats = getOtherFormats (sourceTableFormat );
231245 String partitionConfig = null ;
@@ -248,7 +262,8 @@ public void testVariousOperations(
248262 table ,
249263 targetTableFormats ,
250264 partitionConfig ,
251- null );
265+ null ,
266+ hudiTargetVersion );
252267 conversionController .sync (conversionConfig , conversionSourceProvider );
253268 checkDatasetEquivalence (sourceTableFormat , table , targetTableFormats , 100 );
254269
@@ -280,7 +295,8 @@ public void testVariousOperations(
280295 tableWithUpdatedSchema ,
281296 targetTableFormats ,
282297 partitionConfig ,
283- null );
298+ null ,
299+ hudiTargetVersion );
284300 List <Row > insertsAfterSchemaUpdate = tableWithUpdatedSchema .insertRows (100 );
285301 tableWithUpdatedSchema .reload ();
286302 conversionController .sync (conversionConfig , conversionSourceProvider );
@@ -524,43 +540,53 @@ private static Stream<Arguments> provideArgsForPartitionTesting() {
524540 String severityFilter = "severity = 1" ;
525541 String timestampAndLevelFilter = String .format ("%s and %s" , timestampFilter , levelFilter );
526542 return Stream .of (
527- Arguments .of (
528543 buildArgsForPartition (
529- HUDI , Arrays .asList (ICEBERG , DELTA ), "level:SIMPLE" , "level:VALUE" , levelFilter )),
530- Arguments .of (
544+ HUDI , Arrays .asList (ICEBERG , DELTA ), "level:SIMPLE" , "level:VALUE" , levelFilter ),
531545 buildArgsForPartition (
532- DELTA , Arrays .asList (ICEBERG , HUDI ), null , "level:VALUE" , levelFilter )),
533- Arguments .of (
546+ DELTA , Arrays .asList (ICEBERG , HUDI ), null , "level:VALUE" , levelFilter ),
534547 buildArgsForPartition (
535- ICEBERG , Arrays .asList (DELTA , HUDI ), null , "level:VALUE" , levelFilter )),
536- // Delta is excluded here since it does not support nested partition columns.
537- Arguments .of (
548+ ICEBERG , Arrays .asList (DELTA , HUDI ), null , "level:VALUE" , levelFilter ),
549+ // Delta is excluded here since it does not support nested partition columns.
538550 buildArgsForPartition (
539551 HUDI ,
540552 Arrays .asList (ICEBERG ),
541553 "nested_record.level:SIMPLE" ,
542554 "nested_record.level:VALUE" ,
543- nestedLevelFilter )),
544- Arguments .of (
555+ nestedLevelFilter ),
545556 buildArgsForPartition (
546557 HUDI ,
547558 Arrays .asList (ICEBERG , DELTA ),
548559 "severity:SIMPLE" ,
549560 "severity:VALUE" ,
550- severityFilter )),
551- Arguments .of (
561+ severityFilter ),
552562 buildArgsForPartition (
553563 HUDI ,
554564 Arrays .asList (ICEBERG , DELTA ),
555565 "timestamp_micros_nullable_field:TIMESTAMP,level:SIMPLE" ,
556566 "timestamp_micros_nullable_field:DAY:yyyy/MM/dd,level:VALUE" ,
557567 timestampAndLevelFilter ,
558- getAdditionalHudiReadOptions ())));
568+ getAdditionalHudiReadOptions ()))
569+ .flatMap (ITConversionController ::withHudiTargetVersions );
570+ }
571+
572+ /**
573+ * Expands a partition-test case across both Hudi target versions (6 and 9) when Hudi is one of
574+ * the target formats; otherwise yields the single case with no version override.
575+ */
576+ private static Stream <Arguments > withHudiTargetVersions (TableFormatPartitionDataHolder holder ) {
577+ if (holder .getTargetTableFormats ().contains (HUDI )) {
578+ return Stream .of (
579+ Arguments .of (holder , HoodieTableVersion .SIX ),
580+ Arguments .of (holder , HoodieTableVersion .NINE ));
581+ }
582+ return Stream .of (Arguments .of (holder , (HoodieTableVersion ) null ));
559583 }
560584
561585 @ ParameterizedTest
562586 @ MethodSource ("provideArgsForPartitionTesting" )
563- public void testPartitionedData (TableFormatPartitionDataHolder tableFormatPartitionDataHolder ) {
587+ public void testPartitionedData (
588+ TableFormatPartitionDataHolder tableFormatPartitionDataHolder ,
589+ HoodieTableVersion hudiTargetVersion ) {
564590 String tableName = getTableName ();
565591 String sourceTableFormat = tableFormatPartitionDataHolder .getSourceTableFormat ();
566592 List <String > targetTableFormats = tableFormatPartitionDataHolder .getTargetTableFormats ();
@@ -587,7 +613,8 @@ public void testPartitionedData(TableFormatPartitionDataHolder tableFormatPartit
587613 table ,
588614 targetTableFormats ,
589615 xTablePartitionConfig ,
590- null );
616+ null ,
617+ hudiTargetVersion );
591618 tableToClose .insertRows (100 );
592619 conversionController .sync (conversionConfig , conversionSourceProvider );
593620 // Do a second sync to force the test to read back the metadata it wrote earlier
@@ -1184,6 +1211,26 @@ private static ConversionConfig getTableSyncConfig(
11841211 List <String > targetTableFormats ,
11851212 String partitionConfig ,
11861213 Duration metadataRetention ) {
1214+ return getTableSyncConfig (
1215+ sourceTableFormat ,
1216+ syncMode ,
1217+ tableName ,
1218+ table ,
1219+ targetTableFormats ,
1220+ partitionConfig ,
1221+ metadataRetention ,
1222+ null );
1223+ }
1224+
1225+ private static ConversionConfig getTableSyncConfig (
1226+ String sourceTableFormat ,
1227+ SyncMode syncMode ,
1228+ String tableName ,
1229+ GenericTable table ,
1230+ List <String > targetTableFormats ,
1231+ String partitionConfig ,
1232+ Duration metadataRetention ,
1233+ HoodieTableVersion hudiTargetVersion ) {
11871234 Properties sourceProperties = new Properties ();
11881235 if (partitionConfig != null ) {
11891236 sourceProperties .put (PARTITION_FIELD_SPEC_CONFIG , partitionConfig );
@@ -1207,7 +1254,7 @@ private static ConversionConfig getTableSyncConfig(
12071254 // set the metadata path to the data path as the default (required by Hudi)
12081255 .basePath (table .getDataPath ())
12091256 .metadataRetention (metadataRetention )
1210- .additionalProperties (new TypedProperties ( ))
1257+ .additionalProperties (hudiTargetProperties ( formatName , hudiTargetVersion ))
12111258 .build ())
12121259 .collect (Collectors .toList ());
12131260
@@ -1217,4 +1264,18 @@ private static ConversionConfig getTableSyncConfig(
12171264 .syncMode (syncMode )
12181265 .build ();
12191266 }
1267+
1268+ /**
1269+ * Returns the additional properties for a target table, pinning the Hudi target table version
1270+ * when one is supplied (and the target is Hudi) so a single test can exercise both v6 and v9.
1271+ */
1272+ private static TypedProperties hudiTargetProperties (
1273+ String formatName , HoodieTableVersion hudiTargetVersion ) {
1274+ TypedProperties properties = new TypedProperties ();
1275+ if (HUDI .equals (formatName ) && hudiTargetVersion != null ) {
1276+ properties .setProperty (
1277+ HudiTargetConfig .HUDI_TABLE_VERSION , String .valueOf (hudiTargetVersion .versionCode ()));
1278+ }
1279+ return properties ;
1280+ }
12201281}
0 commit comments