Skip to content

Commit 994aa31

Browse files
committed
Default the Hudi target to table version 6 and parameterize tests for versions 6 and 9
Flip HudiTargetConfig.DEFAULT_TABLE_VERSION from NINE to SIX so the default output stays readable by released Hudi readers; version 9 remains fully supported via xtable.hudi.target.table_version=9. Parameterize the Hudi test suites so every run exercises both table versions instead of only the default: - ITHudiConversionTarget: partitioned x {SIX, NINE} via a MethodSource cross-product; the target client sets the version through HudiTargetConfig.HUDI_TABLE_VERSION. - ITHudiConversionSource: source tables are created at {SIX, NINE} via the table-type/partition MethodSource cross-products, and the parameterized tests write through TestSparkHudiTable (Spark writer) instead of the Java client. - ITConversionController: combinations targeting HUDI are emitted once per version; getTableSyncConfig gained an overload that applies the version to the Hudi target properties. - TestHudiTargetConfig/TestHudiConversionTarget assert against DEFAULT_TABLE_VERSION instead of a hard-coded version. Version 9 source coverage in ITHudiConversionSource depends on two Hudi fixes validated against a locally patched 1.3.0-SNAPSHOT: apache/hudi#19126 (column stats on map/array-nested leaves during MOR log-append) and the savepoint backlog fix in the previous commit.
1 parent b871737 commit 994aa31

7 files changed

Lines changed: 207 additions & 74 deletions

File tree

xtable-core/src/main/java/org/apache/xtable/hudi/HudiTargetConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class HudiTargetConfig {
3434
*/
3535
public static final String HUDI_TABLE_VERSION = "xtable.hudi.target.table_version";
3636

37-
static final HoodieTableVersion DEFAULT_TABLE_VERSION = HoodieTableVersion.NINE;
37+
static final HoodieTableVersion DEFAULT_TABLE_VERSION = HoodieTableVersion.SIX;
3838

3939
HoodieTableVersion tableVersion;
4040

xtable-core/src/test/java/org/apache/xtable/ITConversionController.java

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import org.apache.hudi.common.model.HoodieAvroPayload;
8181
import org.apache.hudi.common.model.HoodieRecord;
8282
import org.apache.hudi.common.model.HoodieTableType;
83+
import org.apache.hudi.common.table.HoodieTableVersion;
8384
import org.apache.hudi.common.table.timeline.HoodieInstant;
8485

8586
import org.apache.iceberg.Snapshot;
@@ -101,6 +102,7 @@
101102
import org.apache.xtable.conversion.TargetTable;
102103
import org.apache.xtable.delta.DeltaConversionSourceProvider;
103104
import org.apache.xtable.hudi.HudiConversionSourceProvider;
105+
import org.apache.xtable.hudi.HudiTargetConfig;
104106
import org.apache.xtable.hudi.HudiTestUtil;
105107
import org.apache.xtable.iceberg.IcebergConversionSourceProvider;
106108
import 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
}

xtable-core/src/test/java/org/apache/xtable/TestSparkHudiTable.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.hudi.common.model.HoodieTableType;
4848
import org.apache.hudi.common.model.WriteOperationType;
4949
import org.apache.hudi.common.table.HoodieTableMetaClient;
50+
import org.apache.hudi.common.table.HoodieTableVersion;
5051
import org.apache.hudi.common.util.CommitUtils;
5152
import org.apache.hudi.common.util.Option;
5253
import org.apache.hudi.config.HoodieWriteConfig;
@@ -115,6 +116,17 @@ public static TestSparkHudiTable forStandardSchema(
115116
tableName, BASIC_SCHEMA, tempDir, jsc, partitionConfig, tableType);
116117
}
117118

119+
public static TestSparkHudiTable forStandardSchema(
120+
String tableName,
121+
Path tempDir,
122+
JavaSparkContext jsc,
123+
String partitionConfig,
124+
HoodieTableType tableType,
125+
HoodieTableVersion tableVersion) {
126+
return new TestSparkHudiTable(
127+
tableName, BASIC_SCHEMA, tempDir, jsc, partitionConfig, tableType, tableVersion);
128+
}
129+
118130
/**
119131
* Create a test table instance with a schema that has more fields than an instance returned by
120132
* {@link #forStandardSchema(String, Path, JavaSparkContext, String, HoodieTableType)}.
@@ -153,7 +165,20 @@ private TestSparkHudiTable(
153165
JavaSparkContext jsc,
154166
String partitionConfig,
155167
HoodieTableType hoodieTableType) {
168+
this(name, schema, tempDir, jsc, partitionConfig, hoodieTableType, HoodieTableVersion.SIX);
169+
}
170+
171+
private TestSparkHudiTable(
172+
String name,
173+
Schema schema,
174+
Path tempDir,
175+
JavaSparkContext jsc,
176+
String partitionConfig,
177+
HoodieTableType hoodieTableType,
178+
HoodieTableVersion tableVersion) {
156179
super(name, schema, tempDir, partitionConfig);
180+
// set the table version before initializing the write/meta clients, which read it
181+
this.tableVersion = tableVersion;
157182
// initialize spark session
158183
this.jsc = jsc;
159184
this.writeClient = initSparkWriteClient(schema, typedProperties);

0 commit comments

Comments
 (0)