[
https://issues.apache.org/jira/browse/IGNITE-26491?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Pavel Pereslegin reassigned IGNITE-26491:
-----------------------------------------
Assignee: Pavel Pereslegin
> Inconsistent behavior of thin/embedded client when reading tuple value
> ----------------------------------------------------------------------
>
> Key: IGNITE-26491
> URL: https://issues.apache.org/jira/browse/IGNITE-26491
> Project: Ignite
> Issue Type: Bug
> Components: sql ai3
> Reporter: Pavel Pereslegin
> Assignee: Pavel Pereslegin
> Priority: Major
> Labels: ignite-3
>
>
> The behavior of the embedded client is inconsistent with the thin client when
> trying to read a narrower integer type from tuple that contains long value.
> For example, when we have table with bigint column val.
> {code:sql}
> create table test_int (id bigint primary key, val bigint not null)
> {code}
> Getting existing row from view
> {code:java}
> tuple = kvVIew.get(null, key);
> {code}
> the next
> {code:java}
> tuple.intValue("VAL")
> {code}
> in thin client produces
> {noformat}
> ClassCastException: Column with name 'VAL' has type INT64 but INT32 was
> requested
> {noformat}
> but does not produces any errors in embedded client.
> (need to check the same case with record view also)
> Full reproducer
> {code:java}
> sql("create table test_int (id bigint primary key, val bigint)");
> KeyValueView<Tuple, Tuple> kvVIew =
> client.tables().table("test_int").keyValueView();
> KeyValueView<Tuple, Tuple> kvVIewEmbedded =
> CLUSTER.aliveNode().tables().table("test_int").keyValueView();
> Tuple key = Tuple.create().set("id", 1L);
> Tuple val = Tuple.create().set("val", 1L);
> kvVIew.put(null, key, val);
> Tuple thinReturnedVal = kvVIew.get(null, key);
> Tuple embeddedReturnedVal = kvVIewEmbedded.get(null, key);
> assertEquals(1, embeddedReturnedVal.longValue(0));
> assertEquals(1, thinReturnedVal.longValue(0));
> // The following statement should fail, but does not fail.
> assertEquals(1, embeddedReturnedVal.intValue("VAL"));
> // The following statement fails with ClassCastException.
> assertEquals(1, thinReturnedVal.intValue("VAL"));
> {code}
> Exception stacktrace:
> {noformat}
> java.lang.ClassCastException: Column with name 'VAL' has type INT64 but INT32
> was requested
> at
> org.apache.ignite.internal.client.table.MutableTupleBinaryTupleAdapter.binaryTupleIndex(MutableTupleBinaryTupleAdapter.java:468)
> at
> org.apache.ignite.internal.client.table.MutableTupleBinaryTupleAdapter.validateSchemaColumnType(MutableTupleBinaryTupleAdapter.java:477)
> at
> org.apache.ignite.internal.client.table.MutableTupleBinaryTupleAdapter.intValue(MutableTupleBinaryTupleAdapter.java:202)
> {noformat}
> It is necessary to determine which behavior is correct and eliminate
> inconsistencies.
> h2. UPDATE
> We want to make the behavior consistent in all clients:
> # align the behavior with the column upgrade rules
> # relax current validation to support downcast where possible because we
> don't want to break column upgrade scenarios
> For example the following scenario should work:
> * The user upgraded the column in the database from int to long, the user
> application continues to treat the column as an int and this shouldn't break
> application during reading or writing.
> * The user updates the application
> h3. Rules
> For the following type families:
> # Integer types: {*}INT8{*}{*},{*} {*}INT16{*}, {*}INT32{*}, *INT64*
> # Floating‑point types: {*}FLOAT{*}, *DOUBLE*
> Within each family the following implicit conversion rules apply:
> # *Implicit widening conversions* are always permitted.
> # *Implicit narrowing conversions* are permitted only if the source value
> can be exactly represented in the target type without loss of precision or
> overflow.
>
> These rules essentially reflect how we write numeric types in binary format.
> h3. Example
> {code:java}
> Column VAL has type INT16.
>
> Implicit conversion when reading:
>
> // allowed for all non null values
> // for null values throws NullPointerException
> long val = tuple.longValue(“val”);
> int val = tuple.intValue(“val”);
> short val = tuple.shortValue(“val”);
>
> // allowed only if the value is in range [-127, 127],
> // if the value is null throws NullPointerException
> // otherwise throws ClassCastException
> byte val = tuple.byteValue(“val"); {code}
>
> h3. Implementation notes
> We have several Tuple implementations. The behavior should be the same across
> all implementations.
> The following section shows the base implementation classes that must be
> updated to support implicit conversion and consistent type checking.
> h3. TupleImpl
> This class is not aware about schema and is currently used as the main
> implementation of Tuple in the public API, and is also used in the internal
> API when modifying an existing Tuple (for example, in MutableRowTupleAdapter,
> etc.)
> Currently it does not support implicit type conversion and throws a
> ClassCastException if the value type does not match the required one. The
> behavior of this class must conform to the specified implicit type conversion
> rules. We must keep this class {*}schema unaware{*}.
> The “set” method will remain unchanged and is expected not to generate any
> exceptions.
> h3. SqlRowImpl
> This class always reads the boxed value according to the schema, and then
> casts it to the desired type,
> Similar to TupleImpl it throws ClassCastException if the value type doesn’t
> match the required type. And similar to TupleImpl we must support implicit
> conversion for numeric types.
> Below is a pseudo code that can be used for implement implicit conversion
> when reading short and float values (in TupleImpl and SqlRowImpl):
>
> {code:java}
> public short shortValue(String columnName) {
> Number number = (Number) row.get(columnIndexChecked(columnName));
>
> if (obj instanceof Long
> || obj instanceof Integer
> || obj instanceof Byte) {
> long longVal = number.longValue();
> short shortVal = number.shortValue();
>
> if (longVal == shortVal)
> { return shortVal; }
>
> throw new ArithmeticException("short value overflow");
> }
>
> return (short) number;
> }
>
> public float floatValue(String columnName) {
> Object obj = row.get(columnIndexChecked(columnName));
>
> if (obj instanceof Double) {
> Double doubleValue = (Double) obj;
> float floatValue = doubleValue.floatValue();
>
> if (doubleValue == floatValue) {
> return floatValue;
> }
>
> throw new ArithmeticException("float value overflow");
> }
>
> return (float) obj;
> } {code}
>
> h3. MutableTupleBinaryTupleAdapter
> This implementation is used in the thin client and, unlike SqlRow, can read
> primitives without value boxing.
> This class performs strict schema validation before reading value from the
> binary tuple.
> Validation needs to be relaxed and behavior needs to be aligned with the
> described type conversion rules. At the same time, the ability to read
> primitives without value boxing must be preserved.
>
> {code:java}
> public float floatValue(String columnName) {
> if (tuple != null) {
> return tuple.floatValue(columnName);
> }
>
> var binaryTupleIndex = binaryTupleIndex(columnName);
>
> // validate index
> // ...
>
> ColumnType actualType = schemaColumnType(binaryTupleIndex);
>
> if (actualType == ColumnType.FLOAT) {
> return binaryTuple.floatValue(binaryTupleIndex);
> }
>
> if (actualType == ColumnType.DOUBLE) {
> double doubleValue = binaryTuple.doubleValue(binaryTupleIndex);
> float floatValue = (float) doubleValue;
> if (doubleValue == floatValue) {
> return floatValue;
> }
> }
>
> throw new ClassCastException("...");
> } {code}
> *Note:* validation in the serializer must also be relaxed to comply with the
> specified rules (see ClientTupleSerializer.writeTupleRow).
>
> h3. MutableRowTupleAdapter
> Similar to MutableTupleBinaryTupleAdapter this class is aware about schema
> and able to read values without unboxing, but doesn’t perform any validation
> currently.
> We need to align its behavior according to the described rules, similar to
> MutableTupleBinaryTupleAdapter.
>
> *Note*: validation at the marshaller-level must also be relaxed to comply
> with the specified rules (see Column.validate).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)