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

fresh-borzoni pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new 3a29bf50b [client] Enum conversion functionality for typed 
writer/reader (#3447)
3a29bf50b is described below

commit 3a29bf50b393b8d605f4d27edc62b18459962e1e
Author: Vladyslav Banar <[email protected]>
AuthorDate: Wed Jun 24 16:37:08 2026 +0200

    [client] Enum conversion functionality for typed writer/reader (#3447)
    
    * [client] Add support of column aliasing for PojoConverter
    
    * [client] enum conversion functionality for typed writer/reader
    
    * [client] Add support of column aliasing for PojoConverter (#3444)
    
    * [client] Add support of column aliasing for PojoConverter
    
    * [client] test
    
    * [client] updated doc
    
    * [client] fix checks
    
    * [client] address comments
    
    ---------
    
    Co-authored-by: valdbanar5 <[email protected]>
    
    (cherry picked from commit 9dd1a56c439e1bfc0527db7d452244478c82d5fc)
    
    * [client] fix test
    
    * [client] fix merge
    
    * [client] address comments
    
    * [client] address comments
    
    * [client] address comments
    
    * [client] fix formatting
    
    * [client] fix for java 8 compatibility
    
    * [client] fix for java 8 compatibility
    
    * [client] re-run the build
    
    * [client] use enum.name() during converting to BinaryString
    
    * [client] adjusted tests to cover additional enum conversion cases
    
    * [client] address comments
    
    ---------
    
    Co-authored-by: valdbanar5 <[email protected]>
---
 .../fluss/client/converter/ConverterCommons.java   |  15 +-
 .../client/converter/FlussArrayToPojoArray.java    |  11 +-
 .../converter/FlussTypeToPojoTypeConverter.java    |  38 +-
 .../apache/fluss/client/converter/PojoType.java    |  13 +-
 .../fluss/client/converter/RowToPojoConverter.java |  16 +-
 .../client/converter/ConverterCommonsTest.java     | 638 +++++++++++++++++++++
 .../client/converter/ConvertersTestFixtures.java   |  56 +-
 .../FlussTypeToPojoTypeConverterTest.java          | 287 +++++++++
 .../converter/PojoArrayToFlussArrayTest.java       |  18 +
 .../fluss/client/converter/PojoTypeTest.java       |   9 +-
 .../client/converter/RowToPojoConverterTest.java   |  21 +-
 11 files changed, 1095 insertions(+), 27 deletions(-)

diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/ConverterCommons.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/ConverterCommons.java
index 82415403f..3e718bf00 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/ConverterCommons.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/ConverterCommons.java
@@ -148,6 +148,15 @@ final class ConverterCommons {
             }
             return;
         }
+        if (actual.isEnum()) {
+            if (typeRoot != DataTypeRoot.STRING) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Enum field '%s' must be a STRING column, got 
%s",
+                                prop.name, typeRoot));
+            }
+            return;
+        }
 
         Set<Class<?>> supported = SUPPORTED_TYPES.get(fieldType.getTypeRoot());
         if (supported == null) {
@@ -171,13 +180,17 @@ final class ConverterCommons {
     }
 
     static BinaryString toBinaryStringForText(Object v, String fieldName, 
DataTypeRoot root) {
-        final String s = String.valueOf(v);
+        final String s = objectToString(v);
         if (root == DataTypeRoot.CHAR && s.length() != 1) {
             throw new 
IllegalArgumentException(charLengthExceptionMessage(fieldName, s.length()));
         }
         return BinaryString.fromString(s);
     }
 
+    private static String objectToString(Object v) {
+        return v instanceof Enum ? ((Enum<?>) v).name() : String.valueOf(v);
+    }
+
     static Set<Class<?>> setOf(Class<?>... javaTypes) {
         return new HashSet<>(Arrays.asList(javaTypes));
     }
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussArrayToPojoArray.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussArrayToPojoArray.java
index 59bf26420..fb4e8f8ed 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussArrayToPojoArray.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussArrayToPojoArray.java
@@ -32,6 +32,7 @@ import java.lang.reflect.Array;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /** Adapter class for converting Fluss InternalArray to Pojo Array. */
 public class FlussArrayToPojoArray {
@@ -115,9 +116,17 @@ public class FlussArrayToPojoArray {
             case STRING:
                 // Default to String when the POJO element type is unspecified 
(Object[])
                 final Class<?> textTarget = (pojoType == Object.class) ? 
String.class : pojoType;
+                final Map<String, Object> enumConstantsMap =
+                        textTarget.isEnum()
+                                ? 
FlussTypeToPojoTypeConverter.buildEnumConstantsMap(textTarget)
+                                : null;
                 return (arr, i) ->
                         FlussTypeToPojoTypeConverter.convertTextValue(
-                                elementType, fieldName, textTarget, 
arr.getString(i));
+                                elementType,
+                                fieldName,
+                                textTarget,
+                                arr.getString(i),
+                                enumConstantsMap);
             case BINARY:
             case BYTES:
                 return InternalArray::getBytes;
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverter.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverter.java
index 2b693f4e3..6fb09985f 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverter.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverter.java
@@ -31,9 +31,28 @@ import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 /** Shared utilities for Fluss type and Pojo type. */
 public class FlussTypeToPojoTypeConverter {
+
+    /**
+     * Builds a map of enum name to enum constant for the given enum class.
+     *
+     * @param enumClass the enum class
+     * @return an immutable map from enum constant name to enum constant
+     */
+    static Map<String, Object> buildEnumConstantsMap(Class<?> enumClass) {
+        Map<String, Object> map = new HashMap<>();
+        for (Object constant : enumClass.getEnumConstants()) {
+            map.put(((Enum<?>) constant).name(), constant);
+        }
+        return Collections.unmodifiableMap(map);
+    }
+
     /**
      * Converts a text value (CHAR/STRING) read from an InternalRow into the 
target Java type
      * declared by the POJO property.
@@ -45,12 +64,18 @@ public class FlussTypeToPojoTypeConverter {
      * @param fieldName The field name
      * @param pojoType The pojo type
      * @param s The BinaryString read from the row
+     * @param enumConstantsMap (optional) Pre-built enum constants map for 
enum types; ignored for
+     *     non-enum types
      * @return Converted Java value (String or Character)
      * @throws IllegalArgumentException if the target type is unsupported or 
constraints are
      *     violated
      */
     static Object convertTextValue(
-            DataType fieldType, String fieldName, Class<?> pojoType, 
BinaryString s) {
+            DataType fieldType,
+            String fieldName,
+            Class<?> pojoType,
+            BinaryString s,
+            Map<String, Object> enumConstantsMap) {
         if (s == null) {
             return null;
         }
@@ -74,6 +99,17 @@ public class FlussTypeToPojoTypeConverter {
                         ConverterCommons.charLengthExceptionMessage(fieldName, 
v.length()));
             }
             return v.charAt(0);
+        } else if (pojoType.isEnum()) {
+            Map<String, Object> enumMap =
+                    enumConstantsMap != null ? enumConstantsMap : 
buildEnumConstantsMap(pojoType);
+            Object enumConstant = enumMap.get(v);
+            if (enumConstant == null) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Could not parse value for enum %s. Expected 
one of: %s",
+                                pojoType, 
Arrays.toString(pojoType.getEnumConstants())));
+            }
+            return enumConstant;
         }
         throw new IllegalArgumentException(
                 String.format(
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/PojoType.java 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/PojoType.java
index bf57a3299..a81f523d8 100644
--- a/fluss-client/src/main/java/org/apache/fluss/client/converter/PojoType.java
+++ b/fluss-client/src/main/java/org/apache/fluss/client/converter/PojoType.java
@@ -126,8 +126,7 @@ final class PojoType<T> {
                             field.getGenericType(),
                             publicField ? field : null,
                             getter,
-                            setter,
-                            mappedColumnName));
+                            setter));
         }
 
         return new PojoType<>(pojoClass, ctor, props);
@@ -298,12 +297,6 @@ final class PojoType<T> {
         /** The generic type of the field (e.g. {@code Map<String, 
AddressPojo>}). */
         final Type genericType;
 
-        /**
-         * The name of the column in the Fluss table. This may differ from 
'name' if a @ColumnName
-         * annotation is present. Used for looking up the property by table 
column name.
-         */
-        final String mappedName;
-
         @Nullable final Field publicField;
         @Nullable final Method getter;
         @Nullable final Method setter;
@@ -314,12 +307,10 @@ final class PojoType<T> {
                 Type genericType,
                 @Nullable Field publicField,
                 @Nullable Method getter,
-                @Nullable Method setter,
-                String mappedName) {
+                @Nullable Method setter) {
             this.name = Objects.requireNonNull(name, "name");
             this.type = Objects.requireNonNull(type, "type");
             this.genericType = Objects.requireNonNull(genericType, 
"genericType");
-            this.mappedName = Objects.requireNonNull(mappedName, "mappedName");
             this.publicField = publicField;
             this.getter = getter;
             this.setter = setter;
diff --git 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/RowToPojoConverter.java
 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/RowToPojoConverter.java
index a54b8d97d..3897f5057 100644
--- 
a/fluss-client/src/main/java/org/apache/fluss/client/converter/RowToPojoConverter.java
+++ 
b/fluss-client/src/main/java/org/apache/fluss/client/converter/RowToPojoConverter.java
@@ -141,9 +141,19 @@ public final class RowToPojoConverter<T> {
                 return InternalRow::getDouble;
             case CHAR:
             case STRING:
-                return (row, pos) ->
-                        FlussTypeToPojoTypeConverter.convertTextValue(
-                                fieldType, prop.name, prop.type, 
row.getString(pos));
+                {
+                    final Map<String, Object> enumConstantsMap =
+                            prop.type.isEnum()
+                                    ? 
FlussTypeToPojoTypeConverter.buildEnumConstantsMap(prop.type)
+                                    : null;
+                    return (row, pos) ->
+                            FlussTypeToPojoTypeConverter.convertTextValue(
+                                    fieldType,
+                                    prop.name,
+                                    prop.type,
+                                    row.getString(pos),
+                                    enumConstantsMap);
+                }
             case BINARY:
             case BYTES:
                 return InternalRow::getBytes;
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/ConverterCommonsTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/ConverterCommonsTest.java
new file mode 100644
index 000000000..a3e0c5a58
--- /dev/null
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/ConverterCommonsTest.java
@@ -0,0 +1,638 @@
+/*
+ * 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.fluss.client.converter;
+
+import org.apache.fluss.row.BinaryString;
+import org.apache.fluss.types.DataTypes;
+import org.apache.fluss.types.RowType;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link ConverterCommons}. */
+public class ConverterCommonsTest {
+
+    // ==================== validatePojoMatchesTable Tests ====================
+
+    @Test
+    public void validatePojoMatchesTableWithExactMatch() {
+        RowType table =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        ConverterCommons.validatePojoMatchesTable(pojoType, table);
+    }
+
+    @Test
+    public void validatePojoMatchesTableWithTypeIncompatibility() {
+        RowType table =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.INT())
+                        .build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        assertThatThrownBy(() -> 
ConverterCommons.validatePojoMatchesTable(pojoType, table))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("incompatible with Fluss type");
+    }
+
+    @Test
+    public void validatePojoMatchesTableWithMissingField() {
+        RowType table =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .field("age", DataTypes.INT())
+                        .build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        assertThatThrownBy(() -> 
ConverterCommons.validatePojoMatchesTable(pojoType, table))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("must exactly match");
+    }
+
+    // ==================== validatePojoMatchesProjection Tests 
====================
+
+    @Test
+    public void validatePojoMatchesProjectionWithSubset() {
+        RowType projection =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        ConverterCommons.validatePojoMatchesProjection(pojoType, projection);
+    }
+
+    @Test
+    public void validatePojoMatchesProjectionWithSingleField() {
+        RowType projection = RowType.builder().field("id", 
DataTypes.INT()).build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        ConverterCommons.validatePojoMatchesProjection(pojoType, projection);
+    }
+
+    @Test
+    public void validatePojoMatchesProjectionWithMissingField() {
+        RowType projection =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("missingField", DataTypes.STRING())
+                        .build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        assertThatThrownBy(
+                        () -> 
ConverterCommons.validatePojoMatchesProjection(pojoType, projection))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void validatePojoMatchesProjectionWithTypeIncompatibility() {
+        RowType projection =
+                RowType.builder()
+                        .field("id", DataTypes.STRING())
+                        .field("name", DataTypes.STRING())
+                        .build();
+
+        PojoType<AllFieldsPojo> pojoType = PojoType.of(AllFieldsPojo.class);
+        assertThatThrownBy(
+                        () -> 
ConverterCommons.validatePojoMatchesProjection(pojoType, projection))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    // ==================== validateProjectionSubset Tests ====================
+
+    @Test
+    public void validateProjectionSubsetAllFieldsInTable() {
+        RowType tableSchema =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .field("age", DataTypes.INT())
+                        .build();
+
+        RowType projection =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .build();
+
+        ConverterCommons.validateProjectionSubset(projection, tableSchema);
+    }
+
+    @Test
+    public void validateProjectionSubsetSingleFieldInTable() {
+        RowType tableSchema =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .build();
+
+        RowType projection = RowType.builder().field("id", 
DataTypes.INT()).build();
+
+        ConverterCommons.validateProjectionSubset(projection, tableSchema);
+    }
+
+    @Test
+    public void validateProjectionSubsetWithFieldNotInTable() {
+        RowType tableSchema =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("name", DataTypes.STRING())
+                        .build();
+
+        RowType projection =
+                RowType.builder()
+                        .field("id", DataTypes.INT())
+                        .field("unknown", DataTypes.STRING())
+                        .build();
+
+        assertThatThrownBy(() -> 
ConverterCommons.validateProjectionSubset(projection, tableSchema))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    // ==================== validateCompatibility Tests ====================
+
+    @Test
+    public void compatibilityBooleanWithBoolean() {
+        PojoType<BooleanPojo> pojoType = PojoType.of(BooleanPojo.class);
+        PojoType.Property prop = pojoType.getProperty("flag");
+        ConverterCommons.validateCompatibility(DataTypes.BOOLEAN(), prop);
+    }
+
+    @Test
+    public void compatibilityTinyintWithByte() {
+        PojoType<BytePojo> pojoType = PojoType.of(BytePojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.TINYINT(), prop);
+    }
+
+    @Test
+    public void compatibilitySmallintWithShort() {
+        PojoType<ShortPojo> pojoType = PojoType.of(ShortPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.SMALLINT(), prop);
+    }
+
+    @Test
+    public void compatibilityIntegerWithInteger() {
+        PojoType<IntPojo> pojoType = PojoType.of(IntPojo.class);
+        PojoType.Property prop = pojoType.getProperty("id");
+        ConverterCommons.validateCompatibility(DataTypes.INT(), prop);
+    }
+
+    @Test
+    public void compatibilityBigintWithLong() {
+        PojoType<LongPojo> pojoType = PojoType.of(LongPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.BIGINT(), prop);
+    }
+
+    @Test
+    public void compatibilityFloatWithFloat() {
+        PojoType<FloatPojo> pojoType = PojoType.of(FloatPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.FLOAT(), prop);
+    }
+
+    @Test
+    public void compatibilityDoubleWithDouble() {
+        PojoType<DoublePojo> pojoType = PojoType.of(DoublePojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.DOUBLE(), prop);
+    }
+
+    @Test
+    public void compatibilityCharWithString() {
+        PojoType<StringPojo> pojoType = PojoType.of(StringPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.CHAR(10), prop);
+    }
+
+    @Test
+    public void compatibilityCharWithCharacter() {
+        PojoType<CharacterPojo> pojoType = PojoType.of(CharacterPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.CHAR(1), prop);
+    }
+
+    @Test
+    public void compatibilityStringWithString() {
+        PojoType<StringPojo> pojoType = PojoType.of(StringPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.STRING(), prop);
+    }
+
+    @Test
+    public void compatibilityStringWithCharacter() {
+        PojoType<CharacterPojo> pojoType = PojoType.of(CharacterPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        ConverterCommons.validateCompatibility(DataTypes.STRING(), prop);
+    }
+
+    @Test
+    public void compatibilityBinaryWithByteArray() {
+        PojoType<ByteArrayPojo> pojoType = PojoType.of(ByteArrayPojo.class);
+        PojoType.Property prop = pojoType.getProperty("bytes");
+        ConverterCommons.validateCompatibility(DataTypes.BINARY(100), prop);
+    }
+
+    @Test
+    public void compatibilityBytesWithByteArray() {
+        PojoType<ByteArrayPojo> pojoType = PojoType.of(ByteArrayPojo.class);
+        PojoType.Property prop = pojoType.getProperty("bytes");
+        ConverterCommons.validateCompatibility(DataTypes.BYTES(), prop);
+    }
+
+    @Test
+    public void compatibilityDecimalWithBigDecimal() {
+        PojoType<BigDecimalPojo> pojoType = PojoType.of(BigDecimalPojo.class);
+        PojoType.Property prop = pojoType.getProperty("amount");
+        ConverterCommons.validateCompatibility(DataTypes.DECIMAL(10, 2), prop);
+    }
+
+    @Test
+    public void compatibilityDateWithLocalDate() {
+        PojoType<LocalDatePojo> pojoType = PojoType.of(LocalDatePojo.class);
+        PojoType.Property prop = pojoType.getProperty("date");
+        ConverterCommons.validateCompatibility(DataTypes.DATE(), prop);
+    }
+
+    @Test
+    public void compatibilityTimeWithLocalTime() {
+        PojoType<LocalTimePojo> pojoType = PojoType.of(LocalTimePojo.class);
+        PojoType.Property prop = pojoType.getProperty("time");
+        ConverterCommons.validateCompatibility(DataTypes.TIME(), prop);
+    }
+
+    @Test
+    public void compatibilityTimestampNtzWithLocalDateTime() {
+        PojoType<LocalDateTimePojo> pojoType = 
PojoType.of(LocalDateTimePojo.class);
+        PojoType.Property prop = pojoType.getProperty("timestamp");
+        ConverterCommons.validateCompatibility(DataTypes.TIMESTAMP(), prop);
+    }
+
+    @Test
+    public void compatibilityTimestampLtzWithInstant() {
+        PojoType<InstantPojo> pojoType = PojoType.of(InstantPojo.class);
+        PojoType.Property prop = pojoType.getProperty("timestamp");
+        ConverterCommons.validateCompatibility(DataTypes.TIMESTAMP_LTZ(), 
prop);
+    }
+
+    @Test
+    public void compatibilityTimestampLtzWithOffsetDateTime() {
+        PojoType<OffsetDateTimePojo> pojoType = 
PojoType.of(OffsetDateTimePojo.class);
+        PojoType.Property prop = pojoType.getProperty("timestamp");
+        ConverterCommons.validateCompatibility(DataTypes.TIMESTAMP_LTZ(), 
prop);
+    }
+
+    @Test
+    public void compatibilityArrayWithArrayType() {
+        PojoType<IntArrayPojo> pojoType = PojoType.of(IntArrayPojo.class);
+        PojoType.Property prop = pojoType.getProperty("items");
+        
ConverterCommons.validateCompatibility(DataTypes.ARRAY(DataTypes.INT()), prop);
+    }
+
+    @Test
+    public void compatibilityArrayWithListType() {
+        PojoType<ListPojo> pojoType = PojoType.of(ListPojo.class);
+        PojoType.Property prop = pojoType.getProperty("items");
+        
ConverterCommons.validateCompatibility(DataTypes.ARRAY(DataTypes.INT()), prop);
+    }
+
+    @Test
+    public void compatibilityMapType() {
+        PojoType<MapPojo> pojoType = PojoType.of(MapPojo.class);
+        PojoType.Property prop = pojoType.getProperty("mapping");
+        ConverterCommons.validateCompatibility(
+                DataTypes.MAP(DataTypes.STRING(), DataTypes.INT()), prop);
+    }
+
+    @Test
+    public void compatibilityRowWithNestedPojo() {
+        PojoType<NestedPojo> pojoType = PojoType.of(NestedPojo.class);
+        PojoType.Property prop = pojoType.getProperty("nested");
+        ConverterCommons.validateCompatibility(
+                DataTypes.ROW(
+                        DataTypes.FIELD("id", DataTypes.INT()),
+                        DataTypes.FIELD("name", DataTypes.STRING())),
+                prop);
+    }
+
+    @Test
+    public void compatibilityEnumWithString() {
+        PojoType<EnumPojo> pojoType = PojoType.of(EnumPojo.class);
+        PojoType.Property prop = pojoType.getProperty("status");
+        ConverterCommons.validateCompatibility(DataTypes.STRING(), prop);
+    }
+
+    @Test
+    public void incompatibilityBooleanWithString() {
+        PojoType<StringPojo> pojoType = PojoType.of(StringPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        assertThatThrownBy(() -> 
ConverterCommons.validateCompatibility(DataTypes.BOOLEAN(), prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("incompatible");
+    }
+
+    @Test
+    public void incompatibilityIntegerWithString() {
+        PojoType<StringPojo> pojoType = PojoType.of(StringPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        assertThatThrownBy(() -> 
ConverterCommons.validateCompatibility(DataTypes.INT(), prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("incompatible");
+    }
+
+    @Test
+    public void incompatibilityArrayWithString() {
+        PojoType<StringPojo> pojoType = PojoType.of(StringPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        assertThatThrownBy(
+                        () ->
+                                ConverterCommons.validateCompatibility(
+                                        DataTypes.ARRAY(DataTypes.INT()), 
prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("must be an array or Collection");
+    }
+
+    @Test
+    public void incompatibilityMapWithString() {
+        PojoType<StringPojo> pojoType = PojoType.of(StringPojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        assertThatThrownBy(
+                        () ->
+                                ConverterCommons.validateCompatibility(
+                                        DataTypes.MAP(DataTypes.STRING(), 
DataTypes.INT()), prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("must be a Map");
+    }
+
+    @Test
+    public void incompatibilityRowWithArray() {
+        PojoType<IntArrayPojo> pojoType = PojoType.of(IntArrayPojo.class);
+        PojoType.Property prop = pojoType.getProperty("items");
+        assertThatThrownBy(
+                        () ->
+                                ConverterCommons.validateCompatibility(
+                                        DataTypes.ROW(DataTypes.FIELD("id", 
DataTypes.INT())),
+                                        prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("must be a POJO class");
+    }
+
+    @Test
+    public void incompatibilityRowWithList() {
+        PojoType<ListPojo> pojoType = PojoType.of(ListPojo.class);
+        PojoType.Property prop = pojoType.getProperty("items");
+        assertThatThrownBy(
+                        () ->
+                                ConverterCommons.validateCompatibility(
+                                        DataTypes.ROW(DataTypes.FIELD("id", 
DataTypes.INT())),
+                                        prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("must be a POJO class");
+    }
+
+    @Test
+    public void incompatibilityEnumWithInt() {
+        PojoType<EnumPojo> pojoType = PojoType.of(EnumPojo.class);
+        PojoType.Property prop = pojoType.getProperty("status");
+        assertThatThrownBy(() -> 
ConverterCommons.validateCompatibility(DataTypes.INT(), prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("must be a STRING column");
+    }
+
+    @Test
+    public void incompatibilityBigintWithByte() {
+        PojoType<BytePojo> pojoType = PojoType.of(BytePojo.class);
+        PojoType.Property prop = pojoType.getProperty("value");
+        assertThatThrownBy(() -> 
ConverterCommons.validateCompatibility(DataTypes.BIGINT(), prop))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("incompatible");
+    }
+
+    @Test
+    public void convertStringToBinaryStringForCharTypeWithWrongLengthThrows() {
+        assertThatThrownBy(
+                        () ->
+                                ConverterCommons.toBinaryStringForText(
+                                        "Hello", "testField", 
DataTypes.CHAR(1).getTypeRoot()))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void convertBooleanToBinaryString() {
+        BinaryString result =
+                ConverterCommons.toBinaryStringForText(
+                        true, "field", DataTypes.STRING().getTypeRoot());
+        assertThat(result.toString()).isEqualTo("true");
+    }
+
+    @Test
+    public void convertNullValueToBinaryString() {
+        BinaryString result =
+                ConverterCommons.toBinaryStringForText(
+                        null, "field", DataTypes.STRING().getTypeRoot());
+        assertThat(result.toString()).isEqualTo("null");
+    }
+
+    // ==================== Test POJOs ====================
+
+    /** Test POJO with multiple fields. */
+    public static class AllFieldsPojo {
+        public Integer id;
+        public String name;
+
+        public AllFieldsPojo() {}
+    }
+
+    /** Test POJO with Boolean field. */
+    public static class BooleanPojo {
+        public Boolean flag;
+
+        public BooleanPojo() {}
+    }
+
+    /** Test POJO with Byte field. */
+    public static class BytePojo {
+        public Byte value;
+
+        public BytePojo() {}
+    }
+
+    /** Test POJO with Short field. */
+    public static class ShortPojo {
+        public Short value;
+
+        public ShortPojo() {}
+    }
+
+    /** Test POJO with Integer field. */
+    public static class IntPojo {
+        public Integer id;
+
+        public IntPojo() {}
+    }
+
+    /** Test POJO with Long field. */
+    public static class LongPojo {
+        public Long value;
+
+        public LongPojo() {}
+    }
+
+    /** Test POJO with Float field. */
+    public static class FloatPojo {
+        public Float value;
+
+        public FloatPojo() {}
+    }
+
+    /** Test POJO with Double field. */
+    public static class DoublePojo {
+        public Double value;
+
+        public DoublePojo() {}
+    }
+
+    /** Test POJO with String field. */
+    public static class StringPojo {
+        public String value;
+
+        public StringPojo() {}
+    }
+
+    /** Test POJO with Character field. */
+    public static class CharacterPojo {
+        public Character value;
+
+        public CharacterPojo() {}
+    }
+
+    /** Test POJO with byte[] field. */
+    public static class ByteArrayPojo {
+        public byte[] bytes;
+
+        public ByteArrayPojo() {}
+    }
+
+    /** Test POJO with BigDecimal field. */
+    public static class BigDecimalPojo {
+        public BigDecimal amount;
+
+        public BigDecimalPojo() {}
+    }
+
+    /** Test POJO with LocalDate field. */
+    public static class LocalDatePojo {
+        public LocalDate date;
+
+        public LocalDatePojo() {}
+    }
+
+    /** Test POJO with LocalTime field. */
+    public static class LocalTimePojo {
+        public LocalTime time;
+
+        public LocalTimePojo() {}
+    }
+
+    /** Test POJO with LocalDateTime field. */
+    public static class LocalDateTimePojo {
+        public LocalDateTime timestamp;
+
+        public LocalDateTimePojo() {}
+    }
+
+    /** Test POJO with Instant field. */
+    public static class InstantPojo {
+        public Instant timestamp;
+
+        public InstantPojo() {}
+    }
+
+    /** Test POJO with OffsetDateTime field. */
+    public static class OffsetDateTimePojo {
+        public OffsetDateTime timestamp;
+
+        public OffsetDateTimePojo() {}
+    }
+
+    /** Test POJO with Integer[] field. */
+    public static class IntArrayPojo {
+        public Integer[] items;
+
+        public IntArrayPojo() {}
+    }
+
+    /** Test POJO with List field. */
+    public static class ListPojo {
+        public List<Integer> items;
+
+        public ListPojo() {}
+    }
+
+    /** Test POJO with Map field. */
+    public static class MapPojo {
+        public Map<String, Integer> mapping;
+
+        public MapPojo() {}
+    }
+
+    /** Test POJO with nested POJO field. */
+    public static class NestedPojo {
+        public AllFieldsPojo nested;
+
+        public NestedPojo() {}
+    }
+
+    /** Test POJO with Enum field. */
+    public static class EnumPojo {
+        public StatusEnum status;
+
+        public EnumPojo() {}
+    }
+
+    /** Test enum for compatibility testing. */
+    public enum StatusEnum {
+        ACTIVE,
+        inactive;
+
+        @Override
+        public String toString() {
+            return "StatusEnum{}";
+        }
+    }
+}
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/ConvertersTestFixtures.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/ConvertersTestFixtures.java
index 0a90c522d..8852e0fea 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/ConvertersTestFixtures.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/ConvertersTestFixtures.java
@@ -57,7 +57,11 @@ public final class ConvertersTestFixtures {
                 .field("offsetDateTimeField", DataTypes.TIMESTAMP_LTZ())
                 .field("arrayField", DataTypes.ARRAY(DataTypes.INT()))
                 .field("mapField", DataTypes.MAP(DataTypes.STRING(), 
DataTypes.INT()))
+                .field("enumField", DataTypes.STRING())
                 .field("string_with_column_name", DataTypes.STRING())
+                .field("enumList", DataTypes.ARRAY(DataTypes.STRING()))
+                .field("enumValueMap", DataTypes.MAP(DataTypes.STRING(), 
DataTypes.STRING()))
+                .field("enumKeyMap", DataTypes.MAP(DataTypes.STRING(), 
DataTypes.STRING()))
                 .build();
     }
 
@@ -82,10 +86,15 @@ public final class ConvertersTestFixtures {
         public OffsetDateTime offsetDateTimeField;
         public Integer[] arrayField;
         public Map<String, Integer> mapField;
+        public StatusEnum enumField;
 
         @ColumnName("string_with_column_name")
         public String stringWithColumnName;
 
+        public List<StatusEnum> enumList;
+        public Map<String, StatusEnum> enumValueMap;
+        public Map<StatusEnum, String> enumKeyMap;
+
         public TestPojo() {}
 
         public TestPojo(
@@ -106,7 +115,11 @@ public final class ConvertersTestFixtures {
                 OffsetDateTime offsetDateTimeField,
                 Integer[] arrayField,
                 Map<String, Integer> mapField,
-                String stringWithColumnName) {
+                StatusEnum enumField,
+                String stringWithColumnName,
+                List<StatusEnum> enumList,
+                Map<String, StatusEnum> enumValueMap,
+                Map<StatusEnum, String> enumKeyMap) {
             this.booleanField = booleanField;
             this.byteField = byteField;
             this.shortField = shortField;
@@ -124,7 +137,11 @@ public final class ConvertersTestFixtures {
             this.offsetDateTimeField = offsetDateTimeField;
             this.arrayField = arrayField;
             this.mapField = mapField;
+            this.enumField = enumField;
             this.stringWithColumnName = stringWithColumnName;
+            this.enumList = enumList;
+            this.enumValueMap = enumValueMap;
+            this.enumKeyMap = enumKeyMap;
         }
 
         public static TestPojo sample() {
@@ -151,7 +168,21 @@ public final class ConvertersTestFixtures {
                             put("test_2", 2);
                         }
                     },
-                    "string value");
+                    StatusEnum.OK,
+                    "string value",
+                    Arrays.asList(StatusEnum.error, StatusEnum.OK),
+                    new HashMap<String, StatusEnum>() {
+                        {
+                            put("key1", StatusEnum.OK);
+                            put("key2", StatusEnum.error);
+                        }
+                    },
+                    new HashMap<StatusEnum, String>() {
+                        {
+                            put(StatusEnum.OK, "value1");
+                            put(StatusEnum.error, "value2");
+                        }
+                    });
         }
 
         @Override
@@ -179,8 +210,12 @@ public final class ConvertersTestFixtures {
                     && Objects.equals(timestampLtzField, 
testPojo.timestampLtzField)
                     && Objects.equals(offsetDateTimeField, 
testPojo.offsetDateTimeField)
                     && Objects.equals(stringWithColumnName, 
testPojo.stringWithColumnName)
+                    && Objects.equals(enumField, testPojo.enumField)
                     && Arrays.equals(arrayField, testPojo.arrayField)
-                    && Objects.equals(mapField, testPojo.mapField);
+                    && Objects.equals(mapField, testPojo.mapField)
+                    && Objects.equals(enumList, testPojo.enumList)
+                    && Objects.equals(enumKeyMap, testPojo.enumKeyMap)
+                    && Objects.equals(enumValueMap, testPojo.enumValueMap);
         }
 
         @Override
@@ -201,7 +236,11 @@ public final class ConvertersTestFixtures {
                             timestampField,
                             timestampLtzField,
                             stringWithColumnName,
+                            enumField,
                             offsetDateTimeField,
+                            enumList,
+                            enumKeyMap,
+                            enumValueMap,
                             mapField);
             result = 31 * result + Arrays.hashCode(bytesField);
             result = 31 * result + Arrays.hashCode(arrayField);
@@ -485,4 +524,15 @@ public final class ConvertersTestFixtures {
 
         public ListOfRowPojo() {}
     }
+
+    /** Enum to test enum conversion. */
+    public enum StatusEnum {
+        OK,
+        error;
+
+        @Override
+        public String toString() {
+            return "StatusEnum{}";
+        }
+    }
 }
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverterTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverterTest.java
new file mode 100644
index 000000000..970194e42
--- /dev/null
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/FlussTypeToPojoTypeConverterTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.fluss.client.converter;
+
+import org.apache.fluss.row.BinaryString;
+import org.apache.fluss.row.TimestampLtz;
+import org.apache.fluss.row.TimestampNtz;
+import org.apache.fluss.types.DataTypes;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link FlussTypeToPojoTypeConverter}. */
+public class FlussTypeToPojoTypeConverterTest {
+
+    // ==================== convertTextValue Tests ====================
+
+    @Test
+    public void testConvertTextValueToString() {
+        BinaryString binaryStr = BinaryString.fromString("Hello");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.STRING(), "field", String.class, binaryStr, 
null);
+        assertThat(result).isEqualTo("Hello");
+    }
+
+    @Test
+    public void testConvertTextValueToStringFromChar() {
+        BinaryString binaryStr = BinaryString.fromString("A");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.CHAR(1), "field", String.class, binaryStr, 
null);
+        assertThat(result).isEqualTo("A");
+    }
+
+    @Test
+    public void testConvertTextValueToCharacter() {
+        BinaryString binaryStr = BinaryString.fromString("X");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.STRING(), "field", Character.class, 
binaryStr, null);
+        assertThat(result).isEqualTo('X');
+    }
+
+    @Test
+    public void testConvertTextValueToCharacterFromChar() {
+        BinaryString binaryStr = BinaryString.fromString("Z");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.CHAR(1), "field", Character.class, 
binaryStr, null);
+        assertThat(result).isEqualTo('Z');
+    }
+
+    @Test
+    public void testConvertTextValueNullReturnsNull() {
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.STRING(), "field", String.class, null, null);
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void testConvertTextValueCharWithLengthOneIsValid() {
+        BinaryString binaryStr = BinaryString.fromString("M");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.CHAR(1), "field", String.class, binaryStr, 
null);
+        assertThat(result).isEqualTo("M");
+    }
+
+    @Test
+    public void testConvertTextValueCharWithWrongLengthThrows() {
+        BinaryString binaryStr = BinaryString.fromString("Hello");
+        assertThatThrownBy(
+                        () ->
+                                FlussTypeToPojoTypeConverter.convertTextValue(
+                                        DataTypes.CHAR(1),
+                                        "myField",
+                                        String.class,
+                                        binaryStr,
+                                        null))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testConvertTextValueCharacterWithWrongLengthThrows() {
+        BinaryString binaryStr = BinaryString.fromString("AB");
+        assertThatThrownBy(
+                        () ->
+                                FlussTypeToPojoTypeConverter.convertTextValue(
+                                        DataTypes.CHAR(1),
+                                        "charField",
+                                        Character.class,
+                                        binaryStr,
+                                        null))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testConvertTextValueEmptyStringToCharacterThrows() {
+        BinaryString binaryStr = BinaryString.fromString("");
+        assertThatThrownBy(
+                        () ->
+                                FlussTypeToPojoTypeConverter.convertTextValue(
+                                        DataTypes.STRING(),
+                                        "emptyField",
+                                        Character.class,
+                                        binaryStr,
+                                        null))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testConvertTextValueToEnumWithMatchingValue() {
+        BinaryString binaryStr = BinaryString.fromString("ACTIVE");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.STRING(),
+                        "status",
+                        StatusEnum.class,
+                        binaryStr,
+                        buildConstantMap());
+        assertThat(result).isEqualTo(StatusEnum.ACTIVE);
+    }
+
+    @Test
+    public void testConvertTextValueToEnumWithLowercaseInput() {
+        BinaryString binaryStr = BinaryString.fromString("finished");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.STRING(),
+                        "status",
+                        StatusEnum.class,
+                        binaryStr,
+                        buildConstantMap());
+        assertThat(result).isEqualTo(StatusEnum.finished);
+    }
+
+    @Test
+    public void testConvertTextValueToEnumWithInvalidValueThrows() {
+        BinaryString binaryStr = BinaryString.fromString("UNKNOWN");
+        assertThatThrownBy(
+                        () ->
+                                FlussTypeToPojoTypeConverter.convertTextValue(
+                                        DataTypes.STRING(),
+                                        "status",
+                                        StatusEnum.class,
+                                        binaryStr,
+                                        buildConstantMap()))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testConvertTextValueUnsupportedTypeThrows() {
+        BinaryString binaryStr = BinaryString.fromString("value");
+        assertThatThrownBy(
+                        () ->
+                                FlussTypeToPojoTypeConverter.convertTextValue(
+                                        DataTypes.STRING(),
+                                        "wrongField",
+                                        Integer.class,
+                                        binaryStr,
+                                        buildConstantMap()))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void 
testConvertTextValueToEnumWithMatchingValueAndWithoutConstantMap() {
+        BinaryString binaryStr = BinaryString.fromString("ACTIVE");
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTextValue(
+                        DataTypes.STRING(), "status", StatusEnum.class, 
binaryStr, null);
+        assertThat(result).isEqualTo(StatusEnum.ACTIVE);
+    }
+
+    // ==================== convertDateValue Tests ====================
+
+    @Test
+    public void testConvertDateValueEpoch() {
+        LocalDate result = FlussTypeToPojoTypeConverter.convertDateValue(0);
+        assertThat(result).isEqualTo(LocalDate.of(1970, 1, 1));
+    }
+
+    @Test
+    public void testConvertDateValuePositiveOffset() {
+        LocalDate result = 
FlussTypeToPojoTypeConverter.convertDateValue(18993);
+        assertThat(result).isEqualTo(LocalDate.of(2022, 1, 1));
+    }
+
+    // ==================== convertTimeValue Tests ====================
+
+    @Test
+    public void testConvertTimeValueMidnight() {
+        LocalTime result = FlussTypeToPojoTypeConverter.convertTimeValue(0);
+        assertThat(result).isEqualTo(LocalTime.MIDNIGHT);
+    }
+
+    @Test
+    public void testConvertTimeValueNoon() {
+        long millisOfDay = 12 * 60 * 60 * 1000;
+        LocalTime result = FlussTypeToPojoTypeConverter.convertTimeValue((int) 
millisOfDay);
+        assertThat(result).isEqualTo(LocalTime.of(12, 0, 0));
+    }
+
+    // ==================== convertTimestampNtzValue Tests ====================
+
+    @Test
+    public void testConvertTimestampNtzValue() {
+        TimestampNtz ts = 
TimestampNtz.fromLocalDateTime(LocalDateTime.of(2023, 1, 15, 10, 30));
+        Object result = 
FlussTypeToPojoTypeConverter.convertTimestampNtzValue(ts);
+        assertThat(result).isEqualTo(LocalDateTime.of(2023, 1, 15, 10, 30));
+    }
+
+    @Test
+    public void testConvertTimestampNtzValueWithNanoseconds() {
+        TimestampNtz ts = TimestampNtz.fromMillis(1000L, 500000); // 1 second 
+ 500000 nanos
+        Object result = 
FlussTypeToPojoTypeConverter.convertTimestampNtzValue(ts);
+        assertThat(result).isInstanceOf(LocalDateTime.class);
+        LocalDateTime ldt = (LocalDateTime) result;
+        assertThat(ldt.getNano()).isEqualTo(500000);
+    }
+
+    // ==================== convertTimestampLtzValue Tests ====================
+
+    @Test
+    public void testConvertTimestampLtzValueToInstant() {
+        TimestampLtz ts = TimestampLtz.fromEpochMillis(1000L);
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTimestampLtzValue(ts, 
"field", Instant.class);
+        assertThat(result).isEqualTo(Instant.ofEpochMilli(1000L));
+    }
+
+    @Test
+    public void testConvertTimestampLtzValueToOffsetDateTime() {
+        TimestampLtz ts = TimestampLtz.fromEpochMillis(0L);
+        Object result =
+                FlussTypeToPojoTypeConverter.convertTimestampLtzValue(
+                        ts, "field", OffsetDateTime.class);
+        assertThat(result).isEqualTo(OffsetDateTime.of(1970, 1, 1, 0, 0, 0, 0, 
ZoneOffset.UTC));
+    }
+
+    private static Map<String, Object> buildConstantMap() {
+        return 
FlussTypeToPojoTypeConverter.buildEnumConstantsMap(StatusEnum.class);
+    }
+
+    // ==================== Helper Enum ====================
+
+    /** Test enum for String-to-Enum conversion tests. */
+    public enum StatusEnum {
+        ACTIVE,
+        INACTIVE,
+        PENDING,
+        finished,
+        ;
+
+        @Override
+        public String toString() {
+            return "StatusEnum{}";
+        }
+    }
+}
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoArrayToFlussArrayTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoArrayToFlussArrayTest.java
index 2c21e7222..a1787bd61 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoArrayToFlussArrayTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoArrayToFlussArrayTest.java
@@ -70,6 +70,7 @@ public class PojoArrayToFlussArrayTest {
                         .field(
                                 "mapArray",
                                 
DataTypes.ARRAY(DataTypes.MAP(DataTypes.STRING(), DataTypes.INT())))
+                        .field("enumArray", 
DataTypes.ARRAY(DataTypes.STRING()))
                         .build();
 
         PojoToRowConverter<ArrayPojo> writer = 
PojoToRowConverter.of(ArrayPojo.class, table, table);
@@ -242,6 +243,12 @@ public class PojoArrayToFlussArrayTest {
 
         assertThat(resultMap2).containsEntry("test_3", 3);
         assertThat(resultMap2).containsEntry("test_4", 4);
+        // Verify enum array
+        InternalArray enumArray = row.getArray(22);
+        assertThat(enumArray.size()).isEqualTo(3);
+        assertThat(enumArray.getString(0).toString()).isEqualTo("PENDING");
+        assertThat(enumArray.getString(1).toString()).isEqualTo("ACTIVE");
+        assertThat(enumArray.getString(2).toString()).isEqualTo("finished");
     }
 
     @Test
@@ -314,6 +321,7 @@ public class PojoArrayToFlussArrayTest {
         public Instant[] timestampLtzArray;
         public Integer[][] nestedIntArray;
         public Map<String, Integer>[] mapArray;
+        public StatusEnum[] enumArray;
 
         public ArrayPojo() {}
 
@@ -368,6 +376,8 @@ public class PojoArrayToFlussArrayTest {
                             }
                         }
                     };
+            pojo.enumArray =
+                    new StatusEnum[] {StatusEnum.PENDING, StatusEnum.ACTIVE, 
StatusEnum.finished};
             return pojo;
         }
     }
@@ -434,4 +444,12 @@ public class PojoArrayToFlussArrayTest {
 
         public SimpleArrayPojo() {}
     }
+
+    /** Test enum for String-to-Enum conversion tests. */
+    public enum StatusEnum {
+        ACTIVE,
+        INACTIVE,
+        PENDING,
+        finished,
+    }
 }
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoTypeTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoTypeTest.java
index 2d261dc50..1e70a7ed9 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoTypeTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/PojoTypeTest.java
@@ -68,19 +68,16 @@ class PojoTypeTest {
 
         assertThat(pojoType.getProperty("user_id"))
                 .isNotNull()
-                .hasFieldOrPropertyWithValue("name", "userId")
-                .hasFieldOrPropertyWithValue("mappedName", "user_id");
+                .hasFieldOrPropertyWithValue("name", "userId");
 
         assertThat(pojoType.getProperty("first_name"))
                 .isNotNull()
-                .hasFieldOrPropertyWithValue("name", "firstName")
-                .hasFieldOrPropertyWithValue("mappedName", "first_name");
+                .hasFieldOrPropertyWithValue("name", "firstName");
 
         // Fields without @ColumnName should map to themselves
         assertThat(pojoType.getProperty("email"))
                 .isNotNull()
-                .hasFieldOrPropertyWithValue("name", "email")
-                .hasFieldOrPropertyWithValue("mappedName", "email");
+                .hasFieldOrPropertyWithValue("name", "email");
     }
 
     @Test
diff --git 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/RowToPojoConverterTest.java
 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/RowToPojoConverterTest.java
index 0d849a61e..cf09ab031 100644
--- 
a/fluss-client/src/test/java/org/apache/fluss/client/converter/RowToPojoConverterTest.java
+++ 
b/fluss-client/src/test/java/org/apache/fluss/client/converter/RowToPojoConverterTest.java
@@ -44,7 +44,26 @@ public class RowToPojoConverterTest {
 
         ConvertersTestFixtures.TestPojo pojo = 
ConvertersTestFixtures.TestPojo.sample();
         GenericRow row = writer.toRow(pojo);
-        assertThat(row.getFieldCount()).isEqualTo(18);
+        assertThat(row.getFieldCount()).isEqualTo(22);
+
+        ConvertersTestFixtures.TestPojo back = scanner.fromRow(row);
+        assertThat(back).isEqualTo(pojo);
+    }
+
+    @Test
+    public void testRoundTripFullSchemaWithLowerCaseEnum() {
+        RowType table = ConvertersTestFixtures.fullSchema();
+        RowType projection = table;
+
+        PojoToRowConverter<ConvertersTestFixtures.TestPojo> writer =
+                PojoToRowConverter.of(ConvertersTestFixtures.TestPojo.class, 
table, projection);
+        RowToPojoConverter<ConvertersTestFixtures.TestPojo> scanner =
+                RowToPojoConverter.of(ConvertersTestFixtures.TestPojo.class, 
table, projection);
+
+        ConvertersTestFixtures.TestPojo pojo = 
ConvertersTestFixtures.TestPojo.sample();
+        pojo.enumField = ConvertersTestFixtures.StatusEnum.error;
+        GenericRow row = writer.toRow(pojo);
+        assertThat(row.getFieldCount()).isEqualTo(22);
 
         ConvertersTestFixtures.TestPojo back = scanner.fromRow(row);
         assertThat(back).isEqualTo(pojo);

Reply via email to