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())

Reply via email to