Copilot commented on code in PR #7365:
URL: https://github.com/apache/ignite-3/pull/7365#discussion_r2685494262
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendShortValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Short.class) {
+ builder.appendShort((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendShort((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ short shortVal = (short) intVal;
+
+ if (intVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ short shortVal = (short) longVal;
+
+ if (longVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendIntValue(BinaryTupleBuilder builder, Object val)
{
+ if (val.getClass() == Integer.class) {
+ builder.appendInt((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendInt((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendInt((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ int intVal = (int) longVal;
+
+ if (longVal == intVal) {
+ builder.appendInt(intVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
Review Comment:
The ClassCastException thrown here lacks a descriptive error message. When
implicit conversion fails (e.g., overflow), this exception will be caught by
the outer catch block and wrapped in a MarshallerException, but it would be
more helpful to provide a clear message indicating why the conversion failed
(e.g., "Value overflow when converting to int").
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendShortValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Short.class) {
+ builder.appendShort((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendShort((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ short shortVal = (short) intVal;
+
+ if (intVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ short shortVal = (short) longVal;
+
+ if (longVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
Review Comment:
The ClassCastException thrown here lacks a descriptive error message. When
implicit conversion fails (e.g., overflow), this exception will be caught by
the outer catch block and wrapped in a MarshallerException, but it would be
more helpful to provide a clear message indicating why the conversion failed
(e.g., "Value overflow when converting to short").
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendShortValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Short.class) {
+ builder.appendShort((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendShort((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ short shortVal = (short) intVal;
+
+ if (intVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ short shortVal = (short) longVal;
+
+ if (longVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendIntValue(BinaryTupleBuilder builder, Object val)
{
+ if (val.getClass() == Integer.class) {
+ builder.appendInt((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendInt((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendInt((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ int intVal = (int) longVal;
+
+ if (longVal == intVal) {
+ builder.appendInt(intVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendLongValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Integer.class) {
+ builder.appendLong((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendLong((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendLong((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ builder.appendLong((long) val);
+ return;
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendFloatValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Float.class) {
+ builder.appendFloat((float) val);
+ return;
+ }
+
+ if (val.getClass() == Double.class) {
+ double doubleVal = (double) val;
+ float floatVal = (float) doubleVal;
+
Review Comment:
Using direct floating-point equality comparison can be problematic. Similar
to other instances, this comparison may not work correctly for NaN values. When
doubleVal is NaN, the comparison will always be false even if floatVal is also
NaN. Consider adding special handling for NaN and Infinity values.
```suggestion
if (Double.isNaN(doubleVal) && Float.isNaN(floatVal)) {
builder.appendFloat(floatVal);
return;
}
```
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendShortValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Short.class) {
+ builder.appendShort((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendShort((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ short shortVal = (short) intVal;
+
+ if (intVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ short shortVal = (short) longVal;
+
+ if (longVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendIntValue(BinaryTupleBuilder builder, Object val)
{
+ if (val.getClass() == Integer.class) {
+ builder.appendInt((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendInt((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendInt((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ int intVal = (int) longVal;
+
+ if (longVal == intVal) {
+ builder.appendInt(intVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendLongValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Integer.class) {
+ builder.appendLong((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendLong((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendLong((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ builder.appendLong((long) val);
+ return;
+ }
+
+ throw new ClassCastException();
Review Comment:
The ClassCastException thrown here lacks a descriptive error message. When
implicit conversion fails, this exception will be caught by the outer catch
block and wrapped in a MarshallerException, but it would be more helpful to
provide a clear message indicating why the conversion failed (e.g.,
"Unsupported type for long conversion").
##########
modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java:
##########
@@ -251,7 +241,7 @@ public float floatValue(int columnIndex) {
IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex);
Review Comment:
Inconsistent null checking: the floatValue(int columnIndex) method has an
explicit IgniteUtils.ensureNotNull call before calling
TupleTypeCastUtils.readFloatValue, but all other numeric value methods
(byteValue, shortValue, intValue, longValue, doubleValue) removed this check
and rely on the null check inside TupleTypeCastUtils. This inconsistency should
be addressed - either remove the explicit check here to be consistent with
other methods, or add it to all methods.
```suggestion
```
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendShortValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Short.class) {
+ builder.appendShort((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendShort((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ short shortVal = (short) intVal;
+
+ if (intVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ short shortVal = (short) longVal;
+
+ if (longVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendIntValue(BinaryTupleBuilder builder, Object val)
{
+ if (val.getClass() == Integer.class) {
+ builder.appendInt((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendInt((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendInt((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ int intVal = (int) longVal;
+
+ if (longVal == intVal) {
+ builder.appendInt(intVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendLongValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Integer.class) {
+ builder.appendLong((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendLong((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendLong((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ builder.appendLong((long) val);
+ return;
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendFloatValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Float.class) {
+ builder.appendFloat((float) val);
+ return;
+ }
+
+ if (val.getClass() == Double.class) {
+ double doubleVal = (double) val;
+ float floatVal = (float) doubleVal;
+
+ if (doubleVal == floatVal) {
+ builder.appendFloat(floatVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
Review Comment:
The ClassCastException thrown here lacks a descriptive error message. When
implicit conversion fails (e.g., precision loss), this exception will be caught
by the outer catch block and wrapped in a MarshallerException, but it would be
more helpful to provide a clear message indicating why the conversion failed
(e.g., "Precision loss when converting double to float").
```suggestion
throw new ClassCastException("Precision loss when converting
double to float: " + doubleVal);
}
throw new ClassCastException(
"Cannot convert value of type " + val.getClass().getName() +
" to float");
```
##########
modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java:
##########
@@ -0,0 +1,547 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.util;
+
+import java.util.EnumSet;
+import java.util.Set;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
+import org.apache.ignite.internal.lang.InternalTuple;
+import org.apache.ignite.sql.ColumnType;
+
+/**
+ * Utility helpers for reading and casting values from {@link InternalTuple}
instances.
+ *
+ * <p>Provides typed readers (byte, short, int, long, float, double) that
validate the actual column
+ * type and perform safe implicit conversions where allowed. Methods throw
{@link ClassCastException}
+ * when the requested type is incompatible with the actual column type.
Narrowing conversions that
+ * would lose information throw {@link ArithmeticException}.</p>
+ */
+public class TupleTypeCastUtils {
+ private static final String TYPE_CAST_ERROR_COLUMN_NAME = "Column with
name '{}' has type {} but {} was requested";
+
+ private static final String TYPE_CAST_ERROR_COLUMN_INDEX = "Column with
index {} has type {} but {} was requested";
+
+ private static final Set<ColumnType> INT_TYPES =
+ EnumSet.of(ColumnType.INT8, ColumnType.INT16, ColumnType.INT32,
ColumnType.INT64);
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
+ */
+ public static float readFloatValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (actualType == ColumnType.FLOAT || actualType == ColumnType.DOUBLE)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnIndex);
+
+ return castToFloat(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
+ */
+ public static float readFloatValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (actualType == ColumnType.FLOAT || actualType == ColumnType.DOUBLE)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnName);
+
+ return castToFloat(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnName);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a double. Accepts DOUBLE
and FLOAT actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code double}
+ * @throws ClassCastException if the actual column type is neither DOUBLE
nor FLOAT
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static double readDoubleValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (actualType == ColumnType.DOUBLE || actualType == ColumnType.FLOAT)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnIndex);
+
+ return binaryTuple.doubleValue(binaryTupleIndex);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a double. Accepts DOUBLE
and FLOAT actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code double}
+ * @throws ClassCastException if the actual column type is neither DOUBLE
nor FLOAT
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static double readDoubleValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (actualType == ColumnType.DOUBLE || actualType == ColumnType.FLOAT)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnName);
+
+ return binaryTuple.doubleValue(binaryTupleIndex);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnName);
+ }
+
+ /**
+ * Casts an integer value from the tuple to {@code byte} performing range
checks.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param valueType actual integer column type
+ * @return the value as a {@code byte}
+ * @throws ArithmeticException if value doesn't fit into a {@code byte}
+ */
+ private static byte castToByte(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType valueType) {
+ switch (valueType) {
+ case INT8:
+ return binaryTuple.byteValue(binaryTupleIndex);
+
+ case INT16: {
+ short value = binaryTuple.shortValue(binaryTupleIndex);
+ byte byteValue = (byte) value;
+
+ if (byteValue == value) {
+ return byteValue;
+ }
+
+ break;
+ }
+ case INT32: {
+ int value = binaryTuple.intValue(binaryTupleIndex);
+ byte byteValue = (byte) value;
+
+ if (byteValue == value) {
+ return byteValue;
+ }
+
+ break;
+ }
+ case INT64: {
+ long value = binaryTuple.longValue(binaryTupleIndex);
+ byte byteValue = (byte) value;
+
+ if (byteValue == value) {
+ return byteValue;
+ }
+
+ break;
+ }
+
+ default:
+ assert false : valueType;
+ }
+
+ throw new ArithmeticException("Byte value overflow");
+ }
+
+ /**
+ * Casts an integer value from the tuple to {@code short} performing range
checks.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param valueType actual integer column type
+ * @return the value as a {@code short}
+ * @throws ArithmeticException if value doesn't fit into a {@code short}
+ */
+ private static short castToShort(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType valueType) {
+ switch (valueType) {
+ case INT16:
+ case INT8:
+ return binaryTuple.shortValue(binaryTupleIndex);
+
+ case INT32: {
+ int value = binaryTuple.intValue(binaryTupleIndex);
+ short shortValue = (short) value;
+
+ if (shortValue == value) {
+ return shortValue;
+ }
+
+ break;
+ }
+ case INT64: {
+ long value = binaryTuple.longValue(binaryTupleIndex);
+ short shortValue = (short) value;
+
+ if (shortValue == value) {
+ return shortValue;
+ }
+
+ break;
+ }
+
+ default:
+ assert false : valueType;
+ }
+
+ throw new ArithmeticException("Short value overflow");
+ }
+
+ /**
+ * Casts an integer value from the tuple to {@code int} performing range
checks.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param valueType actual integer column type
+ * @return the value as an {@code int}
+ * @throws ArithmeticException if value doesn't fit into an {@code int}
+ */
+ private static int castToInt(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType valueType) {
+ switch (valueType) {
+ case INT32:
+ case INT16:
+ case INT8:
+ return binaryTuple.intValue(binaryTupleIndex);
+
+ case INT64: {
+ long value = binaryTuple.longValue(binaryTupleIndex);
+ int intValue = (int) value;
+
+ if (intValue == value) {
+ return intValue;
+ }
+
+ break;
+ }
+
+ default:
+ assert false : valueType;
+ }
+
+ throw new ArithmeticException("Int value overflow");
+ }
+
+ /**
+ * Casts a floating-point value from the tuple to {@code float} performing
precision checks.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual floating-point column type
+ * @return the value as a {@code float}
+ * @throws ArithmeticException if a double value cannot be represented as
a float without precision loss
+ */
+ private static float castToFloat(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType) {
+ if (actualType == ColumnType.FLOAT) {
+ return binaryTuple.floatValue(binaryTupleIndex);
+ }
+
+ double doubleValue = binaryTuple.doubleValue(binaryTupleIndex);
+ float floatValue = (float) doubleValue;
+
Review Comment:
Using direct floating-point equality comparison can be problematic in
general due to precision issues, but in this specific context it's checking if
precision was lost during conversion from double to float. The suppression
comment acknowledges this, but the comparison may not work correctly for all
values. For example, if doubleVal is NaN, the comparison will always be false.
Consider adding a special case for NaN and Infinity values to ensure they're
handled correctly.
```suggestion
// Handle special IEEE-754 values explicitly to avoid incorrect
equality checks.
if (Double.isNaN(doubleValue)) {
return Float.NaN;
}
if (Double.isInfinite(doubleValue)) {
return floatValue;
}
```
##########
modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java:
##########
@@ -0,0 +1,547 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.util;
+
+import java.util.EnumSet;
+import java.util.Set;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
+import org.apache.ignite.internal.lang.InternalTuple;
+import org.apache.ignite.sql.ColumnType;
+
+/**
+ * Utility helpers for reading and casting values from {@link InternalTuple}
instances.
+ *
+ * <p>Provides typed readers (byte, short, int, long, float, double) that
validate the actual column
+ * type and perform safe implicit conversions where allowed. Methods throw
{@link ClassCastException}
+ * when the requested type is incompatible with the actual column type.
Narrowing conversions that
+ * would lose information throw {@link ArithmeticException}.</p>
+ */
+public class TupleTypeCastUtils {
+ private static final String TYPE_CAST_ERROR_COLUMN_NAME = "Column with
name '{}' has type {} but {} was requested";
+
+ private static final String TYPE_CAST_ERROR_COLUMN_INDEX = "Column with
index {} has type {} but {} was requested";
+
+ private static final Set<ColumnType> INT_TYPES =
+ EnumSet.of(ColumnType.INT8, ColumnType.INT16, ColumnType.INT32,
ColumnType.INT64);
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
+ */
+ public static float readFloatValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (actualType == ColumnType.FLOAT || actualType == ColumnType.DOUBLE)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnIndex);
+
+ return castToFloat(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
+ */
+ public static float readFloatValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (actualType == ColumnType.FLOAT || actualType == ColumnType.DOUBLE)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnName);
+
+ return castToFloat(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnName);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a double. Accepts DOUBLE
and FLOAT actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code double}
+ * @throws ClassCastException if the actual column type is neither DOUBLE
nor FLOAT
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static double readDoubleValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (actualType == ColumnType.DOUBLE || actualType == ColumnType.FLOAT)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnIndex);
+
+ return binaryTuple.doubleValue(binaryTupleIndex);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a double. Accepts DOUBLE
and FLOAT actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code double}
+ * @throws ClassCastException if the actual column type is neither DOUBLE
nor FLOAT
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static double readDoubleValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (actualType == ColumnType.DOUBLE || actualType == ColumnType.FLOAT)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnName);
+
+ return binaryTuple.doubleValue(binaryTupleIndex);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnName);
Review Comment:
The error message format is inconsistent with the readDoubleValue method
error. This exception message says ColumnType.FLOAT but should say
ColumnType.DOUBLE since this is the readDoubleValue method.
##########
modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java:
##########
@@ -459,4 +483,89 @@ private <T> T valueNotNull(String columnName) {
return value;
}
+
+ private static byte castToByte(Number number) {
+ if (number instanceof Long
+ || number instanceof Integer
+ || number instanceof Short) {
+ long longVal = number.longValue();
+ byte byteVal = number.byteValue();
+
+ if (longVal == byteVal) {
+ return byteVal;
+ }
+
+ throw new ArithmeticException("Byte value overflow");
+ }
+
+ return (byte) number;
+ }
+
+ private static short castToShort(Number number) {
+ if (number instanceof Long
+ || number instanceof Integer
+ || number instanceof Byte) {
+ long longVal = number.longValue();
+ short shortVal = number.shortValue();
+
+ if (longVal == shortVal) {
+ return shortVal;
+ }
+
+ throw new ArithmeticException("Short value overflow");
+ }
+
+ return (short) number;
+ }
+
+ private static int castToInt(Number number) {
+ if (number instanceof Long
+ || number instanceof Short
+ || number instanceof Byte) {
+ long longVal = number.longValue();
+ int intVal = number.intValue();
+
+ if (longVal == intVal) {
+ return intVal;
+ }
+
+ throw new ArithmeticException("Int value overflow");
+ }
+
+ return (int) number;
+ }
+
+ private static long castToLong(Number number) {
+ if (number instanceof Integer
+ || number instanceof Short
+ || number instanceof Byte) {
+ return number.longValue();
+ }
+
+ return (long) number;
+ }
+
+ private static float castToFloat(Number number) {
+ if (number instanceof Double) {
+ double doubleVal = number.doubleValue();
+ float floatVal = number.floatValue();
+
Review Comment:
Using direct floating-point equality comparison can be problematic. Similar
to other instances, this comparison may not work correctly for NaN values. When
doubleVal is NaN, the comparison will always be false even if floatVal is also
NaN. Consider adding special handling for NaN and Infinity values.
```suggestion
// Handle NaN explicitly, because NaN != NaN in floating-point
equality.
if (Double.isNaN(doubleVal) && Float.isNaN(floatVal)) {
return floatVal;
}
```
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java:
##########
@@ -436,24 +464,109 @@ public String toString() {
return S.tupleToString(this);
}
- private Object getValueNotNull(int columnIndex) {
+ private <T> T getValueNotNull(int columnIndex) {
Object value = row.get(columnIndex);
if (value == null) {
throw new
NullPointerException(format(IgniteUtils.NULL_TO_PRIMITIVE_ERROR_MESSAGE,
columnIndex));
}
- return value;
+ return (T) value;
}
- private Object getValueNotNull(String columnName) {
+ private <T> T getValueNotNull(String columnName) {
Object value = row.get(columnIndexChecked(columnName));
if (value == null) {
throw new
NullPointerException(format(IgniteUtils.NULL_TO_PRIMITIVE_NAMED_ERROR_MESSAGE,
columnName));
}
- return value;
+ return (T) value;
+ }
+
+ private static byte castToByte(Number number) {
+ if (number instanceof Long
+ || number instanceof Integer
+ || number instanceof Short) {
+ long longVal = number.longValue();
+ byte byteVal = number.byteValue();
+
+ if (longVal == byteVal) {
+ return byteVal;
+ }
+
+ throw new ArithmeticException("Byte value overflow");
+ }
+
+ return (byte) number;
+ }
+
+ private static short castToShort(Number number) {
+ if (number instanceof Long
+ || number instanceof Integer
+ || number instanceof Byte) {
+ long longVal = number.longValue();
+ short shortVal = number.shortValue();
+
+ if (longVal == shortVal) {
+ return shortVal;
+ }
+
+ throw new ArithmeticException("Short value overflow");
+ }
+
+ return (short) number;
+ }
+
+ private static int castToInt(Number number) {
+ if (number instanceof Long
+ || number instanceof Short
+ || number instanceof Byte) {
+ long longVal = number.longValue();
+ int intVal = number.intValue();
+
+ if (longVal == intVal) {
+ return intVal;
+ }
+
+ throw new ArithmeticException("Int value overflow");
+ }
+
+ return (int) number;
+ }
+
+ private static long castToLong(Number number) {
+ if (number instanceof Integer
+ || number instanceof Short
+ || number instanceof Byte) {
+ return number.longValue();
+ }
+
+ return (long) number;
+ }
+
+ private static float castToFloat(Number number) {
+ if (number instanceof Double) {
+ double doubleVal = number.doubleValue();
+ float floatVal = number.floatValue();
+
Review Comment:
Using direct floating-point equality comparison can be problematic. Similar
to the issue in TupleTypeCastUtils, this comparison may not work correctly for
NaN values. When doubleVal is NaN, the comparison will always be false even if
floatVal is also NaN (since NaN != NaN). Consider adding special handling for
NaN and Infinity values.
```suggestion
// Handle NaN explicitly: NaN should propagate as NaN.
if (Double.isNaN(doubleVal) && Float.isNaN(floatVal)) {
return floatVal;
}
// Handle infinities explicitly to avoid relying on == for
special values.
if (Double.isInfinite(doubleVal) ||
Float.isInfinite(floatVal)) {
if (Double.isInfinite(doubleVal)
&& Float.isInfinite(floatVal)
&& Math.signum(doubleVal) ==
Math.signum(floatVal)) {
return floatVal;
}
throw new ArithmeticException("Float value overflow");
}
```
##########
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java:
##########
@@ -216,9 +216,13 @@ private int columnIndexChecked(String columnName) {
/** {@inheritDoc} */
@Override
public <T> T valueOrDefault(String columnName, T defaultValue) {
- T ret = (T) row.get(columnIndexChecked(columnName));
+ int columnIndex = columnIndex(columnName);
- return ret != null ? ret : defaultValue;
+ if (columnIndex == -1) {
+ return defaultValue;
+ }
+
+ return (T) row.get(columnIndex);
Review Comment:
The valueOrDefault method no longer checks if the value is null before
returning it. The previous implementation would return the defaultValue if the
retrieved value was null, but now it returns the value directly from
row.get(columnIndex) which could be null. This changes the semantics of
valueOrDefault and may break existing code that relies on null values being
replaced with the default.
```suggestion
T val = (T) row.get(columnIndex);
return val != null ? val : defaultValue;
```
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendShortValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Short.class) {
+ builder.appendShort((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendShort((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ short shortVal = (short) intVal;
+
+ if (intVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ short shortVal = (short) longVal;
+
+ if (longVal == shortVal) {
+ builder.appendShort(shortVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendIntValue(BinaryTupleBuilder builder, Object val)
{
+ if (val.getClass() == Integer.class) {
+ builder.appendInt((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendInt((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendInt((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ int intVal = (int) longVal;
+
+ if (longVal == intVal) {
+ builder.appendInt(intVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendLongValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Integer.class) {
+ builder.appendLong((int) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ builder.appendLong((short) val);
+ return;
+ }
+
+ if (val.getClass() == Byte.class) {
+ builder.appendLong((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Long.class) {
+ builder.appendLong((long) val);
+ return;
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendFloatValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Float.class) {
+ builder.appendFloat((float) val);
+ return;
+ }
+
+ if (val.getClass() == Double.class) {
+ double doubleVal = (double) val;
+ float floatVal = (float) doubleVal;
+
+ if (doubleVal == floatVal) {
+ builder.appendFloat(floatVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
+ }
+
+ private static void appendDoubleValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Double.class) {
+ builder.appendDouble((double) val);
+ return;
+ }
+
+ if (val.getClass() == Float.class) {
+ builder.appendDouble((float) val);
+ return;
+ }
+
+ throw new ClassCastException();
Review Comment:
The ClassCastException thrown here lacks a descriptive error message. When
implicit conversion fails, this exception will be caught by the outer catch
block and wrapped in a MarshallerException, but it would be more helpful to
provide a clear message indicating why the conversion failed (e.g.,
"Unsupported type for double conversion").
##########
modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientBinaryTupleUtils.java:
##########
@@ -288,6 +288,165 @@ public static void appendValue(BinaryTupleBuilder
builder, ColumnType type, Stri
}
}
+ private static void appendByteValue(BinaryTupleBuilder builder, Object
val) {
+ if (val.getClass() == Byte.class) {
+ builder.appendByte((byte) val);
+ return;
+ }
+
+ if (val.getClass() == Short.class) {
+ short shortVal = (short) val;
+ byte byteVal = (byte) shortVal;
+
+ if (shortVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Integer.class) {
+ int intVal = (int) val;
+ byte byteVal = (byte) intVal;
+
+ if (intVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ if (val.getClass() == Long.class) {
+ long longVal = (long) val;
+ byte byteVal = (byte) longVal;
+
+ if (longVal == byteVal) {
+ builder.appendByte(byteVal);
+ return;
+ }
+ }
+
+ throw new ClassCastException();
Review Comment:
The ClassCastException thrown here lacks a descriptive error message. When
implicit conversion fails (e.g., overflow), this exception will be caught by
the outer catch block and wrapped in a MarshallerException, but it would be
more helpful to provide a clear message indicating why the conversion failed
(e.g., "Value overflow when converting to byte").
##########
modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java:
##########
@@ -0,0 +1,547 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.util;
+
+import java.util.EnumSet;
+import java.util.Set;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
+import org.apache.ignite.internal.lang.InternalTuple;
+import org.apache.ignite.sql.ColumnType;
+
+/**
+ * Utility helpers for reading and casting values from {@link InternalTuple}
instances.
+ *
+ * <p>Provides typed readers (byte, short, int, long, float, double) that
validate the actual column
+ * type and perform safe implicit conversions where allowed. Methods throw
{@link ClassCastException}
+ * when the requested type is incompatible with the actual column type.
Narrowing conversions that
+ * would lose information throw {@link ArithmeticException}.</p>
+ */
+public class TupleTypeCastUtils {
+ private static final String TYPE_CAST_ERROR_COLUMN_NAME = "Column with
name '{}' has type {} but {} was requested";
+
+ private static final String TYPE_CAST_ERROR_COLUMN_INDEX = "Column with
index {} has type {} but {} was requested";
+
+ private static final Set<ColumnType> INT_TYPES =
+ EnumSet.of(ColumnType.INT8, ColumnType.INT16, ColumnType.INT32,
ColumnType.INT64);
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
Review Comment:
The comment states this method "Accepts FLOAT and DOUBLE actual types", but
line 217 only checks for ColumnType.FLOAT and ColumnType.DOUBLE. However,
according to the logic, when FLOAT and DOUBLE are both allowed, the method
should read a float value directly if the column is FLOAT. The comment is
accurate but could be clearer about the conversion behavior.
```suggestion
* Reads a value from the tuple and returns it as a {@code float}.
Accepts {@link ColumnType#FLOAT}
* and {@link ColumnType#DOUBLE} actual types.
*
* <p>If the actual type is {@link ColumnType#FLOAT}, the value is read
directly as a float. If the
* actual type is {@link ColumnType#DOUBLE}, the value is read as a
double and then converted to
* float with a range check.</p>
*
* @param binaryTuple the tuple instance
* @param binaryTupleIndex index of the column in the tuple
* @param actualType actual column type stored in the tuple
* @param columnIndex column index used for error reporting
* @return the column value as a {@code float}
* @throws ClassCastException if the actual column type is neither
{@code FLOAT} nor {@code DOUBLE}
* @throws NullPointerException if {@code binaryTuple} is null
* @throws ArithmeticException if a {@code DOUBLE} value cannot be
represented as {@code float}
* without precision loss
```
##########
modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java:
##########
@@ -0,0 +1,547 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.util;
+
+import java.util.EnumSet;
+import java.util.Set;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
+import org.apache.ignite.internal.lang.InternalTuple;
+import org.apache.ignite.sql.ColumnType;
+
+/**
+ * Utility helpers for reading and casting values from {@link InternalTuple}
instances.
+ *
+ * <p>Provides typed readers (byte, short, int, long, float, double) that
validate the actual column
+ * type and perform safe implicit conversions where allowed. Methods throw
{@link ClassCastException}
+ * when the requested type is incompatible with the actual column type.
Narrowing conversions that
+ * would lose information throw {@link ArithmeticException}.</p>
+ */
+public class TupleTypeCastUtils {
+ private static final String TYPE_CAST_ERROR_COLUMN_NAME = "Column with
name '{}' has type {} but {} was requested";
+
+ private static final String TYPE_CAST_ERROR_COLUMN_INDEX = "Column with
index {} has type {} but {} was requested";
+
+ private static final Set<ColumnType> INT_TYPES =
+ EnumSet.of(ColumnType.INT8, ColumnType.INT16, ColumnType.INT32,
ColumnType.INT64);
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a byte if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code byte}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null (enforced
by {@code IgniteUtils.ensureNotNull})
+ * @throws ArithmeticException if the value cannot be represented as
{@code byte} without overflow
+ */
+ public static byte readByteValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT8, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToByte(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a short if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code short}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code short} without overflow
+ */
+ public static short readShortValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT16, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToShort(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to an int if possible.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as an {@code int}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if the value cannot be represented as
{@code int} without overflow
+ */
+ public static int readIntValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT32, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return castToInt(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnIndex);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a long. Only integer
column types are allowed.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code long}
+ * @throws ClassCastException if the actual column type is not an integer
type
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static long readLongValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (!integerType(actualType)) {
+ throwClassCastException(ColumnType.INT64, actualType, columnName);
+ }
+
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName);
+
+ return binaryTuple.longValue(binaryTupleIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
+ */
+ public static float readFloatValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (actualType == ColumnType.FLOAT || actualType == ColumnType.DOUBLE)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnIndex);
+
+ return castToFloat(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex);
+ }
+
+ /**
+ * Reads a value from the tuple and converts it to a float if possible.
Accepts FLOAT and DOUBLE actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnName column name used for error reporting
+ * @return the column value as a {@code float}
+ * @throws ClassCastException if the actual column type is neither FLOAT
nor DOUBLE
+ * @throws NullPointerException if {@code binaryTuple} is null
+ * @throws ArithmeticException if a double value cannot be represented as
float without precision loss
+ */
+ public static float readFloatValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, String columnName) {
+ if (actualType == ColumnType.FLOAT || actualType == ColumnType.DOUBLE)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnName);
+
+ return castToFloat(binaryTuple, binaryTupleIndex, actualType);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnName);
+ }
+
+ /**
+ * Reads a value from the tuple and returns it as a double. Accepts DOUBLE
and FLOAT actual types.
+ *
+ * @param binaryTuple the tuple instance
+ * @param binaryTupleIndex index of the column in the tuple
+ * @param actualType actual column type stored in the tuple
+ * @param columnIndex column index used for error reporting
+ * @return the column value as a {@code double}
+ * @throws ClassCastException if the actual column type is neither DOUBLE
nor FLOAT
+ * @throws NullPointerException if {@code binaryTuple} is null
+ */
+ public static double readDoubleValue(InternalTuple binaryTuple, int
binaryTupleIndex, ColumnType actualType, int columnIndex) {
+ if (actualType == ColumnType.DOUBLE || actualType == ColumnType.FLOAT)
{
+ IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex,
columnIndex);
+
+ return binaryTuple.doubleValue(binaryTupleIndex);
+ }
+
+ throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex);
Review Comment:
The error message format is inconsistent with the readDoubleValue method
error. On line 278, the exception message says ColumnType.FLOAT but should say
ColumnType.DOUBLE since this is the readDoubleValue method. Similarly, line 299
has the same issue.
##########
modules/api/src/testFixtures/java/org/apache/ignite/table/AbstractImmutableTupleTest.java:
##########
@@ -334,6 +340,307 @@ public void nullPointerWhenReadingNullByNameAsPrimitive(
assertEquals(String.format(NULL_TO_PRIMITIVE_NAMED_ERROR_MESSAGE,
"VAL"), err.getMessage());
}
+ @Test
+ void castByte() {
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT8, "INT8",
Byte.MAX_VALUE);
+
+ assertThat(tuple.byteValue("INT8"), is(Byte.MAX_VALUE));
+ assertThat(tuple.shortValue("INT8"), is((short) Byte.MAX_VALUE));
+ assertThat(tuple.intValue("INT8"), is((int) Byte.MAX_VALUE));
+ assertThat(tuple.longValue("INT8"), is((long) Byte.MAX_VALUE));
+
+ assertThat(tuple.byteValue(0), is(Byte.MAX_VALUE));
+ assertThat(tuple.shortValue(0), is((short) Byte.MAX_VALUE));
+ assertThat(tuple.intValue(0), is((int) Byte.MAX_VALUE));
+ assertThat(tuple.longValue(0), is((long) Byte.MAX_VALUE));
+ }
+
+ @Test
+ void castShort() {
+ // The field value is within the byte range
+ {
+ String columnName = "INT16";
+ short value = Byte.MAX_VALUE;
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT16,
columnName, value);
+
+ assertThat(tuple.byteValue(columnName), is((byte) value));
+ assertThat(tuple.shortValue(columnName), is(value));
+ assertThat(tuple.intValue(columnName), is((int) value));
+ assertThat(tuple.longValue(columnName), is((long) value));
+
+ assertThat(tuple.byteValue(0), is((byte) value));
+ assertThat(tuple.shortValue(0), is(value));
+ assertThat(tuple.intValue(0), is((int) value));
+ assertThat(tuple.longValue(0), is((long) value));
+ }
+
+ // The field value is out of the byte range.
+ {
+ String columnName = "INT16";
+ short value = Byte.MAX_VALUE + 1;
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT16,
columnName, value);
+
+ ArithmeticException ex0 = assertThrows(ArithmeticException.class,
() -> tuple.byteValue(columnName));
+ assertThat(ex0.getMessage(), equalTo("Byte value overflow"));
+
+ assertThat(tuple.shortValue(columnName), is(value));
+ assertThat(tuple.intValue(columnName), is((int) value));
+ assertThat(tuple.longValue(columnName), is((long) value));
+
+ ArithmeticException ex1 = assertThrows(ArithmeticException.class,
() -> tuple.byteValue(0));
+ assertThat(ex1.getMessage(), equalTo("Byte value overflow"));
+
+ assertThat(tuple.shortValue(0), is(value));
+ assertThat(tuple.intValue(0), is((int) value));
+ assertThat(tuple.longValue(0), is((long) value));
+ }
+ }
+
+ @Test
+ void castInt() {
+ {
+ int value = Byte.MAX_VALUE;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT32,
columnName, value);
+
+ assertThat(tuple.byteValue(columnName), is((byte) value));
+ assertThat(tuple.shortValue(columnName), is((short) value));
+ assertThat(tuple.intValue(columnName), is(value));
+ assertThat(tuple.longValue(columnName), is((long) value));
+
+ assertThat(tuple.byteValue(0), is((byte) value));
+ assertThat(tuple.shortValue(0), is((short) value));
+ assertThat(tuple.intValue(0), is(value));
+ assertThat(tuple.longValue(0), is((long) value));
+ }
+
+ {
+ int value = Byte.MAX_VALUE + 1;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT32,
columnName, value);
+
+ ArithmeticException ex0 = assertThrows(ArithmeticException.class,
() -> tuple.byteValue(columnName));
+ assertThat(ex0.getMessage(), equalTo("Byte value overflow"));
+
+ assertThat(tuple.shortValue(columnName), is((short) value));
+ assertThat(tuple.intValue(columnName), is(value));
+ assertThat(tuple.longValue(columnName), is((long) value));
+
+ ArithmeticException ex1 = assertThrows(ArithmeticException.class,
() -> tuple.byteValue(0));
+ assertThat(ex1.getMessage(), equalTo("Byte value overflow"));
+
+ assertThat(tuple.shortValue(0), is((short) value));
+ assertThat(tuple.intValue(0), is(value));
+ assertThat(tuple.longValue(0), is((long) value));
+ }
+
+ {
+ int value = Short.MAX_VALUE + 1;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT32,
columnName, value);
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.byteValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Byte value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.shortValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Short value overflow"));
+ }
+
+ assertThat(tuple.intValue(columnName), is(value));
+ assertThat(tuple.longValue(columnName), is((long) value));
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.byteValue(0));
+ assertThat(ex.getMessage(), equalTo("Byte value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.shortValue(0));
+ assertThat(ex.getMessage(), equalTo("Short value overflow"));
+ }
+
+ assertThat(tuple.intValue(0), is(value));
+ assertThat(tuple.longValue(0), is((long) value));
+ }
+ }
+
+ @Test
+ void castLong() {
+ {
+ long value = Byte.MAX_VALUE;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT64,
columnName, value);
+
+ assertThat(tuple.byteValue(columnName), is((byte) value));
+ assertThat(tuple.shortValue(columnName), is((short) value));
+ assertThat(tuple.intValue(columnName), is((int) value));
+ assertThat(tuple.longValue(columnName), is(value));
+
+ assertThat(tuple.byteValue(0), is((byte) value));
+ assertThat(tuple.shortValue(0), is((short) value));
+ assertThat(tuple.intValue(0), is((int) value));
+ assertThat(tuple.longValue(0), is(value));
+ }
+
+ {
+ long value = Byte.MAX_VALUE + 1;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT64,
columnName, value);
+
+ ArithmeticException ex0 = assertThrows(ArithmeticException.class,
() -> tuple.byteValue(columnName));
+ assertThat(ex0.getMessage(), equalTo("Byte value overflow"));
+
+ assertThat(tuple.shortValue(columnName), is((short) value));
+ assertThat(tuple.intValue(columnName), is((int) value));
+ assertThat(tuple.longValue(columnName), is(value));
+
+ ArithmeticException ex1 = assertThrows(ArithmeticException.class,
() -> tuple.byteValue(0));
+ assertThat(ex1.getMessage(), equalTo("Byte value overflow"));
+
+ assertThat(tuple.shortValue(0), is((short) value));
+ assertThat(tuple.intValue(0), is((int) value));
+ assertThat(tuple.longValue(0), is(value));
+ }
+
+ {
+ long value = Short.MAX_VALUE + 1;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT64,
columnName, value);
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.byteValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Byte value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.shortValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Short value overflow"));
+ }
+
+ assertThat(tuple.intValue(columnName), is((int) value));
+ assertThat(tuple.longValue(columnName), is(value));
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.byteValue(0));
+ assertThat(ex.getMessage(), equalTo("Byte value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.shortValue(0));
+ assertThat(ex.getMessage(), equalTo("Short value overflow"));
+ }
+
+ assertThat(tuple.intValue(0), is((int) value));
+ assertThat(tuple.longValue(0), is(value));
+ }
+
+ {
+ long value = Integer.MAX_VALUE + 1L;
+ String columnName = "VALUE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.INT64,
columnName, value);
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.byteValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Byte value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.shortValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Short value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.intValue(columnName));
+ assertThat(ex.getMessage(), equalTo("Int value overflow"));
+ }
+
+ assertThat(tuple.longValue(columnName), is(value));
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.byteValue(0));
+ assertThat(ex.getMessage(), equalTo("Byte value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.shortValue(0));
+ assertThat(ex.getMessage(), equalTo("Short value overflow"));
+ }
+
+ {
+ ArithmeticException ex =
assertThrows(ArithmeticException.class, () -> tuple.intValue(0));
+ assertThat(ex.getMessage(), equalTo("Int value overflow"));
+ }
+
+ assertThat(tuple.longValue(0), is(value));
+ }
+ }
+
+ @Test
+ void castFloat() {
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.FLOAT, "FLOAT",
Float.MAX_VALUE);
+
+ assertThat(tuple.floatValue("FLOAT"), is(Float.MAX_VALUE));
+ assertThat(tuple.doubleValue("FLOAT"), is((double) Float.MAX_VALUE));
+
+ assertThat(tuple.floatValue(0), is(Float.MAX_VALUE));
+ assertThat(tuple.doubleValue(0), is((double) Float.MAX_VALUE));
+ }
+
+ @Test
+ void castDouble() {
+ // The field value can be represented as float.
+ {
+ double value = Float.MAX_VALUE;
+ String columnName = "DOUBLE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.DOUBLE,
columnName, value);
+
+ assertThat(tuple.floatValue(columnName), is((float) value));
+ assertThat(tuple.floatValue(0), is((float) value));
+
+ assertThat(tuple.doubleValue(columnName), is(value));
+ assertThat(tuple.doubleValue(0), is(value));
+ }
+
+ // The field value cannot be represented as float.
+ {
+ double value = Double.MAX_VALUE;
+ String columnName = "DOUBLE";
+ Tuple tuple = createTupleOfSingleColumn(ColumnType.DOUBLE,
columnName, value);
+
+ ArithmeticException ex0 = assertThrows(ArithmeticException.class,
() -> tuple.floatValue(columnName));
+ assertThat(ex0.getMessage(), equalTo("Float value overflow"));
+
+ ArithmeticException ex1 = assertThrows(ArithmeticException.class,
() -> tuple.floatValue(0));
+ assertThat(ex1.getMessage(), equalTo("Float value overflow"));
+
+ assertThat(tuple.doubleValue(columnName), is(value));
+ assertThat(tuple.doubleValue(0), is(value));
+ }
+ }
Review Comment:
The test coverage for floating-point conversions does not include edge cases
like NaN and Infinity values. Consider adding test cases for these special
floating-point values to ensure they are handled correctly during conversions
between float and double types.
##########
modules/schema/src/main/java/org/apache/ignite/internal/schema/Column.java:
##########
@@ -213,6 +214,8 @@ public void validate(@Nullable Object val) {
}
}
+ // assert false;
+
Review Comment:
There's a commented-out assertion (assert false;) that should either be
removed or uncommented with proper context. Leaving commented code without
explanation is not a good practice.
```suggestion
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]