hudi-agent commented on code in PR #19542:
URL: https://github.com/apache/hudi/pull/19542#discussion_r3725927884
##########
hudi-hadoop-common/src/test/java/org/apache/hudi/common/util/TestAvroOrcUtils.java:
##########
@@ -113,4 +136,226 @@ public void testLocalTimestampConvertedToLong() {
assertEquals(TypeDescription.Category.LONG,
orcLocalTimestampMicros.getCategory(),
"LocalTimestampMicros should convert to ORC Long (preserving old
behavior)");
}
+
+ @Test
+ public void testPrimitiveValuesRoundTripThroughColumnVectors() {
+ assertEquals(true,
roundTrip(HoodieSchema.create(HoodieSchemaType.BOOLEAN), true));
+ assertEquals(12, roundTrip(HoodieSchema.create(HoodieSchemaType.INT), 12));
+ assertEquals(34L, roundTrip(HoodieSchema.create(HoodieSchemaType.LONG),
34L));
+ assertEquals(1.25f, roundTrip(HoodieSchema.create(HoodieSchemaType.FLOAT),
1.25f));
+ assertEquals(2.5d, roundTrip(HoodieSchema.create(HoodieSchemaType.DOUBLE),
2.5d));
+ assertEquals("hoodie",
roundTrip(HoodieSchema.create(HoodieSchemaType.STRING), new
Utf8("hoodie")).toString());
+ assertEquals(42, roundTrip(HoodieSchema.createDate(), 42));
+ assertEquals(1_700_000_000_123L,
roundTrip(HoodieSchema.createTimestampMillis(), 1_700_000_000_123L));
+ assertEquals(1_700_000_000_123_456L,
+ roundTrip(HoodieSchema.createTimestampMicros(),
1_700_000_000_123_456L));
+
+ ByteBuffer binary = (ByteBuffer) roundTrip(
+ HoodieSchema.create(HoodieSchemaType.BYTES), ByteBuffer.wrap(new
byte[] {1, 2, 3}));
+ assertArrayEquals(new byte[] {1, 2, 3}, toByteArray(binary));
+ }
+
+ @Test
+ public void testComplexValuesRoundTripThroughColumnVectors() {
+ HoodieSchema arraySchema =
HoodieSchema.createArray(HoodieSchema.create(HoodieSchemaType.INT));
+ assertEquals(Arrays.asList(1, 2, 3), roundTrip(arraySchema,
Arrays.asList(1, 2, 3)));
+
+ HoodieSchema mapSchema =
HoodieSchema.createMap(HoodieSchema.create(HoodieSchemaType.LONG));
+ Map<String, Long> values = new LinkedHashMap<>();
+ values.put("first", 10L);
+ values.put("second", 20L);
+ assertEquals(values, roundTrip(mapSchema, values));
+
+ HoodieSchema recordSchema = HoodieSchema.createRecord("nested", null,
null, Arrays.asList(
+ HoodieSchemaField.of("name",
HoodieSchema.create(HoodieSchemaType.STRING), null, null),
+ HoodieSchemaField.of("count",
HoodieSchema.create(HoodieSchemaType.INT), null, null)));
+ GenericRecord record = new GenericData.Record(recordSchema.toAvroSchema());
+ record.put("name", "record-name");
+ record.put("count", 7);
+ GenericRecord converted = (GenericRecord) roundTrip(recordSchema, record);
+ assertEquals("record-name", converted.get("name").toString());
+ assertEquals(7, converted.get("count"));
+
+ HoodieSchema unionSchema = HoodieSchema.createUnion(Arrays.asList(
+ HoodieSchema.create(HoodieSchemaType.INT),
HoodieSchema.create(HoodieSchemaType.STRING)));
+ assertEquals(9, roundTrip(unionSchema, 9));
+ assertEquals("union-value", roundTrip(unionSchema,
"union-value").toString());
+ }
+
+ @Test
+ public void testDecimalEnumAndFixedValuesRoundTripThroughColumnVectors() {
+ HoodieSchema decimalSchema = HoodieSchema.createDecimal(10, 2);
+ ByteBuffer decimalBytes = (ByteBuffer) roundTrip(decimalSchema, new
BigDecimal("1234.50"));
+ assertEquals(new BigInteger("123450"), new
BigInteger(toByteArray(decimalBytes)));
+
+ HoodieSchema fixedDecimalSchema =
HoodieSchema.parse("{\"type\":\"fixed\",\"name\":\"amount\","
+ +
"\"size\":8,\"logicalType\":\"decimal\",\"precision\":12,\"scale\":2}");
+ BigDecimal fixedValue = new BigDecimal("9876.50");
+ GenericData.Fixed fixed = (GenericData.Fixed) new
Conversions.DecimalConversion().toFixed(
+ fixedValue, fixedDecimalSchema.toAvroSchema(),
fixedDecimalSchema.toAvroSchema().getLogicalType());
+ GenericData.Fixed convertedFixed = (GenericData.Fixed)
roundTrip(fixedDecimalSchema, fixed);
+ assertEquals(fixedValue, new Conversions.DecimalConversion().fromFixed(
+ convertedFixed, fixedDecimalSchema.toAvroSchema(),
fixedDecimalSchema.toAvroSchema().getLogicalType()));
+
+ HoodieSchema enumSchema = HoodieSchema.parse(
+
"{\"type\":\"enum\",\"name\":\"status\",\"symbols\":[\"OPEN\",\"CLOSED\"]}");
+ GenericData.EnumSymbol open = new
GenericData.EnumSymbol(enumSchema.toAvroSchema(), "OPEN");
+ assertEquals(open, roundTrip(enumSchema, open));
+ }
+
+ @Test
+ public void testNullResizeAndRepeatingVectors() {
+ HoodieSchema schema = HoodieSchema.create(HoodieSchemaType.LONG);
+ TypeDescription type = AvroOrcUtils.createOrcSchema(schema);
+ VectorizedRowBatch batch =
TypeDescription.createStruct().addField("value", type).createRowBatch();
+ ColumnVector vector = batch.cols[0];
+
+ int expandedPosition = vector.isNull.length;
+ AvroOrcUtils.addToVector(type, vector, schema, null, expandedPosition);
+ assertFalse(vector.noNulls);
+ assertTrue(vector.isNull[expandedPosition]);
+ assertNull(AvroOrcUtils.readFromVector(type, vector, schema,
expandedPosition));
+
+ ((LongColumnVector) vector).vector[0] = 99;
+ vector.isNull[0] = false;
+ vector.isRepeating = true;
+ assertEquals(99L, AvroOrcUtils.readFromVector(type, vector, schema, 17));
+ }
+
+ @Test
+ public void testCreateSchemaCoversEveryOrcCategory() {
+ TypeDescription orcSchema = TypeDescription.fromString("struct<"
+ +
"boolean_field:boolean,byte_field:tinyint,short_field:smallint,int_field:int,long_field:bigint,"
+ +
"float_field:float,double_field:double,string_field:string,char_field:char(5),varchar_field:varchar(8),"
+ +
"date_field:date,timestamp_field:timestamp,binary_field:binary,decimal_field:decimal(12,3),"
+ +
"list_field:array<int>,map_field:map<string,bigint>,union_field:uniontype<int,string>>");
+
+ HoodieSchema schema = AvroOrcUtils.createSchema(orcSchema);
+ assertEquals(HoodieSchemaType.RECORD, schema.getType());
+ assertEquals(17, schema.getFields().size());
+ assertEquals(HoodieSchemaType.BOOLEAN,
schema.getField("boolean_field").get().schema().getType());
+ assertEquals(HoodieSchemaType.BYTES,
schema.getField("binary_field").get().schema().getType());
+ assertEquals(HoodieSchemaType.ARRAY,
schema.getField("list_field").get().schema().getType());
+ assertEquals(HoodieSchemaType.MAP,
schema.getField("map_field").get().schema().getType());
+ assertEquals(HoodieSchemaType.UNION,
schema.getField("union_field").get().schema().getType());
+ }
+
+ @Test
+ public void testCreateSchemaWithDefaultsAndNestedNamespaces() {
+ TypeDescription orcSchema = TypeDescription.fromString(
+
"struct<id:bigint,nested:struct<name:string,active:boolean>,amount:decimal(8,2),tags:array<string>>");
+
+ HoodieSchema nullable = AvroOrcUtils.createSchemaWithDefaultValue(
+ orcSchema, "root_record", "org.apache.hudi.test", true);
+ assertEquals("root_record", nullable.getName());
+ assertTrue(nullable.getFields().stream().allMatch(field ->
field.schema().getType() == HoodieSchemaType.UNION));
+
+ HoodieSchema required = AvroOrcUtils.createSchemaWithDefaultValue(
+ orcSchema, "root_record", "org.apache.hudi.test", false);
+ assertTrue(required.getFields().stream().noneMatch(field ->
field.schema().getType() == HoodieSchemaType.UNION));
+ assertEquals("nested",
required.getField("nested").get().schema().getName());
+ }
+
+ @Test
+ public void testCachingOfByteReferencesDoesNotCopyInput() {
+ HoodieSchema schema = HoodieSchema.create(HoodieSchemaType.BYTES);
+ TypeDescription type = AvroOrcUtils.createOrcSchema(schema);
+ VectorizedRowBatch batch =
TypeDescription.createStruct().addField("value", type).createRowBatch();
+ byte[] input = new byte[] {4, 5, 6};
+
+ AvroOrcUtils.addToVector(type, batch.cols[0], schema, input, 0);
+
+ BytesColumnVector vector = (BytesColumnVector) batch.cols[0];
+ assertSame(input, vector.vector[0]);
+ }
+
+ @Test
+ public void testAdditionalOrcVectorRepresentationsAndFailures() {
+ assertEquals((byte) 7, roundTrip(TypeDescription.createByte(),
HoodieSchema.create(HoodieSchemaType.INT), (byte) 7));
+ assertEquals((short) 11, roundTrip(TypeDescription.createShort(),
HoodieSchema.create(HoodieSchemaType.INT), (short) 11));
+ assertEquals("char",
roundTrip(TypeDescription.createChar().withMaxLength(8),
+ HoodieSchema.create(HoodieSchemaType.STRING), "char"));
+ assertEquals("varchar",
roundTrip(TypeDescription.createVarchar().withMaxLength(8),
+ HoodieSchema.create(HoodieSchemaType.STRING), "varchar"));
+
+ HoodieSchema dateSchema = HoodieSchema.create(HoodieSchemaType.INT);
+ assertEquals(1, roundTrip(TypeDescription.createDate(), dateSchema,
Date.valueOf("1970-01-02")));
+ assertEquals(2, roundTrip(TypeDescription.createDate(), dateSchema, new
java.util.Date(2L * 86_400_000L)));
Review Comment:
🤖 This assertion looks timezone-dependent. `new java.util.Date(2 *
86_400_000L)` is midnight UTC on day 2, but for a plain `java.util.Date` the
DATE path goes through `DateWritable.millisToDays`, which adds
`TimeZone.getDefault().getOffset(...)`. In any negative-offset zone (e.g. US
Pacific/Eastern) `(172800000 + negativeOffset)/86400000` truncates to 1, not 2,
so this would fail off-UTC. Could you pin the JVM timezone to UTC here, or use
a `java.sql.Date` (offset-corrected via `dateToDays`, like line 282) instead?
<sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag
quality.</i></sub>
##########
hudi-hadoop-common/src/test/java/org/apache/hudi/common/util/TestAvroOrcUtils.java:
##########
@@ -113,4 +136,226 @@ public void testLocalTimestampConvertedToLong() {
assertEquals(TypeDescription.Category.LONG,
orcLocalTimestampMicros.getCategory(),
"LocalTimestampMicros should convert to ORC Long (preserving old
behavior)");
}
+
+ @Test
+ public void testPrimitiveValuesRoundTripThroughColumnVectors() {
+ assertEquals(true,
roundTrip(HoodieSchema.create(HoodieSchemaType.BOOLEAN), true));
Review Comment:
🤖 nit: `assertEquals(true, ...)` reads less clearly than `assertTrue(...)`.
The same applies to the boolean assertion pattern throughout this test — could
you swap those to `assertTrue`/`assertFalse` so the intent is immediately
obvious?
<sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag
quality.</i></sub>
##########
hudi-hadoop-common/src/test/java/org/apache/hudi/hadoop/fs/TestHadoopFSUtils.java:
##########
@@ -255,6 +269,121 @@ public void testFileStatusConversion(String path,
convertedPathInfo, path, length, isDirectory, blockReplication,
blockSize, modificationTime);
}
+ @Test
+ public void testStorageConfigurationCopyAndFileSystemOverloads(@TempDir
java.nio.file.Path tempDir) {
+ Configuration conf = new Configuration(false);
+ conf.set("test.key", "before");
+ StorageConfiguration<Configuration> shared =
HadoopFSUtils.getStorageConf(conf);
+ StorageConfiguration<Configuration> copied =
HadoopFSUtils.getStorageConfWithCopy(conf);
+ conf.set("test.key", "after");
+
+ assertEquals("after", shared.getString("test.key").get());
+ assertEquals("before", copied.getString("test.key").get());
+ assertEquals("file", HadoopFSUtils.getFs(tempDir.toUri().toString(),
shared).getScheme());
+ assertEquals("file", HadoopFSUtils.getFs(tempDir.toUri().toString(),
shared, true).getScheme());
+ assertEquals("file", HadoopFSUtils.getFs(new Path(tempDir.toUri()),
shared).getScheme());
+ assertEquals("file", HadoopFSUtils.getFs(new Path(tempDir.toUri()),
shared, true).getScheme());
+ assertEquals("file", HadoopFSUtils.getFs(new StoragePath(tempDir.toUri()),
conf).getScheme());
+ assertEquals("file", HadoopFSUtils.getFs(tempDir.toString(), conf,
true).getScheme());
+ assertInstanceOf(StorageConfiguration.class,
HadoopFSUtils.getStorageConf());
+ }
+
+ @Test
+ public void testAvroPathPermissionAndStatusConversions() {
+ Path path = new Path("s3://bucket/table/file.parquet");
Review Comment:
🤖 nit: `assertEquals(null, ...)` shows up several times in this method —
could you replace those with `assertNull(...)` (e.g.
`assertNull(HadoopFSUtils.toPath(null))`)? It reads more naturally and gives a
better failure message.
<sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag
quality.</i></sub>
##########
hudi-hadoop-common/src/test/java/org/apache/hudi/hadoop/fs/TestCachingPath.java:
##########
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hudi.hadoop.fs;
+
+import org.apache.hadoop.fs.Path;
+import org.junit.jupiter.api.Test;
+
+import java.net.URI;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestCachingPath {
+
+ @Test
+ void testCachesDerivedValues() {
+ CachingPath path = new
CachingPath("s3://bucket/table/partition/file.parquet");
+
+ String name = path.getName();
+ Path parent = path.getParent();
+ String fullPath = path.toString();
+
+ assertSame(name, path.getName());
+ assertSame(parent, path.getParent());
+ assertSame(fullPath, path.toString());
+ assertEquals("file.parquet", name);
+ assertEquals("s3://bucket/table/partition", parent.toString());
+ }
+
+ @Test
+ void testEveryConstructorCreatesEquivalentPath() {
+ Path parent = new Path("file:///tmp/table");
+ Path child = new Path("partition/file.parquet");
+ URI expected = URI.create("file:/tmp/table/partition/file.parquet");
+
+ assertEquals(expected, new CachingPath(parent.toString(),
child.toString()).toUri());
+ assertEquals(expected, new CachingPath(parent, child.toString()).toUri());
+ assertEquals(expected, new CachingPath(parent.toString(), child).toUri());
+ assertEquals(expected, new CachingPath(parent, child).toUri());
+ assertEquals(expected, new CachingPath(expected).toUri());
+ }
+
+ @Test
+ void testWrapAndSubPath() {
+ CachingPath cached = new CachingPath("s3://bucket/table");
+ assertSame(cached, CachingPath.wrap(cached));
+
+ CachingPath wrapped = CachingPath.wrap(new Path("s3://bucket/table"));
+ assertEquals(cached, wrapped);
+ assertEquals("s3://bucket/table/partition/file.parquet",
+ wrapped.subPath("partition/file.parquet").toString());
+ }
+
+ @Test
+ void testUnsafeConcatenationHandlesSeparatorsAndPreservesUriParts() throws
Exception {
+ Path base = new Path(new URI("s3", "bucket", "/table", "version=1",
"fragment"));
+
+ CachingPath expected = CachingPath.concatPathUnsafe(base, "partition");
+ assertEquals("/table/partition", expected.toUri().getPath());
+ assertEquals("version=1", expected.toUri().getQuery());
+ assertEquals("fragment", expected.toUri().getFragment());
+
+ assertEquals("/table/partition", CachingPath.concatPathUnsafe(new
Path("s3://bucket/table/"), "/partition").toUri().getPath());
+ assertEquals("/table/partition", CachingPath.concatPathUnsafe(new
Path("s3://bucket/table/"), "partition").toUri().getPath());
+ assertEquals("/table/partition", CachingPath.concatPathUnsafe(new
Path("s3://bucket/table"), "/partition").toUri().getPath());
+ assertEquals("/table/partition", CachingPath.concatPathUnsafe(new
Path("s3://bucket/table"), new Path("partition")).toUri().getPath());
+ assertThrows(IllegalStateException.class,
+ () -> CachingPath.concatPathUnsafe(base, new
Path("s3://other/absolute")));
+ }
+
+ @Test
+ void testRelativePathAndSchemeRemoval() {
+ CachingPath relative =
CachingPath.createRelativePathUnsafe("partition%2Fvalue/file.parquet");
+ assertTrue(!relative.toUri().isAbsolute());
+ assertEquals("partition%2Fvalue/file.parquet", relative.toString());
Review Comment:
🤖 nit: `assertTrue(!relative.toUri().isAbsolute())` — could you flip this to
`assertFalse(relative.toUri().isAbsolute())`? The negation inside assertTrue
makes the intent a little harder to read at a glance.
<sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag
quality.</i></sub>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]