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

amashenkov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 5f1ef00f698 IGNITE-24898 Fixed broken serialization in table views 
when a column is mapped to a primitive field using TypeConverter (#5660)
5f1ef00f698 is described below

commit 5f1ef00f6983e6aa67924a32de6c4ea58cce8e3c
Author: Andrew V. Mashenkov <[email protected]>
AuthorDate: Wed Apr 23 17:38:24 2025 +0300

    IGNITE-24898 Fixed broken serialization in table views when a column is 
mapped to a primitive field using TypeConverter (#5660)
---
 .../ignite/internal/marshaller/FieldAccessor.java  | 80 +++++++++------------
 .../schema/marshaller/KvMarshallerTest.java        | 81 +++++++++++++++++++++-
 .../schema/marshaller/RecordMarshallerTest.java    | 81 ++++++++++++++++++++++
 3 files changed, 193 insertions(+), 49 deletions(-)

diff --git 
a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
 
b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
index a922922cb7f..94efed6b686 100644
--- 
a/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
+++ 
b/modules/marshaller-common/src/main/java/org/apache/ignite/internal/marshaller/FieldAccessor.java
@@ -75,58 +75,44 @@ abstract class FieldAccessor {
                 validateColumnType(col, field.getType());
             }
 
-            BinaryMode fieldAccessMode = BinaryMode.forClass(field.getType());
             MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(type, 
MethodHandles.lookup());
-
             VarHandle varHandle = lookup.unreflectVarHandle(field);
 
-            assert fieldAccessMode != null : "Invalid fieldAccessMode for 
type: " + field.getType();
-
-            switch (fieldAccessMode) {
-                case P_BOOLEAN:
-                    return new BooleanPrimitiveAccessor(varHandle, colIdx);
-
-                case P_BYTE:
-                    return new BytePrimitiveAccessor(varHandle, colIdx);
-
-                case P_SHORT:
-                    return new ShortPrimitiveAccessor(varHandle, colIdx);
-
-                case P_INT:
-                    return new IntPrimitiveAccessor(varHandle, colIdx);
-
-                case P_LONG:
-                    return new LongPrimitiveAccessor(varHandle, colIdx);
-
-                case P_FLOAT:
-                    return new FloatPrimitiveAccessor(varHandle, colIdx);
-
-                case P_DOUBLE:
-                    return new DoublePrimitiveAccessor(varHandle, colIdx);
-
-                case BOOLEAN:
-                case BYTE:
-                case SHORT:
-                case INT:
-                case LONG:
-                case FLOAT:
-                case DOUBLE:
-                case STRING:
-                case UUID:
-                case BYTE_ARR:
-                case DECIMAL:
-                case TIME:
-                case DATE:
-                case DATETIME:
-                case TIMESTAMP:
-                case POJO:
-                    return new ReferenceFieldAccessor(varHandle, colIdx, 
col.type(), col.scale(), typeConverter);
-
-                default:
-                    assert false : "Invalid field access mode " + 
fieldAccessMode;
+            // Optimization for primitive types to avoid boxing/unboxing.
+            if (typeConverter == null && field.getType().isPrimitive()) {
+                BinaryMode fieldAccessMode = 
BinaryMode.forClass(field.getType());
+                assert fieldAccessMode != null : "Invalid fieldAccessMode for 
type: " + field.getType();
+
+                switch (fieldAccessMode) {
+                    case P_BOOLEAN:
+                        return new BooleanPrimitiveAccessor(varHandle, colIdx);
+
+                    case P_BYTE:
+                        return new BytePrimitiveAccessor(varHandle, colIdx);
+
+                    case P_SHORT:
+                        return new ShortPrimitiveAccessor(varHandle, colIdx);
+
+                    case P_INT:
+                        return new IntPrimitiveAccessor(varHandle, colIdx);
+
+                    case P_LONG:
+                        return new LongPrimitiveAccessor(varHandle, colIdx);
+
+                    case P_FLOAT:
+                        return new FloatPrimitiveAccessor(varHandle, colIdx);
+
+                    case P_DOUBLE:
+                        return new DoublePrimitiveAccessor(varHandle, colIdx);
+
+                    default:
+                        assert false : "Invalid field access mode " + 
fieldAccessMode;
+                }
+
+                throw new IllegalArgumentException("Failed to create accessor 
for field [name=" + field.getName() + ']');
             }
 
-            throw new IllegalArgumentException("Failed to create accessor for 
field [name=" + field.getName() + ']');
+            return new ReferenceFieldAccessor(varHandle, colIdx, col.type(), 
col.scale(), typeConverter);
         } catch (NoSuchFieldException | SecurityException | 
IllegalAccessException ex) {
             throw new IllegalArgumentException(ex);
         }
diff --git 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/KvMarshallerTest.java
 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/KvMarshallerTest.java
index df6dd01230f..a3bf2a1f180 100644
--- 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/KvMarshallerTest.java
+++ 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/KvMarshallerTest.java
@@ -620,6 +620,48 @@ public class KvMarshallerTest {
         assertEquals(columnIdxToValue.get(4), fullRow.value(4));
     }
 
+    @Test
+    public void testTypesMismatch() {
+        SchemaDescriptor schema = new SchemaDescriptor(
+                schemaVersion.incrementAndGet(),
+                new Column[]{new Column("KEY", INT64, false)},
+                new Column[]{new Column("VAL", NativeTypes.stringOf(255), 
true),
+                });
+
+        TypeConverter<Object, Object> converter = new TypeConverter<>() {
+            @Override
+            public Object toColumnType(Object obj) {
+                return obj;
+            }
+
+            @Override
+            public Object toObjectType(Object data) {
+                return data;
+            }
+        };
+
+        KvMarshaller<Long, TestPojo2> intFieldMarshaller = new 
ReflectionMarshallerFactory().create(
+                schema,
+                Mapper.of(Long.class, "key"),
+                Mapper.builder(TestPojo2.class).map("intField", "val", 
converter).build()
+        );
+        KvMarshaller<Long, TestPojo2> stringFieldMarshaller = new 
ReflectionMarshallerFactory().create(
+                schema,
+                Mapper.of(Long.class, "key"),
+                Mapper.builder(TestPojo2.class).map("strField", "val", 
converter).build()
+        );
+
+        TestPojo2 pojo = new TestPojo2(42, "43");
+        Row row = stringFieldMarshaller.marshal(1L, pojo);
+
+        assertEquals(new TestPojo2(0, "43"), 
stringFieldMarshaller.unmarshalValue(row));
+
+        assertThrows(MarshallerException.class, () -> 
intFieldMarshaller.marshal(1L, pojo),
+                "Value type does not match [column='VAL', 
expected=STRING(255), actual=INT32]");
+        assertThrows(MarshallerException.class, () -> 
intFieldMarshaller.unmarshalValue(row),
+                "Value type does not match [column='VAL', 
expected=STRING(255), actual=INT32]");
+    }
+
     static class TestObjectKeyPart {
         int col2;
         boolean col4;
@@ -774,8 +816,8 @@ public class KvMarshallerTest {
      * Compare object regarding NativeType.
      *
      * @param type Native type.
-     * @param exp  Expected value.
-     * @param act  Actual value.
+     * @param exp Expected value.
+     * @param act Actual value.
      */
     private void compareObjects(NativeType type, Object exp, Object act) {
         if (type.spec() == NativeTypeSpec.BYTES) {
@@ -1095,6 +1137,41 @@ public class KvMarshallerTest {
         }
     }
 
+    /**
+     * Test object represents a user object of arbitrary type.
+     */
+    static class TestPojo2 implements Serializable {
+        private static final long serialVersionUID = -1L;
+
+        int intField;
+        String strField;
+
+        TestPojo2() {
+        }
+
+        TestPojo2(int intVal, String strVal) {
+            this.intField = intVal;
+            this.strField = strVal;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TestPojo2 testPojo = (TestPojo2) o;
+            return intField == testPojo.intField && Objects.equals(strField, 
testPojo.strField);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(intField, strField);
+        }
+    }
+
     /**
      * Wrapper for the {@link TestPojo}.
      */
diff --git 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/RecordMarshallerTest.java
 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/RecordMarshallerTest.java
index 3d6f41e875f..080375c4e90 100644
--- 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/RecordMarshallerTest.java
+++ 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/marshaller/RecordMarshallerTest.java
@@ -47,6 +47,7 @@ import com.facebook.presto.bytecode.MethodDefinition;
 import com.facebook.presto.bytecode.ParameterizedType;
 import com.facebook.presto.bytecode.Variable;
 import com.facebook.presto.bytecode.expression.BytecodeExpressions;
+import java.io.Serializable;
 import java.lang.reflect.Field;
 import java.time.LocalDate;
 import java.util.Arrays;
@@ -313,6 +314,52 @@ public class RecordMarshallerTest {
         }
     }
 
+    @Test
+    public void testTypesMismatch() {
+        SchemaDescriptor schema = new SchemaDescriptor(
+                schemaVersion.incrementAndGet(),
+                new Column[]{new Column("KEY", INT64, false)},
+                new Column[]{new Column("VAL", NativeTypes.stringOf(255), 
true),
+                });
+
+        TypeConverter<Object, Object> converter = new TypeConverter<>() {
+            @Override
+            public Object toColumnType(Object obj) {
+                return obj;
+            }
+
+            @Override
+            public Object toObjectType(Object data) {
+                return data;
+            }
+        };
+
+        RecordMarshaller<TestPojo> intFieldMarshaller = new 
ReflectionMarshallerFactory().create(
+                schema,
+                Mapper.builder(TestPojo.class)
+                        .map("id", "key")
+                        .map("intField", "val", converter)
+                        .build()
+        );
+        RecordMarshaller<TestPojo> stringFieldMarshaller = new 
ReflectionMarshallerFactory().create(
+                schema,
+                Mapper.builder(TestPojo.class)
+                        .map("id", "key")
+                        .map("strField", "val", converter)
+                        .build()
+        );
+
+        TestPojo pojo = new TestPojo(1L, 42, "43");
+        Row row = stringFieldMarshaller.marshal(pojo);
+
+        assertEquals(new TestPojo(1L, 0, "43"), 
stringFieldMarshaller.unmarshal(row));
+
+        assertThrows(MarshallerException.class, () -> 
intFieldMarshaller.marshal(pojo),
+                "Value type does not match [column='VAL', 
expected=STRING(255), actual=INT32]");
+        assertThrows(MarshallerException.class, () -> 
intFieldMarshaller.unmarshal(row),
+                "Value type does not match [column='VAL', 
expected=STRING(255), actual=INT32]");
+    }
+
     /**
      * Validate all types are tested.
      */
@@ -523,6 +570,40 @@ public class RecordMarshallerTest {
         }
     }
 
+    /**
+     * Test object represents a user object of arbitrary type.
+     */
+    static class TestPojo implements Serializable {
+        private static final long serialVersionUID = -1L;
+
+        long id;
+        int intField;
+        String strField;
+
+        TestPojo() {
+        }
+
+        TestPojo(long id, int intVal, String strVal) {
+            this.id = id;
+            this.intField = intVal;
+            this.strField = strVal;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TestPojo testPojo = (TestPojo) o;
+            return id == testPojo.id && intField == testPojo.intField && 
Objects.equals(strField, testPojo.strField);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, intField, strField);
+        }
+    }
+
     /**
      * Test object without default constructor.
      */

Reply via email to