This is an automated email from the ASF dual-hosted git repository.
ppa pushed a commit to branch ignite-27612-tuple-implicit-casts
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to
refs/heads/ignite-27612-tuple-implicit-casts by this push:
new 98697c8d265 IGNITE-27552 Add support for writing numeric primitive
values with allowed type casting (#7439)
98697c8d265 is described below
commit 98697c8d265d29d5706a369807801fc92d1c83e3
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Thu Jan 22 19:51:45 2026 +0300
IGNITE-27552 Add support for writing numeric primitive values with allowed
type casting (#7439)
---
.../client/proto/ClientBinaryTupleUtils.java | 15 +-
.../ignite/internal/util/TupleTypeCastUtils.java | 48 ++-
.../org/apache/ignite/internal/schema/Column.java | 5 +
.../ignite/internal/schema/row/RowAssembler.java | 15 +-
.../table/ItKeyValueBinaryViewApiTest.java | 427 +++++++++++++++++++-
.../internal/table/ItRecordBinaryViewApiTest.java | 433 ++++++++++++++++++++-
.../table/ItTableViewApiUnifiedBaseTest.java | 15 +
7 files changed, 939 insertions(+), 19 deletions(-)
diff --git
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
index 73bc9cb2c33..5e9e38e1932 100644
---
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
+++
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java
@@ -32,6 +32,7 @@ import
org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
import org.apache.ignite.internal.binarytuple.BinaryTupleReader;
import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
+import org.apache.ignite.internal.util.TupleTypeCastUtils;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.MarshallerException;
import org.apache.ignite.sql.ColumnType;
@@ -204,27 +205,27 @@ public class ClientBinaryTupleUtils {
return;
case INT8:
- builder.appendByte((byte) v);
+ builder.appendByte(TupleTypeCastUtils.castToByte(v));
return;
case INT16:
- builder.appendShort((short) v);
+ builder.appendShort(TupleTypeCastUtils.castToShort(v));
return;
case INT32:
- builder.appendInt((int) v);
+ builder.appendInt(TupleTypeCastUtils.castToInt(v));
return;
case INT64:
- builder.appendLong((long) v);
+ builder.appendLong(TupleTypeCastUtils.castToLong(v));
return;
case FLOAT:
- builder.appendFloat((float) v);
+ builder.appendFloat(TupleTypeCastUtils.castToFloat(v));
return;
case DOUBLE:
- builder.appendDouble((double) v);
+ builder.appendDouble(TupleTypeCastUtils.castToDouble(v));
return;
case DECIMAL:
@@ -262,7 +263,7 @@ public class ClientBinaryTupleUtils {
default:
throw new IllegalArgumentException("Unsupported type: " +
type);
}
- } catch (ClassCastException e) {
+ } catch (ArithmeticException | ClassCastException e) {
NativeType nativeType = NativeTypes.fromObject(v);
if (nativeType == null) {
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java
b/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java
index 845a69990ac..d816bc03627 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java
@@ -23,8 +23,8 @@ import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.table.Tuple;
/**
- * Helper methods that perform conversions between numeric types. These
methods are used when reading
- * and writing the primitive numeric values of a {@link Tuple tuple}.
+ * Helper methods that perform conversions between numeric types. These
methods are
+ * used when reading and writing the primitive numeric values to a {@link
Tuple tuple}.
*
* <p>The following conversions are supported:
* <ul>
@@ -40,8 +40,52 @@ public class TupleTypeCastUtils {
private static final String TYPE_CAST_ERROR_COLUMN_INDEX = "Column with
index {} has type {} but {} was requested";
+ /** Integer column types bitmask. */
private static final int INT_COLUMN_TYPES_BITMASK =
buildIntegerTypesBitMask();
+ /**
+ * Checks whether a cast is possible between two types for the given value.
+ *
+ * <p>Widening casts between integer types and between floating-point
types are always allowed.
+ *
+ * <p>Narrowing casts between integer types and between floating-point
types are allowed only
+ * when the provided value can be represented in the target type.
+ *
+ * @param from Source column type
+ * @param to Target column type
+ * @param val The value to be cast
+ * @return {@code True} if the cast is possible without data loss, {@code
false} otherwise.
+ */
+ public static boolean isCastAllowed(ColumnType from, ColumnType to, Object
val) {
+ if (!(val instanceof Number)) {
+ return false;
+ }
+
+ Number number = (Number) val;
+
+ switch (to) {
+ case INT8:
+ return integerType(from) && number.byteValue() ==
number.longValue();
+ case INT16:
+ return integerType(from) && number.shortValue() ==
number.longValue();
+ case INT32:
+ return integerType(from) && number.intValue() ==
number.longValue();
+ case INT64:
+ return integerType(from);
+ case FLOAT:
+ if (from == ColumnType.DOUBLE) {
+ double doubleValue = number.doubleValue();
+ return number.floatValue() == doubleValue ||
Double.isNaN(doubleValue);
+ }
+ return false;
+ case DOUBLE:
+ return from == ColumnType.FLOAT;
+
+ default:
+ return false;
+ }
+ }
+
/** Reads a value from the tuple and converts it to a byte if possible. */
public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
if (!integerType(actualType)) {
diff --git
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/Column.java
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/Column.java
index 8b1fd49948a..18029a59397 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/Column.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/Column.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.tostring.S;
import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
import org.apache.ignite.internal.type.VarlenNativeType;
+import org.apache.ignite.internal.util.TupleTypeCastUtils;
import org.apache.ignite.sql.ColumnType;
import org.jetbrains.annotations.Nullable;
@@ -222,6 +223,10 @@ public class Column {
String error = format("Value too long [column='{}', type={}]",
name, type.displayName());
throw new InvalidTypeException(error);
} else {
+ if (TupleTypeCastUtils.isCastAllowed(objType.spec(),
type.spec(), val)) {
+ return;
+ }
+
String error = format(
"Value type does not match [column='{}', expected={},
actual={}]",
name, type.displayName(), objType.displayName()
diff --git
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
index 5dbb941cd70..83c34317776 100644
---
a/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
+++
b/modules/schema/src/main/java/org/apache/ignite/internal/schema/row/RowAssembler.java
@@ -109,6 +109,9 @@ public class RowAssembler {
/**
* Helper method.
*
+ * <p>This method performs implicit conversion of numeric types without
range checking,
+ * it assumes that the check has already been performed (see {@link
Column#validate(Object)}).
+ *
* @param val Value.
* @throws SchemaMismatchException If a value doesn't match the current
column type.
*/
@@ -124,22 +127,22 @@ public class RowAssembler {
return appendBoolean((boolean) val);
}
case INT8: {
- return appendByte((byte) val);
+ return appendByte(((Number) val).byteValue());
}
case INT16: {
- return appendShort((short) val);
+ return appendShort(((Number) val).shortValue());
}
case INT32: {
- return appendInt((int) val);
+ return appendInt(((Number) val).intValue());
}
case INT64: {
- return appendLong((long) val);
+ return appendLong(((Number) val).longValue());
}
case FLOAT: {
- return appendFloat((float) val);
+ return appendFloat(((Number) val).floatValue());
}
case DOUBLE: {
- return appendDouble((double) val);
+ return appendDouble(((Number) val).doubleValue());
}
case UUID: {
return appendUuid((UUID) val);
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItKeyValueBinaryViewApiTest.java
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItKeyValueBinaryViewApiTest.java
index 5bc8cc1f272..e9b1d4addbb 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItKeyValueBinaryViewApiTest.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItKeyValueBinaryViewApiTest.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -39,10 +40,12 @@ import
org.apache.ignite.internal.schema.InvalidTypeException;
import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.SchemaMismatchException;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
+import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
import org.apache.ignite.lang.ErrorGroups.Marshalling;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.MarshallerException;
+import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.table.KeyValueView;
import org.apache.ignite.table.Tuple;
import org.apache.ignite.tx.Transaction;
@@ -65,6 +68,8 @@ public class ItKeyValueBinaryViewApiTest extends
ItKeyValueViewApiBaseTest {
private static final String TABLE_NAME_FOR_SCHEMA_VALIDATION =
"test_schema";
+ private static final String TABLE_NAME_FOR_TYPE_CAST = "test_type_cast";
+
Map<String, TestTableDefinition> createdTables;
@BeforeAll
@@ -96,6 +101,18 @@ public class ItKeyValueBinaryViewApiTest extends
ItKeyValueViewApiBaseTest {
new Column("STR", NativeTypes.stringOf(3),
true),
new Column("BLOB", NativeTypes.blobOf(3), true)
}
+ ),
+ new TestTableDefinition(
+ TABLE_NAME_FOR_TYPE_CAST,
+ simpleKey,
+ new Column[]{
+ new Column("C_BYTE", NativeTypes.INT8, true),
+ new Column("C_SHORT", NativeTypes.INT16, true),
+ new Column("C_INT", NativeTypes.INT32, true),
+ new Column("C_LONG", NativeTypes.INT64, true),
+ new Column("C_FLOAT", NativeTypes.FLOAT, true),
+ new Column("C_DOUBLE", NativeTypes.DOUBLE,
true)
+ }
)
);
@@ -441,7 +458,7 @@ public class ItKeyValueBinaryViewApiTest extends
ItKeyValueViewApiBaseTest {
public void validateSchema(TestCase testCase) {
KeyValueView<Tuple, Tuple> tbl = testCase.view();
- Tuple keyTuple0 = Tuple.create().set("id", 0).set("id1", 0);
+ Tuple keyTuple0 = Tuple.create().set("id", 0.0d).set("id1", 0);
Tuple keyTuple1 = Tuple.create().set("id1", 0);
Tuple key = Tuple.create().set("id", 1L);
@@ -582,6 +599,408 @@ public class ItKeyValueBinaryViewApiTest extends
ItKeyValueViewApiBaseTest {
);
}
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsByte(TestCase testCase) {
+ KeyValueView<Tuple, Tuple> tbl = testCase.view();
+ String keyName = testCase.keyColumnName(0);
+ String valName = testCase.valColumnName(0);
+ Tuple key = Tuple.create().set(keyName, 1L);
+ ColumnType targetType = ColumnType.INT8;
+
+ // Put short value.
+ {
+ Tuple val = Tuple.create().set(valName, (short) Byte.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).byteValue(valName),
is(Byte.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, (short)
(Byte.MAX_VALUE + 1));
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT16, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (short) Byte.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).byteValue(valName),
is(Byte.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, (short)
(Byte.MIN_VALUE - 1));
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT16, testCase.thin);
+ }
+
+ // Put int value.
+ {
+ Tuple val = Tuple.create().set(valName, (int) Byte.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).byteValue(valName),
is(Byte.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, Byte.MAX_VALUE + 1);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (int) Byte.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).byteValue(valName),
is(Byte.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, Byte.MIN_VALUE - 1);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ // Put long value.
+ {
+ Tuple val = Tuple.create().set(valName, (long) Byte.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).byteValue(valName),
is(Byte.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, (long)
(Byte.MAX_VALUE + 1));
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (long) Byte.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).byteValue(valName),
is(Byte.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, (long)
(Byte.MIN_VALUE - 1));
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.create().set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, floatValue), valName,
targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.create().set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, doubleValue), valName,
targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.create().set(valName, decimal);
+ expectTypeMismatch(() -> tbl.put(null, key, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsShort(TestCase testCase) {
+ KeyValueView<Tuple, Tuple> tbl = testCase.view();
+ String keyName = testCase.keyColumnName(0);
+ String valName = testCase.valColumnName(1);
+ Tuple key = Tuple.create().set(keyName, 1L);
+ ColumnType targetType = ColumnType.INT16;
+
+ // Put byte value.
+ {
+ Tuple val = Tuple.create().set(valName, Byte.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).shortValue(valName), is((short)
Byte.MAX_VALUE));
+ }
+
+ // Put int value.
+ {
+ Tuple val = Tuple.create().set(valName, (int) Short.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).shortValue(valName),
is(Short.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, Short.MAX_VALUE +
1);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (int) Short.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).shortValue(valName),
is(Short.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, Short.MIN_VALUE -
1);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ // Put long value.
+ {
+ Tuple val = Tuple.create().set(valName, (long) Short.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).shortValue(valName),
is(Short.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, (long)
(Short.MAX_VALUE + 1));
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (long) Short.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).shortValue(valName),
is(Short.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, (long)
(Short.MIN_VALUE - 1));
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.create().set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, floatValue), valName,
targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.create().set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, doubleValue), valName,
targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.create().set(valName, decimal);
+ expectTypeMismatch(() -> tbl.put(null, key, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsInt(TestCase testCase) {
+ KeyValueView<Tuple, Tuple> tbl = testCase.view();
+ String keyName = testCase.keyColumnName(0);
+ String valName = testCase.valColumnName(2);
+ Tuple key = Tuple.create().set(keyName, 1L);
+ ColumnType targetType = ColumnType.INT32;
+
+ // Put byte value.
+ {
+ Tuple val = Tuple.create().set(valName, Byte.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).intValue(valName), is((int)
Byte.MAX_VALUE));
+ }
+
+ // Put short value.
+ {
+ Tuple val = Tuple.create().set(valName, Short.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).intValue(valName), is((int)
Short.MAX_VALUE));
+ }
+
+ // Put long value.
+ {
+ Tuple val = Tuple.create().set(valName, (long) Integer.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).intValue(valName),
is(Integer.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, ((long)
Integer.MAX_VALUE) + 1);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (long) Integer.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).intValue(valName),
is(Integer.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, ((long)
Integer.MIN_VALUE) - 1);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.create().set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, floatValue), valName,
targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.create().set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, doubleValue), valName,
targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.create().set(valName, decimal);
+ expectTypeMismatch(() -> tbl.put(null, key, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsLong(TestCase testCase) {
+ KeyValueView<Tuple, Tuple> tbl = testCase.view();
+ String keyName = testCase.keyColumnName(0);
+ String valName = testCase.valColumnName(3);
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put byte value.
+ {
+ Tuple val = Tuple.create().set(valName, Byte.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).longValue(valName), is((long)
Byte.MAX_VALUE));
+ }
+
+ // Put short value.
+ {
+ Tuple val = Tuple.create().set(valName, Short.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).longValue(valName), is((long)
Short.MAX_VALUE));
+ }
+
+ // Put int value.
+ {
+ Tuple val = Tuple.create().set(valName, Integer.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).longValue(valName), is((long)
Integer.MAX_VALUE));
+ }
+
+ ColumnType targetType = ColumnType.INT64;
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.create().set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, floatValue), valName,
targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.create().set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, doubleValue), valName,
targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.create().set(valName, decimal);
+ expectTypeMismatch(() -> tbl.put(null, key, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsFloat(TestCase testCase) {
+ KeyValueView<Tuple, Tuple> tbl = testCase.view();
+ String keyName = testCase.keyColumnName(0);
+ String valName = testCase.valColumnName(4);
+ Tuple key = Tuple.create().set(keyName, 1L);
+ ColumnType targetType = ColumnType.FLOAT;
+
+ // Put double value.
+ {
+ Tuple val = Tuple.create().set(valName, (double) Float.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).floatValue(valName),
is(Float.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.create().set(valName, (double) Float.MIN_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).floatValue(valName),
is(Float.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.create().set(valName, Double.MIN_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, outOfRange), valName,
targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // NaN value.
+ {
+ Tuple val = Tuple.create().set(valName, Double.NaN);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).floatValue(valName), is(Float.NaN));
+ }
+
+ // Positive infinity value.
+ {
+ Tuple val = Tuple.create().set(valName, Double.POSITIVE_INFINITY);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).floatValue(valName),
is(Float.POSITIVE_INFINITY));
+ }
+
+ // Negative infinity value.
+ {
+ Tuple val = Tuple.create().set(valName, Double.NEGATIVE_INFINITY);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).floatValue(valName),
is(Float.NEGATIVE_INFINITY));
+ }
+
+ // Wrong (integer) types
+ {
+ Tuple byteValue = Tuple.create().set(valName, Byte.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, byteValue), valName,
targetType, NativeTypes.INT8, testCase.thin);
+
+ Tuple shortValue = Tuple.create().set(valName, Short.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, shortValue), valName,
targetType, NativeTypes.INT16, testCase.thin);
+
+ Tuple intValue = Tuple.create().set(valName, Integer.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, intValue), valName,
targetType, NativeTypes.INT32, testCase.thin);
+
+ Tuple longValue = Tuple.create().set(valName, Long.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, longValue), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.create().set(valName, decimal);
+ expectTypeMismatch(() -> tbl.put(null, key, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsDouble(TestCase testCase) {
+ KeyValueView<Tuple, Tuple> tbl = testCase.view();
+ String keyName = testCase.keyColumnName(0);
+ String valName = testCase.valColumnName(5);
+ Tuple key = Tuple.create().set(keyName, 1L);
+ ColumnType targetType = ColumnType.DOUBLE;
+
+ // Put float value.
+ {
+ Tuple val = Tuple.create().set(valName, Float.MAX_VALUE);
+
+ tbl.put(null, key, val);
+ assertThat(tbl.get(null, key).doubleValue(valName), is((double)
Float.MAX_VALUE));
+ }
+
+ // Wrong (integer) types
+ {
+ Tuple byteValue = Tuple.create().set(valName, Byte.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, byteValue), valName,
targetType, NativeTypes.INT8, testCase.thin);
+
+ Tuple shortValue = Tuple.create().set(valName, Short.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, shortValue), valName,
targetType, NativeTypes.INT16, testCase.thin);
+
+ Tuple intValue = Tuple.create().set(valName, Integer.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, intValue), valName,
targetType, NativeTypes.INT32, testCase.thin);
+
+ Tuple longValue = Tuple.create().set(valName, Long.MAX_VALUE);
+ expectTypeMismatch(() -> tbl.put(null, key, longValue), valName,
targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.create().set(valName, decimal);
+ expectTypeMismatch(() -> tbl.put(null, key, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
private List<Arguments> testCases() {
List<Arguments> args1 =
generateKeyValueTestArguments(TABLE_NAME_API_TEST, Tuple.class, Tuple.class);
List<Arguments> args2 =
generateKeyValueTestArguments(TABLE_NAME_API_TEST_QUOTED, Tuple.class,
Tuple.class, " (quoted names)");
@@ -599,6 +1018,10 @@ public class ItKeyValueBinaryViewApiTest extends
ItKeyValueViewApiBaseTest {
return generateKeyValueTestArguments(TABLE_NAME_FOR_SCHEMA_VALIDATION,
Tuple.class, Tuple.class);
}
+ private List<Arguments> typeCastTestCases() {
+ return generateKeyValueTestArguments(TABLE_NAME_FOR_TYPE_CAST,
Tuple.class, Tuple.class);
+ }
+
@Override
TestCaseFactory getFactory(String name) {
return new TestCaseFactory(name) {
@@ -682,7 +1105,7 @@ public class ItKeyValueBinaryViewApiTest extends
ItKeyValueViewApiBaseTest {
}
void checkValueTypeDoesNotMatchError(Executable run) {
- String expectedMessage = "Value type does not match [column='ID',
expected=INT64, actual=INT32]";
+ String expectedMessage = "Value type does not match [column='ID',
expected=INT64, actual=DOUBLE]";
if (thin) {
MarshallerException ex = (MarshallerException)
assertThrows(MarshallerException.class, run, expectedMessage);
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItRecordBinaryViewApiTest.java
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItRecordBinaryViewApiTest.java
index 94cd7865261..34375033c68 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItRecordBinaryViewApiTest.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItRecordBinaryViewApiTest.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -34,11 +35,13 @@ import
org.apache.ignite.internal.schema.InvalidTypeException;
import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.SchemaMismatchException;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
+import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
import org.apache.ignite.lang.ErrorGroups.Marshalling;
import org.apache.ignite.lang.IgniteException;
import org.apache.ignite.lang.MarshallerException;
import org.apache.ignite.lang.util.IgniteNameUtils;
+import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.table.RecordView;
import org.apache.ignite.table.Tuple;
import org.hamcrest.Matchers;
@@ -69,6 +72,8 @@ public class ItRecordBinaryViewApiTest extends
ItRecordViewApiBaseTest {
private static final String TABLE_BYTE_TYPE_MATCH = "test_byte_type_match";
+ private static final String TABLE_NAME_FOR_TYPE_CAST = "test_type_cast";
+
private Map<String, TestTableDefinition> schemaAwareTestTables;
@BeforeAll
@@ -124,6 +129,18 @@ public class ItRecordBinaryViewApiTest extends
ItRecordViewApiBaseTest {
new Column("STR", NativeTypes.stringOf(3),
true),
new Column("BLOB", NativeTypes.blobOf(3), true)
}
+ ),
+ new TestTableDefinition(
+ TABLE_NAME_FOR_TYPE_CAST,
+ DEFAULT_KEY,
+ new Column[]{
+ new Column("C_BYTE", NativeTypes.INT8, true),
+ new Column("C_SHORT", NativeTypes.INT16, true),
+ new Column("C_INT", NativeTypes.INT32, true),
+ new Column("C_LONG", NativeTypes.INT64, true),
+ new Column("C_FLOAT", NativeTypes.FLOAT, true),
+ new Column("C_DOUBLE", NativeTypes.DOUBLE,
true)
+ }
)
);
@@ -328,7 +345,7 @@ public class ItRecordBinaryViewApiTest extends
ItRecordViewApiBaseTest {
RecordView<Tuple> tbl = testCase.view();
- Tuple keyTuple0 = Tuple.create().set("id", 0).set("id1", 0);
+ Tuple keyTuple0 = Tuple.create().set("id",
Double.MAX_VALUE).set("id1", 0);
Tuple keyTuple1 = Tuple.create().set("id1", 0);
Tuple tuple0 = Tuple.create().set("id", 1L).set("str",
"qweqweqwe").set("val", 11L);
Tuple tuple1 = Tuple.create().set("id", 1L).set("blob", new byte[]{0,
1, 2, 3}).set("val", 22L);
@@ -791,6 +808,414 @@ public class ItRecordBinaryViewApiTest extends
ItRecordViewApiBaseTest {
}
}
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsByte(BinTestCase testCase) {
+ RecordView<Tuple> recordView = testCase.view();
+ String keyName = DEFAULT_KEY[0].name();
+ String valName = "C_BYTE";
+ ColumnType targetType = ColumnType.INT8;
+
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put short value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (short) Byte.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).byteValue(valName),
is(Byte.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, (short)
(Byte.MAX_VALUE + 1));
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT16, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (short) Byte.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).byteValue(valName),
is(Byte.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, (short)
(Byte.MIN_VALUE - 1));
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT16, testCase.thin);
+ }
+
+ // Put int value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (int) Byte.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).byteValue(valName),
is(Byte.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, Byte.MAX_VALUE +
1);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (int) Byte.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).byteValue(valName),
is(Byte.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, Byte.MIN_VALUE -
1);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ // Put long value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (long) Byte.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).byteValue(valName),
is(Byte.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, (long)
(Byte.MAX_VALUE + 1));
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (long) Byte.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).byteValue(valName),
is(Byte.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, (long)
(Byte.MIN_VALUE - 1));
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.copy(key).set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, floatValue),
valName, targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.copy(key).set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, doubleValue),
valName, targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.copy(key).set(valName, decimal);
+ expectTypeMismatch(() -> recordView.upsert(null, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsShort(BinTestCase testCase) {
+ RecordView<Tuple> recordView = testCase.view();
+ String keyName = DEFAULT_KEY[0].name();
+ String valName = "C_SHORT";
+ ColumnType targetType = ColumnType.INT16;
+
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put byte value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Byte.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).shortValue(valName),
is((short) Byte.MAX_VALUE));
+ }
+
+ // Put int value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (int) Short.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).shortValue(valName),
is(Short.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, Short.MAX_VALUE +
1);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (int) Short.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).shortValue(valName),
is(Short.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, Short.MIN_VALUE -
1);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT32, testCase.thin);
+ }
+
+ // Put long value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (long) Short.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).shortValue(valName),
is(Short.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, (long)
(Short.MAX_VALUE + 1));
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (long) Short.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).shortValue(valName),
is(Short.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, (long)
(Short.MIN_VALUE - 1));
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.copy(key).set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, floatValue),
valName, targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.copy(key).set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, doubleValue),
valName, targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.copy(key).set(valName, decimal);
+ expectTypeMismatch(() -> recordView.upsert(null, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsInt(BinTestCase testCase) {
+ RecordView<Tuple> recordView = testCase.view();
+ String keyName = DEFAULT_KEY[0].name();
+ String valName = "C_INT";
+ ColumnType targetType = ColumnType.INT32;
+
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put byte value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Byte.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).intValue(valName), is((int)
Byte.MAX_VALUE));
+ }
+
+ // Put short value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Short.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).intValue(valName), is((int)
Short.MAX_VALUE));
+ }
+
+ // Put long value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (long) Integer.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).intValue(valName),
is(Integer.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, ((long)
Integer.MAX_VALUE) + 1);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (long) Integer.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).intValue(valName),
is(Integer.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, ((long)
Integer.MIN_VALUE) - 1);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.copy(key).set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, floatValue),
valName, targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.copy(key).set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, doubleValue),
valName, targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.copy(key).set(valName, decimal);
+ expectTypeMismatch(() -> recordView.upsert(null, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsLong(BinTestCase testCase) {
+ RecordView<Tuple> recordView = testCase.view();
+ String keyName = DEFAULT_KEY[0].name();
+ String valName = "C_LONG";
+
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put byte value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Byte.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).longValue(valName), is((long)
Byte.MAX_VALUE));
+ }
+
+ // Put short value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Short.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).longValue(valName), is((long)
Short.MAX_VALUE));
+ }
+
+ // Put int value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Integer.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).longValue(valName), is((long)
Integer.MAX_VALUE));
+ }
+
+ ColumnType targetType = ColumnType.INT64;
+
+ // Wrong (floating point) types
+ {
+ Tuple floatValue = Tuple.copy(key).set(valName, Float.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, floatValue),
valName, targetType, NativeTypes.FLOAT, testCase.thin);
+
+ Tuple doubleValue = Tuple.copy(key).set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, doubleValue),
valName, targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.copy(key).set(valName, decimal);
+ expectTypeMismatch(() -> recordView.upsert(null, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsFloat(BinTestCase testCase) {
+ RecordView<Tuple> recordView = testCase.view();
+ String keyName = DEFAULT_KEY[0].name();
+ String valName = "C_FLOAT";
+ ColumnType targetType = ColumnType.FLOAT;
+
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put double value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, (double) Float.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).floatValue(valName),
is(Float.MAX_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, Double.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ {
+ Tuple val = Tuple.copy(key).set(valName, (double) Float.MIN_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).floatValue(valName),
is(Float.MIN_VALUE));
+
+ Tuple outOfRange = Tuple.copy(key).set(valName, Double.MIN_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, outOfRange),
valName, targetType, NativeTypes.DOUBLE, testCase.thin);
+ }
+
+ // NaN value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Double.NaN);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).floatValue(valName),
is(Float.NaN));
+ }
+
+ // Positive infinity value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Double.POSITIVE_INFINITY);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).floatValue(valName),
is(Float.POSITIVE_INFINITY));
+ }
+
+ // Negative infinity value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Double.NEGATIVE_INFINITY);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).floatValue(valName),
is(Float.NEGATIVE_INFINITY));
+ }
+
+ // Wrong (integer) types
+ {
+ Tuple byteValue = Tuple.copy(key).set(valName, Byte.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, byteValue),
valName, targetType, NativeTypes.INT8, testCase.thin);
+
+ Tuple shortValue = Tuple.copy(key).set(valName, Short.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, shortValue),
valName, targetType, NativeTypes.INT16, testCase.thin);
+
+ Tuple intValue = Tuple.copy(key).set(valName, Integer.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, intValue),
valName, targetType, NativeTypes.INT32, testCase.thin);
+
+ Tuple longValue = Tuple.copy(key).set(valName, Long.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, longValue),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.copy(key).set(valName, decimal);
+ expectTypeMismatch(() -> recordView.upsert(null, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("typeCastTestCases")
+ public void testWriteAsDouble(BinTestCase testCase) {
+ RecordView<Tuple> recordView = testCase.view();
+ String keyName = DEFAULT_KEY[0].name();
+ String valName = "C_DOUBLE";
+ ColumnType targetType = ColumnType.DOUBLE;
+
+ Tuple key = Tuple.create().set(keyName, 1L);
+
+ // Put float value.
+ {
+ Tuple val = Tuple.copy(key).set(valName, Float.MAX_VALUE);
+
+ recordView.upsert(null, val);
+ assertThat(recordView.get(null, key).doubleValue(valName),
is((double) Float.MAX_VALUE));
+ }
+
+ // Wrong (integer) types
+ {
+ Tuple byteValue = Tuple.copy(key).set(valName, Byte.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, byteValue),
valName, targetType, NativeTypes.INT8, testCase.thin);
+
+ Tuple shortValue = Tuple.copy(key).set(valName, Short.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, shortValue),
valName, targetType, NativeTypes.INT16, testCase.thin);
+
+ Tuple intValue = Tuple.copy(key).set(valName, Integer.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, intValue),
valName, targetType, NativeTypes.INT32, testCase.thin);
+
+ Tuple longValue = Tuple.copy(key).set(valName, Long.MAX_VALUE);
+ expectTypeMismatch(() -> recordView.upsert(null, longValue),
valName, targetType, NativeTypes.INT64, testCase.thin);
+ }
+
+ // Wrong (decimal) type
+ {
+ BigDecimal decimal = new BigDecimal(1);
+ NativeType valueType = NativeTypes.fromObject(decimal);
+ Tuple decimalValue = Tuple.copy(key).set(valName, decimal);
+ expectTypeMismatch(() -> recordView.upsert(null, decimalValue),
valName, targetType, valueType, testCase.thin);
+ }
+ }
+
/**
* Check tuples equality.
*
@@ -863,6 +1288,10 @@ public class ItRecordBinaryViewApiTest extends
ItRecordViewApiBaseTest {
return generateRecordViewTestArguments(TABLE_BYTE_TYPE_MATCH,
Tuple.class);
}
+ private List<Arguments> typeCastTestCases() {
+ return generateRecordViewTestArguments(TABLE_NAME_FOR_TYPE_CAST,
Tuple.class);
+ }
+
@Override
TestCaseFactory getFactory(String name) {
return new TestCaseFactory(name) {
@@ -891,7 +1320,7 @@ public class ItRecordBinaryViewApiTest extends
ItRecordViewApiBaseTest {
}
void checkValueTypeDoesNotMatchError(Executable run) {
- String expectedMessage = "Value type does not match [column='ID',
expected=INT64, actual=INT32]";
+ String expectedMessage = "Value type does not match [column='ID',
expected=INT64, actual=DOUBLE]";
if (thin) {
IgniteException ex = (IgniteException)
IgniteTestUtils.assertThrows(IgniteException.class, run, expectedMessage);
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTableViewApiUnifiedBaseTest.java
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTableViewApiUnifiedBaseTest.java
index 2ff57ce1db9..003ef09fbb1 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTableViewApiUnifiedBaseTest.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTableViewApiUnifiedBaseTest.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.table;
import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -37,13 +38,17 @@ import org.apache.ignite.internal.schema.Column;
import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.sql.engine.util.Commons;
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
+import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypes;
+import org.apache.ignite.lang.MarshallerException;
import org.apache.ignite.lang.util.IgniteNameUtils;
+import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.table.Tuple;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.function.Executable;
/**
* Base class for integration testing of key-value/record view public API.
@@ -128,6 +133,16 @@ abstract class ItTableViewApiUnifiedBaseTest extends
ClusterPerClassIntegrationT
}
}
+ static void expectTypeMismatch(Executable executable, String columnName,
ColumnType expected, NativeType actual, boolean thin) {
+ // TODO https://issues.apache.org/jira/browse/IGNITE-21793 Thin client
message must use native type display name.
+ String actualTypeName = thin ? actual.spec().name() :
actual.displayName();
+
+ //noinspection ThrowableNotThrown
+ assertThrows(MarshallerException.class, executable,
+ IgniteStringFormatter.format("Value type does not match
[column='{}', expected={}, actual={}]",
+ columnName.toUpperCase(), expected.name(),
actualTypeName));
+ }
+
private static List<String> getClientAddresses(List<Ignite> nodes) {
return nodes.stream()
.map(ignite -> unwrapIgniteImpl(ignite).clientAddress().port())