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.
*/