This is an automated email from the ASF dual-hosted git repository.

yihua pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/master by this push:
     new 394229ca5c9f feat(schema): HoodieSchema: add helper methods, fix 
issues with schema subtypes not returned (#14346)
394229ca5c9f is described below

commit 394229ca5c9fb8a6f8749805b3678ddfeb4ea788
Author: Tim Brown <[email protected]>
AuthorDate: Tue Nov 25 01:29:43 2025 -0500

    feat(schema): HoodieSchema: add helper methods, fix issues with schema 
subtypes not returned (#14346)
---
 .../apache/hudi/common/schema/HoodieSchema.java    |  98 ++++++++++-------
 .../hudi/common/schema/TestHoodieSchema.java       | 121 ++++++++++++---------
 .../schema/TestHoodieSchemaCompatibility.java      |   2 +-
 3 files changed, 127 insertions(+), 94 deletions(-)

diff --git 
a/hudi-common/src/main/java/org/apache/hudi/common/schema/HoodieSchema.java 
b/hudi-common/src/main/java/org/apache/hudi/common/schema/HoodieSchema.java
index c866cb56667d..ebdca4903d42 100644
--- a/hudi-common/src/main/java/org/apache/hudi/common/schema/HoodieSchema.java
+++ b/hudi-common/src/main/java/org/apache/hudi/common/schema/HoodieSchema.java
@@ -106,9 +106,22 @@ public class HoodieSchema implements Serializable {
    *
    * @param avroSchema the Avro schema to wrap
    * @return new HoodieSchema instance
-   * @throws IllegalArgumentException if avroSchema is null
    */
   public static HoodieSchema fromAvroSchema(Schema avroSchema) {
+    if (avroSchema == null) {
+      return null;
+    }
+    LogicalType logicalType = avroSchema.getLogicalType();
+    if (logicalType != null) {
+      if (logicalType instanceof LogicalTypes.Decimal) {
+        return new HoodieSchema.Decimal(avroSchema);
+      } else if (logicalType instanceof LogicalTypes.TimeMillis || logicalType 
instanceof LogicalTypes.TimeMicros) {
+        return new HoodieSchema.Time(avroSchema);
+      } else if (logicalType instanceof LogicalTypes.TimestampMillis || 
logicalType instanceof LogicalTypes.TimestampMicros
+          || logicalType instanceof LogicalTypes.LocalTimestampMillis || 
logicalType instanceof LogicalTypes.LocalTimestampMicros) {
+        return new HoodieSchema.Timestamp(avroSchema);
+      }
+    }
     return new HoodieSchema(avroSchema);
   }
 
@@ -149,6 +162,16 @@ public class HoodieSchema implements Serializable {
     return new HoodieSchema(avroSchema);
   }
 
+  /**
+   * Creates a nullable schema for the specified primitive type.
+   * @param type the primitive schema type
+   * @return new HoodieSchema representing a nullable version of the primitive 
type
+   */
+  public static HoodieSchema createNullable(HoodieSchemaType type) {
+    HoodieSchema nonNullSchema = create(type);
+    return createNullable(nonNullSchema);
+  }
+
   /**
    * Creates a nullable schema (union of null and the specified schema).
    *
@@ -171,14 +194,14 @@ public class HoodieSchema implements Serializable {
       }
 
       // Add null to existing union
-      List<Schema> newUnionTypes = new ArrayList<>();
+      List<Schema> newUnionTypes = new ArrayList<>(unionTypes.size() + 1);
       newUnionTypes.add(Schema.create(Schema.Type.NULL));
       newUnionTypes.addAll(unionTypes);
       Schema nullableSchema = Schema.createUnion(newUnionTypes);
       return new HoodieSchema(nullableSchema);
     } else {
       // Create new union with null
-      List<Schema> unionTypes = new ArrayList<>();
+      List<Schema> unionTypes = new ArrayList<>(2);
       unionTypes.add(Schema.create(Schema.Type.NULL));
       unionTypes.add(inputAvroSchema);
       Schema nullableSchema = Schema.createUnion(unionTypes);
@@ -492,19 +515,6 @@ public class HoodieSchema implements Serializable {
     return HoodieSchema.fromAvroSchema(resultAvro);
   }
 
-  /**
-   * Creates a nullable union (null + specified type).
-   *
-   * @param type the non-null type
-   * @return new HoodieSchema representing a nullable union
-   */
-  public static HoodieSchema createNullableSchema(HoodieSchema type) {
-    ValidationUtils.checkArgument(type != null, "Type cannot be null");
-
-    HoodieSchema nullSchema = HoodieSchema.create(HoodieSchemaType.NULL);
-    return createUnion(nullSchema, type);
-  }
-
   /**
    * Returns the type of this schema.
    *
@@ -515,15 +525,15 @@ public class HoodieSchema implements Serializable {
   }
 
   /**
-   * Returns the name of this schema, if it has one.
+   * Returns the name of the schema if a record, otherwise it returns the name 
of the type.
    *
-   * @return Option containing the schema name, or Option.empty() if none
+   * @return the schema name
    */
-  public Option<String> getName() {
+  public String getName() {
     if (avroSchema.getLogicalType() != null) {
-      return Option.of(type.name().toLowerCase(Locale.ENGLISH));
+      return type.name().toLowerCase(Locale.ENGLISH);
     }
-    return Option.ofNullable(avroSchema.getName());
+    return avroSchema.getName();
   }
 
   /**
@@ -536,12 +546,12 @@ public class HoodieSchema implements Serializable {
   }
 
   /**
-   * Returns the full name of this schema (namespace + name).
+   * Returns the full name of this schema (namespace + name) if a record, or 
the type name otherwise.
    *
-   * @return Option containing the full schema name, or Option.empty() if none
+   * @return The full schema name, or name of the type if not a record
    */
-  public Option<String> getFullName() {
-    return Option.ofNullable(avroSchema.getFullName());
+  public String getFullName() {
+    return avroSchema.getFullName();
   }
 
   /**
@@ -617,7 +627,7 @@ public class HoodieSchema implements Serializable {
       throw new IllegalStateException("Cannot get element type from non-array 
schema: " + type);
     }
 
-    return new HoodieSchema(avroSchema.getElementType());
+    return HoodieSchema.fromAvroSchema(avroSchema.getElementType());
   }
 
   /**
@@ -631,7 +641,7 @@ public class HoodieSchema implements Serializable {
       throw new IllegalStateException("Cannot get value type from non-map 
schema: " + type);
     }
 
-    return new HoodieSchema(avroSchema.getValueType());
+    return HoodieSchema.fromAvroSchema(avroSchema.getValueType());
   }
 
   /**
@@ -646,7 +656,7 @@ public class HoodieSchema implements Serializable {
     }
 
     return avroSchema.getTypes().stream()
-        .map(HoodieSchema::new)
+        .map(HoodieSchema::fromAvroSchema)
         .collect(Collectors.toList());
   }
 
@@ -736,14 +746,14 @@ public class HoodieSchema implements Serializable {
   }
 
   /**
-   * If this is a union schema, returns the non-null type.
+   * If this is a union schema, returns the non-null type. Otherwise, returns 
this schema.
    *
-   * @return the non-null schema from a union
-   * @throws IllegalStateException if this is not a nullable union
+   * @return the non-null schema from a union or the current schema
+   * @throws IllegalStateException if the union has more than two types
    */
   public HoodieSchema getNonNullType() {
     if (type != HoodieSchemaType.UNION) {
-      throw new IllegalStateException("Cannot get non-null type from non-union 
schema: " + type);
+      return this;
     }
 
     List<HoodieSchema> types = getTypes();
@@ -1121,8 +1131,12 @@ public class HoodieSchema implements Serializable {
     }
 
     @Override
-    public Option<String> getName() {
-      return Option.of(String.format("decimal(%d,%d)", precision, scale));
+    public String getName() {
+      return String.format("decimal(%d,%d)", precision, scale);
+    }
+
+    public boolean isFixed() {
+      return fixedSize.isPresent();
     }
 
     @Override
@@ -1193,18 +1207,18 @@ public class HoodieSchema implements Serializable {
     }
 
     @Override
-    public Option<String> getName() {
+    public String getName() {
       if (isUtcAdjusted) {
         if (precision == TimePrecision.MILLIS) {
-          return Option.of("timestamp-millis");
+          return "timestamp-millis";
         } else {
-          return Option.of("timestamp-micros");
+          return "timestamp-micros";
         }
       } else {
         if (precision == TimePrecision.MILLIS) {
-          return Option.of("local-timestamp-millis");
+          return "local-timestamp-millis";
         } else {
-          return Option.of("local-timestamp-micros");
+          return "local-timestamp-micros";
         }
       }
     }
@@ -1255,11 +1269,11 @@ public class HoodieSchema implements Serializable {
     }
 
     @Override
-    public Option<String> getName() {
+    public String getName() {
       if (precision == TimePrecision.MILLIS) {
-        return Option.of("time-millis");
+        return "time-millis";
       } else {
-        return Option.of("time-micros");
+        return "time-micros";
       }
     }
 
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchema.java 
b/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchema.java
index cba2af9ac901..25ea4c16f7e1 100644
--- 
a/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchema.java
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchema.java
@@ -36,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -74,13 +75,7 @@ public class TestHoodieSchema {
 
   @Test
   public void testSchemaCreationWithNullAvroSchema() {
-    assertThrows(IllegalArgumentException.class, () -> {
-      HoodieSchema.fromAvroSchema(null);
-    }, "Should throw exception for null Avro schema");
-
-    assertThrows(IllegalArgumentException.class, () -> {
-      HoodieSchema.fromAvroSchema(null);
-    }, "Should throw exception for null Avro schema in constructor");
+    assertNull(HoodieSchema.fromAvroSchema(null));
   }
 
   @Test
@@ -89,9 +84,9 @@ public class TestHoodieSchema {
 
     assertNotNull(schema);
     assertEquals(HoodieSchemaType.RECORD, schema.getType());
-    assertEquals(Option.of("User"), schema.getName());
+    assertEquals("User", schema.getName());
     assertEquals(Option.of("com.example"), schema.getNamespace());
-    assertEquals(Option.of("com.example.User"), schema.getFullName());
+    assertEquals("com.example.User", schema.getFullName());
     assertEquals(Option.of("User record schema"), schema.getDoc());
 
     List<HoodieSchemaField> fields = schema.getFields();
@@ -174,14 +169,13 @@ public class TestHoodieSchema {
 
     assertNotNull(fixedSchema);
     assertEquals(HoodieSchemaType.FIXED, fixedSchema.getType());
-    assertEquals("FixedField", fixedSchema.getName().get());
+    assertEquals("FixedField", fixedSchema.getName());
     assertEquals(16, fixedSchema.getFixedSize());
   }
 
   @Test
   public void testNullableSchemaCreation() {
-    HoodieSchema stringSchema = HoodieSchema.create(HoodieSchemaType.STRING);
-    HoodieSchema nullableSchema = HoodieSchema.createNullable(stringSchema);
+    HoodieSchema nullableSchema = 
HoodieSchema.createNullable(HoodieSchemaType.STRING);
 
     assertEquals(HoodieSchemaType.UNION, nullableSchema.getType());
     assertTrue(nullableSchema.isNullable());
@@ -318,13 +312,9 @@ public class TestHoodieSchema {
     assertEquals(HoodieSchemaType.STRING, nonNullType.getType());
 
     // Test with non-union schema
-    assertThrows(IllegalStateException.class, () -> {
-      stringSchema.getTypes();
-    }, "Should throw exception when getting types from non-union schema");
+    assertThrows(IllegalStateException.class, stringSchema::getTypes, "Should 
throw exception when getting types from non-union schema");
 
-    assertThrows(IllegalStateException.class, () -> {
-      stringSchema.getNonNullType();
-    }, "Should throw exception when getting non-null type from non-union 
schema");
+    assertSame(stringSchema, stringSchema.getNonNullType());
   }
 
   @Test
@@ -337,9 +327,7 @@ public class TestHoodieSchema {
 
     // Test with non-fixed schema
     HoodieSchema stringSchema = HoodieSchema.create(HoodieSchemaType.STRING);
-    assertThrows(IllegalStateException.class, () -> {
-      stringSchema.getFixedSize();
-    }, "Should throw exception when getting fixed size from non-fixed schema");
+    assertThrows(IllegalStateException.class, stringSchema::getFixedSize, 
"Should throw exception when getting fixed size from non-fixed schema");
   }
 
   @Test
@@ -353,25 +341,23 @@ public class TestHoodieSchema {
 
     // Test with non-enum schema
     HoodieSchema stringSchema = HoodieSchema.create(HoodieSchemaType.STRING);
-    assertThrows(IllegalStateException.class, () -> {
-      stringSchema.getEnumSymbols();
-    }, "Should throw exception when getting symbols from non-enum schema");
+    assertThrows(IllegalStateException.class, stringSchema::getEnumSymbols, 
"Should throw exception when getting symbols from non-enum schema");
   }
 
   @Test
   public void testSchemaProperties() {
     HoodieSchema schema = HoodieSchema.parse(SAMPLE_RECORD_SCHEMA);
 
-    assertEquals(Option.of("User"), schema.getName());
+    assertEquals("User", schema.getName());
     assertEquals(Option.of("com.example"), schema.getNamespace());
-    assertEquals(Option.of("com.example.User"), schema.getFullName());
+    assertEquals("com.example.User", schema.getFullName());
     assertEquals(Option.of("User record schema"), schema.getDoc());
 
     // Test schema without these properties (primitive types have their type 
name as name but no namespace)
     HoodieSchema stringSchema = HoodieSchema.create(HoodieSchemaType.STRING);
-    assertEquals(Option.of("string"), stringSchema.getName()); // Primitive 
types use type name
+    assertEquals("string", stringSchema.getName()); // Primitive types use 
type name
     // Note: Don't test getNamespace() on primitive types as Avro throws 
exception
-    assertEquals(Option.of("string"), stringSchema.getFullName()); // Same as 
name for primitives
+    assertEquals("string", stringSchema.getFullName()); // Same as name for 
primitives
     assertEquals(Option.empty(), stringSchema.getDoc());
   }
 
@@ -452,7 +438,7 @@ public class TestHoodieSchema {
 
     assertNotNull(recordSchema);
     assertEquals(HoodieSchemaType.RECORD, recordSchema.getType());
-    assertEquals(Option.of("User"), recordSchema.getName());
+    assertEquals("User", recordSchema.getName());
     assertEquals(Option.of("com.example"), recordSchema.getNamespace());
     assertEquals(Option.of("User record schema"), recordSchema.getDoc());
     assertEquals(2, recordSchema.getFields().size());
@@ -468,7 +454,7 @@ public class TestHoodieSchema {
 
     HoodieSchema recordSchema = HoodieSchema.createRecord("User", 
"com.example", "User record", fields);
     assertEquals(HoodieSchemaType.RECORD, recordSchema.getType());
-    assertEquals(Option.of("User"), recordSchema.getName());
+    assertEquals("User", recordSchema.getName());
 
     // Test createUnion (varargs version)
     HoodieSchema stringSchema = HoodieSchema.create(HoodieSchemaType.STRING);
@@ -481,13 +467,13 @@ public class TestHoodieSchema {
     List<String> symbols = Arrays.asList("RED", "GREEN", "BLUE");
     HoodieSchema enumSchema = HoodieSchema.createEnum("Color", "com.example", 
"Color enum", symbols);
     assertEquals(HoodieSchemaType.ENUM, enumSchema.getType());
-    assertEquals(Option.of("Color"), enumSchema.getName());
+    assertEquals("Color", enumSchema.getName());
     assertEquals(symbols, enumSchema.getEnumSymbols());
 
     // Test createFixed
     HoodieSchema fixedSchema = HoodieSchema.createFixed("MD5", "com.example", 
"MD5 hash", 16);
     assertEquals(HoodieSchemaType.FIXED, fixedSchema.getType());
-    assertEquals(Option.of("MD5"), fixedSchema.getName());
+    assertEquals("MD5", fixedSchema.getName());
     assertEquals(16, fixedSchema.getFixedSize());
   }
 
@@ -533,7 +519,7 @@ public class TestHoodieSchema {
 
     assertNotNull(schema);
     assertEquals(HoodieSchemaType.RECORD, schema.getType());
-    assertEquals("Test", schema.getName().get());
+    assertEquals("Test", schema.getName());
     assertEquals(2, schema.getFields().size());
   }
 
@@ -548,7 +534,7 @@ public class TestHoodieSchema {
 
     assertNotNull(recordSchema);
     assertEquals(HoodieSchemaType.RECORD, recordSchema.getType());
-    assertEquals("ErrorRecord", recordSchema.getName().get());
+    assertEquals("ErrorRecord", recordSchema.getName());
     assertEquals("com.example", recordSchema.getNamespace().get());
     assertTrue(recordSchema.isError());
     assertEquals(2, recordSchema.getFields().size());
@@ -600,7 +586,7 @@ public class TestHoodieSchema {
 
     assertNotNull(recordSchema);
     assertEquals(HoodieSchemaType.RECORD, recordSchema.getType());
-    assertEquals(Option.of("User"), recordSchema.getName());
+    assertEquals("User", recordSchema.getName());
     assertEquals(2, recordSchema.getFields().size());
 
     // Verify fields
@@ -623,9 +609,9 @@ public class TestHoodieSchema {
 
     assertNotNull(recordSchema);
     assertEquals(HoodieSchemaType.RECORD, recordSchema.getType());
-    assertEquals(Option.of("TestRecord"), recordSchema.getName());
+    assertEquals("TestRecord", recordSchema.getName());
     assertEquals(Option.of("com.example"), recordSchema.getNamespace());
-    assertEquals(Option.of("com.example.TestRecord"), 
recordSchema.getFullName());
+    assertEquals("com.example.TestRecord", recordSchema.getFullName());
     assertEquals(Option.of("Test record schema"), recordSchema.getDoc());
   }
 
@@ -657,7 +643,7 @@ public class TestHoodieSchema {
 
     assertNotNull(recordSchema);
     assertEquals(HoodieSchemaType.RECORD, recordSchema.getType());
-    assertEquals(Option.of("EmptyRecord"), recordSchema.getName());
+    assertEquals("EmptyRecord", recordSchema.getName());
     assertEquals(0, recordSchema.getFields().size());
   }
 
@@ -668,7 +654,7 @@ public class TestHoodieSchema {
 
     assertNotNull(enumSchema);
     assertEquals(HoodieSchemaType.ENUM, enumSchema.getType());
-    assertEquals(Option.of("Color"), enumSchema.getName());
+    assertEquals("Color", enumSchema.getName());
     assertEquals(symbols, enumSchema.getEnumSymbols());
   }
 
@@ -703,7 +689,7 @@ public class TestHoodieSchema {
 
     assertNotNull(fixedSchema);
     assertEquals(HoodieSchemaType.FIXED, fixedSchema.getType());
-    assertEquals(Option.of("MD5Hash"), fixedSchema.getName());
+    assertEquals("MD5Hash", fixedSchema.getName());
     assertEquals(16, fixedSchema.getFixedSize());
   }
 
@@ -784,7 +770,7 @@ public class TestHoodieSchema {
   @Test
   public void testCreateNullableSchema() {
     HoodieSchema stringSchema = HoodieSchema.create(HoodieSchemaType.STRING);
-    HoodieSchema nullableSchema = 
HoodieSchema.createNullableSchema(stringSchema);
+    HoodieSchema nullableSchema = HoodieSchema.createNullable(stringSchema);
 
     assertNotNull(nullableSchema);
     assertEquals(HoodieSchemaType.UNION, nullableSchema.getType());
@@ -802,7 +788,7 @@ public class TestHoodieSchema {
   @Test
   public void testCreateNullableSchemaWithInvalidParameters() {
     assertThrows(IllegalArgumentException.class, () -> {
-      HoodieSchema.createNullableSchema(null);
+      HoodieSchema.createNullable((HoodieSchema) null);
     }, "Should throw exception for null type");
   }
 
@@ -812,7 +798,7 @@ public class TestHoodieSchema {
 
     assertTrue(decimalSchema instanceof HoodieSchema.Decimal);
     assertEquals(HoodieSchemaType.DECIMAL, decimalSchema.getType());
-    assertEquals(Option.of("decimal(10,2)"), decimalSchema.getName());
+    assertEquals("decimal(10,2)", decimalSchema.getName());
     assertEquals(10, ((HoodieSchema.Decimal) decimalSchema).getPrecision());
     assertEquals(2, ((HoodieSchema.Decimal) decimalSchema).getScale());
     LogicalTypes.Decimal avroLogicalType = (LogicalTypes.Decimal) 
decimalSchema.toAvroSchema().getLogicalType();
@@ -836,7 +822,7 @@ public class TestHoodieSchema {
     HoodieSchema timestampSchema = HoodieSchema.createTimestampMillis();
 
     assertEquals(HoodieSchemaType.TIMESTAMP, timestampSchema.getType());
-    assertEquals("timestamp-millis", timestampSchema.getName().get());
+    assertEquals("timestamp-millis", timestampSchema.getName());
     assertTrue(((HoodieSchema.Timestamp) timestampSchema).isUtcAdjusted());
     assertEquals(HoodieSchema.TimePrecision.MILLIS, ((HoodieSchema.Timestamp) 
timestampSchema).getPrecision());
     assertInstanceOf(LogicalTypes.TimestampMillis.class, 
timestampSchema.toAvroSchema().getLogicalType());
@@ -847,7 +833,7 @@ public class TestHoodieSchema {
     HoodieSchema timestampSchema = HoodieSchema.createTimestampMicros();
 
     assertEquals(HoodieSchemaType.TIMESTAMP, timestampSchema.getType());
-    assertEquals("timestamp-micros", timestampSchema.getName().get());
+    assertEquals("timestamp-micros", timestampSchema.getName());
     assertTrue(((HoodieSchema.Timestamp) timestampSchema).isUtcAdjusted());
     assertEquals(HoodieSchema.TimePrecision.MICROS, ((HoodieSchema.Timestamp) 
timestampSchema).getPrecision());
     assertInstanceOf(LogicalTypes.TimestampMicros.class, 
timestampSchema.toAvroSchema().getLogicalType());
@@ -858,7 +844,7 @@ public class TestHoodieSchema {
     HoodieSchema timestampSchema = HoodieSchema.createLocalTimestampMillis();
 
     assertEquals(HoodieSchemaType.TIMESTAMP, timestampSchema.getType());
-    assertEquals("local-timestamp-millis", timestampSchema.getName().get());
+    assertEquals("local-timestamp-millis", timestampSchema.getName());
     assertFalse(((HoodieSchema.Timestamp) timestampSchema).isUtcAdjusted());
     assertEquals(HoodieSchema.TimePrecision.MILLIS, ((HoodieSchema.Timestamp) 
timestampSchema).getPrecision());
     assertInstanceOf(LogicalTypes.LocalTimestampMillis.class, 
timestampSchema.toAvroSchema().getLogicalType());
@@ -869,7 +855,7 @@ public class TestHoodieSchema {
     HoodieSchema timestampSchema = HoodieSchema.createLocalTimestampMicros();
 
     assertEquals(HoodieSchemaType.TIMESTAMP, timestampSchema.getType());
-    assertEquals("local-timestamp-micros", timestampSchema.getName().get());
+    assertEquals("local-timestamp-micros", timestampSchema.getName());
     assertFalse(((HoodieSchema.Timestamp) timestampSchema).isUtcAdjusted());
     assertEquals(HoodieSchema.TimePrecision.MICROS, ((HoodieSchema.Timestamp) 
timestampSchema).getPrecision());
     assertInstanceOf(LogicalTypes.LocalTimestampMicros.class, 
timestampSchema.toAvroSchema().getLogicalType());
@@ -880,7 +866,7 @@ public class TestHoodieSchema {
     HoodieSchema timeSchema = HoodieSchema.createTimeMillis();
 
     assertEquals(HoodieSchemaType.TIME, timeSchema.getType());
-    assertEquals("time-millis", timeSchema.getName().get());
+    assertEquals("time-millis", timeSchema.getName());
     assertInstanceOf(LogicalTypes.TimeMillis.class, 
timeSchema.toAvroSchema().getLogicalType());
   }
 
@@ -889,7 +875,7 @@ public class TestHoodieSchema {
     HoodieSchema timeSchema = HoodieSchema.createTimeMicros();
 
     assertEquals(HoodieSchemaType.TIME, timeSchema.getType());
-    assertEquals("time-micros", timeSchema.getName().get());
+    assertEquals("time-micros", timeSchema.getName());
     assertInstanceOf(LogicalTypes.TimeMicros.class, 
timeSchema.toAvroSchema().getLogicalType());
   }
 
@@ -898,7 +884,7 @@ public class TestHoodieSchema {
     HoodieSchema dateSchema = HoodieSchema.createDate();
 
     assertEquals(HoodieSchemaType.DATE, dateSchema.getType());
-    assertEquals("date", dateSchema.getName().get());
+    assertEquals("date", dateSchema.getName());
     assertInstanceOf(LogicalTypes.Date.class, 
dateSchema.toAvroSchema().getLogicalType());
   }
 
@@ -907,7 +893,40 @@ public class TestHoodieSchema {
     HoodieSchema uuidSchema = HoodieSchema.createUUID();
 
     assertEquals(HoodieSchemaType.UUID, uuidSchema.getType());
-    assertEquals("uuid", uuidSchema.getName().get());
+    assertEquals("uuid", uuidSchema.getName());
     assertEquals("uuid", uuidSchema.toAvroSchema().getLogicalType().getName());
   }
+
+  @Test
+  void testSubclassIsPreservedInNestedSchemas() {
+    // Create a record schema with a decimal field
+    HoodieSchema decimalFieldSchema = HoodieSchema.createDecimal(10, 2);
+    HoodieSchemaField decimalField = HoodieSchemaField.of("amount", 
decimalFieldSchema);
+    List<HoodieSchemaField> fields = Collections.singletonList(decimalField);
+
+    HoodieSchema recordSchema = HoodieSchema.createRecord("Transaction", null, 
null, fields);
+
+    // Retrieve the field schema and verify it's still a Decimal subclass
+    Option<HoodieSchemaField> retrievedFieldOpt = 
recordSchema.getField("amount");
+    assertTrue(retrievedFieldOpt.isPresent());
+    HoodieSchema retrievedFieldSchema = retrievedFieldOpt.get().schema();
+
+    assertTrue(retrievedFieldSchema instanceof HoodieSchema.Decimal);
+    assertEquals(10, ((HoodieSchema.Decimal) 
retrievedFieldSchema).getPrecision());
+    assertEquals(2, ((HoodieSchema.Decimal) retrievedFieldSchema).getScale());
+
+    // Create an array schema with timestamp elements
+    HoodieSchema timestampElementSchema = HoodieSchema.createTimestampMillis();
+    HoodieSchema arraySchema = 
HoodieSchema.createArray(timestampElementSchema);
+    HoodieSchema retrievedElementSchema = arraySchema.getElementType();
+    assertTrue(retrievedElementSchema instanceof HoodieSchema.Timestamp);
+    assertEquals(HoodieSchema.TimePrecision.MILLIS, ((HoodieSchema.Timestamp) 
retrievedElementSchema).getPrecision());
+
+    // Create a map schema with time values
+    HoodieSchema timeSchema = HoodieSchema.createTimeMillis();
+    HoodieSchema mapSchema = HoodieSchema.createMap(timeSchema);
+    HoodieSchema retrievedValueSchema = mapSchema.getValueType();
+    assertTrue(retrievedValueSchema instanceof HoodieSchema.Time);
+    assertEquals(HoodieSchema.TimePrecision.MILLIS, ((HoodieSchema.Time) 
retrievedValueSchema).getPrecision());
+  }
 }
diff --git 
a/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchemaCompatibility.java
 
b/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchemaCompatibility.java
index 192747305d8b..c7e36d52c12b 100644
--- 
a/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchemaCompatibility.java
+++ 
b/hudi-common/src/test/java/org/apache/hudi/common/schema/TestHoodieSchemaCompatibility.java
@@ -449,7 +449,7 @@ public class TestHoodieSchemaCompatibility {
 
   @Test
   void testAreSchemasProjectionEquivalentNullableSchemaComparison() {
-    HoodieSchema s1 = 
HoodieSchema.createNullableSchema(HoodieSchema.create(HoodieSchemaType.INT));
+    HoodieSchema s1 = HoodieSchema.createNullable(HoodieSchemaType.INT);
     HoodieSchema s2 = HoodieSchema.create(HoodieSchemaType.INT);
     s2.addProp("prop1", "value1"); // prevent Objects.equals from returning 
true
     assertTrue(HoodieSchemaCompatibility.areSchemasProjectionEquivalent(s1, 
s2));

Reply via email to