This is an automated email from the ASF dual-hosted git repository. ppa pushed a commit to branch ignite-26491 in repository https://gitbox.apache.org/repos/asf/ignite-3.git
commit 9d4a8006500aeed8142272ff7b1d0cd5456aa518 Author: Pavel Pereslegin <[email protected]> AuthorDate: Thu Jan 8 13:00:58 2026 +0300 IGNITE-26491 Implicit conversion (wip) --- .../java/org/apache/ignite/table/TupleImpl.java | 133 +++++- .../ignite/table/AbstractImmutableTupleTest.java | 429 +++++++++++++++++ .../table/MutableTupleBinaryTupleAdapter.java | 133 +++--- .../ignite/internal/util/TupleTypeCastUtils.java | 510 +++++++++++++++++++++ .../internal/table/AbstractRowTupleAdapter.java | 86 ++-- 5 files changed, 1179 insertions(+), 112 deletions(-) diff --git a/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java b/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java index d197f69f27a..814d0d5215d 100644 --- a/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java +++ b/modules/api/src/main/java/org/apache/ignite/table/TupleImpl.java @@ -196,73 +196,97 @@ class TupleImpl implements Tuple, Serializable { /** {@inheritDoc} */ @Override public byte byteValue(String columnName) { - return valueNotNull(columnName); + Number number = valueNotNull(columnName); + + return castToByte(number); } /** {@inheritDoc} */ @Override public byte byteValue(int columnIndex) { - return valueNotNull(columnIndex); + Number number = valueNotNull(columnIndex); + + return castToByte(number); } /** {@inheritDoc} */ @Override public short shortValue(String columnName) { - return valueNotNull(columnName); + Number number = valueNotNull(columnName); + + return castToShort(number); } /** {@inheritDoc} */ @Override public short shortValue(int columnIndex) { - return valueNotNull(columnIndex); + Number number = valueNotNull(columnIndex); + + return castToShort(number); } /** {@inheritDoc} */ @Override public int intValue(String columnName) { - return valueNotNull(columnName); + Number number = valueNotNull(columnName); + + return castToInt(number); } /** {@inheritDoc} */ @Override public int intValue(int columnIndex) { - return valueNotNull(columnIndex); + Number number = valueNotNull(columnIndex); + + return castToInt(number); } /** {@inheritDoc} */ @Override public long longValue(String columnName) { - return valueNotNull(columnName); + Number number = valueNotNull(columnName); + + return castToLong(number); } /** {@inheritDoc} */ @Override public long longValue(int columnIndex) { - return valueNotNull(columnIndex); + Number number = valueNotNull(columnIndex); + + return castToLong(number); } /** {@inheritDoc} */ @Override public float floatValue(String columnName) { - return valueNotNull(columnName); + Number number = valueNotNull(columnName); + + return castToFloat(number); } /** {@inheritDoc} */ @Override public float floatValue(int columnIndex) { - return valueNotNull(columnIndex); + Number number = valueNotNull(columnIndex); + + return castToFloat(number); } /** {@inheritDoc} */ @Override public double doubleValue(String columnName) { - return valueNotNull(columnName); + Number number = valueNotNull(columnName); + + return castToDouble(number); } /** {@inheritDoc} */ @Override public double doubleValue(int columnIndex) { - return valueNotNull(columnIndex); + Number number = valueNotNull(columnIndex); + + return castToDouble(number); } /** {@inheritDoc} */ @@ -459,4 +483,89 @@ class TupleImpl implements Tuple, Serializable { 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(); + + //noinspection FloatingPointEquality + if (doubleVal == floatVal) { + return floatVal; + } + + throw new ArithmeticException("Float value overflow"); + } + + return (float) number; + } + + private static double castToDouble(Number number) { + if (number instanceof Float) { + return number.doubleValue(); + } + + return (double) number; + } } diff --git a/modules/api/src/testFixtures/java/org/apache/ignite/table/AbstractImmutableTupleTest.java b/modules/api/src/testFixtures/java/org/apache/ignite/table/AbstractImmutableTupleTest.java index a3fb7e0e4eb..919e5c52368 100644 --- a/modules/api/src/testFixtures/java/org/apache/ignite/table/AbstractImmutableTupleTest.java +++ b/modules/api/src/testFixtures/java/org/apache/ignite/table/AbstractImmutableTupleTest.java @@ -18,6 +18,9 @@ package org.apache.ignite.table; import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -38,7 +41,10 @@ import java.time.Year; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.temporal.Temporal; +import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; +import java.util.Objects; import java.util.Random; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -334,6 +340,309 @@ public abstract class AbstractImmutableTupleTest { 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); + + tuple.set(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)); + } + } + + @ParameterizedTest(name = "{0} -> {1}") + @MethodSource("allTypesUnsupportedConversionArgs") + void allTypesUnsupportedConversion(ColumnType from, ColumnType to) { + Object value = generateMaxValue(from); + String columnName = "VALUE"; + Tuple tuple = createTupleOfSingleColumn(from, columnName, value); + + assertThrows(ClassCastException.class, () -> readValue(tuple, to, columnName)); + } + + @ParameterizedTest(name = "{0} -> {1}") + @MethodSource("allTypesDowncastOverflowArgs") + void allTypesDowncastOverflow(ColumnType from, ColumnType to) { + Object value = generateMaxValue(from); + String columnName = "VALUE"; + Tuple tuple = createTupleOfSingleColumn(from, columnName, value); + + assertThrows(ArithmeticException.class, () -> readValue(tuple, to, columnName)); + } + /** * Adds sample values for columns of default schema: id (long), simpleName (string), "QuotedName" (string), noValue (null). * @@ -475,6 +784,25 @@ public abstract class AbstractImmutableTupleTest { } } + private static Object generateMaxValue(ColumnType type) { + switch (type) { + case INT8: + return Byte.MAX_VALUE; + case INT16: + return Short.MAX_VALUE; + case INT32: + return Integer.MAX_VALUE; + case INT64: + return Long.MAX_VALUE; + case FLOAT: + return Float.MAX_VALUE; + case DOUBLE: + return Double.MAX_VALUE; + default: + return Objects.requireNonNull(generateValue(ThreadLocalRandom.current(), type)); + } + } + private static Instant generateInstant(Random rnd) { long minTs = LocalDateTime.of(LocalDate.of(1, 1, 1), LocalTime.MIN) .minusSeconds(ZoneOffset.MIN.getTotalSeconds()).toInstant(ZoneOffset.UTC).toEpochMilli(); @@ -507,4 +835,105 @@ public abstract class AbstractImmutableTupleTest { Arguments.of(ColumnType.DOUBLE, (BiConsumer<Tuple, String>) Tuple::doubleValue) ); } + + private static Object readValue(Tuple tuple, ColumnType type, String column) { + switch (type) { + case TIME: + return tuple.timeValue(column); + case TIMESTAMP: + return tuple.timestampValue(column); + case DATE: + return tuple.dateValue(column); + case DATETIME: + return tuple.datetimeValue(column); + case INT8: + return tuple.byteValue(column); + case INT16: + return tuple.shortValue(column); + case INT32: + return tuple.intValue(column); + case INT64: + return tuple.longValue(column); + case FLOAT: + return tuple.floatValue(column); + case DOUBLE: + return tuple.doubleValue(column); + case UUID: + return tuple.uuidValue(column); + case STRING: + return tuple.stringValue(column); + case BOOLEAN: + return tuple.booleanValue(column); + case DECIMAL: + return tuple.decimalValue(column); + case BYTE_ARRAY: + return tuple.bytesValue(column); + default: + throw new UnsupportedOperationException("Unexpected type: " + type); + } + } + + private static List<Arguments> allTypesUnsupportedConversionArgs() { + EnumSet<ColumnType> allTypes = EnumSet.complementOf( + EnumSet.of(ColumnType.NULL, ColumnType.STRUCT, ColumnType.PERIOD, ColumnType.DURATION)); + List<Arguments> arguments = new ArrayList<>(); + + for (ColumnType from : allTypes) { + for (ColumnType to : allTypes) { + if (from == to || isSupportedUpcast(from, to) || isSupportedDowncast(from, to)) { + continue; + } + + arguments.add(Arguments.of(from, to)); + } + } + + return arguments; + } + + private static List<Arguments> allTypesDowncastOverflowArgs() { + EnumSet<ColumnType> allTypes = EnumSet.complementOf( + EnumSet.of(ColumnType.NULL, ColumnType.STRUCT, ColumnType.PERIOD, ColumnType.DURATION)); + List<Arguments> arguments = new ArrayList<>(); + + for (ColumnType from : allTypes) { + for (ColumnType to : allTypes) { + if (isSupportedDowncast(from, to)) { + arguments.add(Arguments.of(from, to)); + } + } + } + + return arguments; + } + + private static boolean isSupportedUpcast(ColumnType source, ColumnType target) { + switch (source) { + case INT8: + return target == ColumnType.INT16 || target == ColumnType.INT32 || target == ColumnType.INT64; + case INT16: + return target == ColumnType.INT32 || target == ColumnType.INT64; + case INT32: + return target == ColumnType.INT64; + case FLOAT: + return target == ColumnType.DOUBLE; + default: + return false; + } + } + + private static boolean isSupportedDowncast(ColumnType source, ColumnType target) { + switch (source) { + case INT64: + return target == ColumnType.INT8 || target == ColumnType.INT16 || target == ColumnType.INT32; + case INT32: + return target == ColumnType.INT8 || target == ColumnType.INT16; + case INT16: + return target == ColumnType.INT8; + case DOUBLE: + return target == ColumnType.FLOAT; + default: + return false; + } + } } diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/table/MutableTupleBinaryTupleAdapter.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/table/MutableTupleBinaryTupleAdapter.java index a9804e793a3..790c973926e 100644 --- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/table/MutableTupleBinaryTupleAdapter.java +++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/table/MutableTupleBinaryTupleAdapter.java @@ -17,6 +17,13 @@ package org.apache.ignite.internal.client.table; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.readByteValue; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.readDoubleValue; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.readFloatValue; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.readIntValue; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.readLongValue; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.readShortValue; + import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDate; @@ -97,7 +104,7 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.columnIndex(columnName); } - int binaryTupleIndex = binaryTupleIndex(columnName, null); + int binaryTupleIndex = binaryTupleIndex(columnName); return binaryTupleIndex < 0 ? -1 : publicIndex(binaryTupleIndex); } @@ -109,7 +116,7 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.valueOrDefault(columnName, defaultValue); } - int binaryTupleIndex = binaryTupleIndex(columnName, null); + int binaryTupleIndex = binaryTupleIndex(columnName); return binaryTupleIndex < 0 || publicIndex(binaryTupleIndex) < 0 @@ -125,7 +132,7 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.value(columnName); } - int binaryTupleIndex = binaryTupleIndex(columnName, null); + int binaryTupleIndex = binaryTupleIndex(columnName); if (binaryTupleIndex < 0 || publicIndex(binaryTupleIndex) < 0) { throw new IllegalArgumentException("Column doesn't exist [name=" + columnName + ']'); @@ -182,11 +189,10 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.byteValue(columnName); } - int binaryTupleIndex = validateSchemaColumnType(columnName, ColumnType.INT8); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName); + int binaryTupleIndex = resolveBinaryTupleIndexByColumnName(columnName); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.byteValue(binaryTupleIndex); + return readByteValue(binaryTuple, binaryTupleIndex, actualType, columnName); } /** {@inheritDoc} */ @@ -196,11 +202,11 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.byteValue(columnIndex); } - int binaryTupleIndex = validateSchemaColumnType(columnIndex, ColumnType.INT8); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + Objects.checkIndex(columnIndex, columnCount); + int binaryTupleIndex = binaryTupleIndex(columnIndex); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.byteValue(binaryTupleIndex); + return readByteValue(binaryTuple, binaryTupleIndex, actualType, columnIndex); } /** {@inheritDoc} */ @@ -210,11 +216,10 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.shortValue(columnName); } - int binaryTupleIndex = validateSchemaColumnType(columnName, ColumnType.INT16); + var binaryTupleIndex = resolveBinaryTupleIndexByColumnName(columnName); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName); - - return binaryTuple.shortValue(binaryTupleIndex); + return readShortValue(binaryTuple, binaryTupleIndex, actualType, columnName); } /** {@inheritDoc} */ @@ -224,11 +229,11 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.shortValue(columnIndex); } - int binaryTupleIndex = validateSchemaColumnType(columnIndex, ColumnType.INT16); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + Objects.checkIndex(columnIndex, columnCount); + int binaryTupleIndex = binaryTupleIndex(columnIndex); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.shortValue(binaryTupleIndex); + return readShortValue(binaryTuple, binaryTupleIndex, actualType, columnIndex); } /** {@inheritDoc} */ @@ -238,11 +243,10 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.intValue(columnName); } - int binaryTupleIndex = validateSchemaColumnType(columnName, ColumnType.INT32); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName); + var binaryTupleIndex = resolveBinaryTupleIndexByColumnName(columnName); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.intValue(binaryTupleIndex); + return readIntValue(binaryTuple, binaryTupleIndex, actualType, columnName); } /** {@inheritDoc} */ @@ -252,11 +256,11 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.intValue(columnIndex); } - int binaryTupleIndex = validateSchemaColumnType(columnIndex, ColumnType.INT32); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + Objects.checkIndex(columnIndex, columnCount); + int binaryTupleIndex = binaryTupleIndex(columnIndex); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.intValue(binaryTupleIndex); + return readIntValue(binaryTuple, binaryTupleIndex, actualType, columnIndex); } /** {@inheritDoc} */ @@ -266,11 +270,10 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.longValue(columnName); } - int binaryTupleIndex = validateSchemaColumnType(columnName, ColumnType.INT64); + var binaryTupleIndex = resolveBinaryTupleIndexByColumnName(columnName); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName); - - return binaryTuple.longValue(binaryTupleIndex); + return readLongValue(binaryTuple, binaryTupleIndex, actualType, columnName); } /** {@inheritDoc} */ @@ -280,11 +283,11 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.longValue(columnIndex); } - int binaryTupleIndex = validateSchemaColumnType(columnIndex, ColumnType.INT64); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + Objects.checkIndex(columnIndex, columnCount); + int binaryTupleIndex = binaryTupleIndex(columnIndex); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.longValue(binaryTupleIndex); + return readLongValue(binaryTuple, binaryTupleIndex, actualType, columnIndex); } /** {@inheritDoc} */ @@ -294,11 +297,10 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.floatValue(columnName); } - int binaryTupleIndex = validateSchemaColumnType(columnName, ColumnType.FLOAT); + var binaryTupleIndex = resolveBinaryTupleIndexByColumnName(columnName); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName); - - return binaryTuple.floatValue(binaryTupleIndex); + return readFloatValue(binaryTuple, binaryTupleIndex, actualType, columnName); } /** {@inheritDoc} */ @@ -308,11 +310,11 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.floatValue(columnIndex); } - int binaryTupleIndex = validateSchemaColumnType(columnIndex, ColumnType.FLOAT); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + Objects.checkIndex(columnIndex, columnCount); + int binaryTupleIndex = binaryTupleIndex(columnIndex); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.floatValue(binaryTupleIndex); + return readFloatValue(binaryTuple, binaryTupleIndex, actualType, columnIndex); } /** {@inheritDoc} */ @@ -322,11 +324,10 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.doubleValue(columnName); } - int binaryTupleIndex = validateSchemaColumnType(columnName, ColumnType.DOUBLE); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnName); + var binaryTupleIndex = resolveBinaryTupleIndexByColumnName(columnName); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.doubleValue(binaryTupleIndex); + return readDoubleValue(binaryTuple, binaryTupleIndex, actualType, columnName); } /** {@inheritDoc} */ @@ -336,11 +337,11 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return tuple.doubleValue(columnIndex); } - int binaryTupleIndex = validateSchemaColumnType(columnIndex, ColumnType.DOUBLE); - - IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + Objects.checkIndex(columnIndex, columnCount); + int binaryTupleIndex = binaryTupleIndex(columnIndex); + ColumnType actualType = schemaColumnType(binaryTupleIndex); - return binaryTuple.doubleValue(binaryTupleIndex); + return readDoubleValue(binaryTuple, binaryTupleIndex, actualType, columnIndex); } /** {@inheritDoc} */ @@ -539,15 +540,15 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup return publicIndex; } - private int binaryTupleIndex(String columnName, @Nullable ColumnType type) { - var binaryTupleIndex = binaryTupleIndex(columnName); + private int validateSchemaColumnType(String columnName, ColumnType type) { + var index = binaryTupleIndex(columnName); - if (binaryTupleIndex < 0) { - return binaryTupleIndex; + if (index < 0) { + throw new IllegalArgumentException("Column doesn't exist [name=" + columnName + ']'); } if (type != null) { - ColumnType actualType = schemaColumnType(binaryTupleIndex); + ColumnType actualType = schemaColumnType(index); if (type != actualType) { throw new ClassCastException("Column with name '" + columnName + "' has type " + actualType @@ -555,16 +556,6 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup } } - return binaryTupleIndex; - } - - private int validateSchemaColumnType(String columnName, ColumnType type) { - var index = binaryTupleIndex(columnName, type); - - if (index < 0) { - throw new IllegalArgumentException("Column doesn't exist [name=" + columnName + ']'); - } - return index; } @@ -652,6 +643,16 @@ public abstract class MutableTupleBinaryTupleAdapter implements Tuple, BinaryTup } } + private int resolveBinaryTupleIndexByColumnName(String columnName) { + int binaryTupleIndex = binaryTupleIndex(columnName); + + if (binaryTupleIndex < 0) { + throw new IllegalArgumentException("Column doesn't exist [name=" + columnName + ']'); + } + + return binaryTupleIndex; + } + @Override public String toString() { return S.tupleToString(this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java new file mode 100644 index 00000000000..bc9489a85b6 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java @@ -0,0 +1,510 @@ +/* + * 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; + + if (doubleValue == floatValue) { + return floatValue; + } + + throw new ArithmeticException("Float value overflow"); + } + + /** + * Validates that the requested column type matches the actual type and throws {@link ClassCastException} + * otherwise. + * + * @param requestedType the requested column type + * @param actualType the actual column type + * @param columnIndex column index used for error reporting + * @throws ClassCastException when types do not match + */ + public static void validateColumnType(ColumnType requestedType, ColumnType actualType, int columnIndex) { + if (requestedType != actualType) { + throwClassCastException(requestedType, actualType, columnIndex); + } + } + + /** + * Validates that the requested column type matches the actual type and throws {@link ClassCastException} + * otherwise. + * + * @param requestedType the requested column type + * @param actualType the actual column type + * @param columnName column name used for error reporting + * @throws ClassCastException when types do not match + */ + public static void validateColumnType(ColumnType requestedType, ColumnType actualType, String columnName) { + if (requestedType != actualType) { + throwClassCastException(requestedType, actualType, columnName); + } + } + + private static void throwClassCastException(ColumnType requestedType, ColumnType actualType, int index) { + throw newClassCastException(requestedType, actualType, index); + } + + private static void throwClassCastException(ColumnType requestedType, ColumnType actualType, String columnName) { + throw newClassCastException(requestedType, actualType, columnName); + } + + private static RuntimeException newClassCastException(ColumnType requestedType, ColumnType actualType, int index) { + return new ClassCastException(IgniteStringFormatter.format(TYPE_CAST_ERROR_COLUMN_INDEX, index, actualType, requestedType)); + } + + private static RuntimeException newClassCastException(ColumnType requestedType, ColumnType actualType, String columnName) { + return new ClassCastException(IgniteStringFormatter.format(TYPE_CAST_ERROR_COLUMN_NAME, columnName, actualType, requestedType)); + } + + /** + * Returns {@code true} when the provided {@link ColumnType} is an integer type. + * + * @param actualType column type to check + * @return {@code true} if {@code actualType} is one of INT8, INT16, INT32 or INT64 + */ + private static boolean integerType(ColumnType actualType) { + return INT_TYPES.contains(actualType); + } +} diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java b/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java index 08a9f898904..96b2ac2bb59 100644 --- a/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java +++ b/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractRowTupleAdapter.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal.table; +import static org.apache.ignite.internal.util.TupleTypeCastUtils.validateColumnType; + import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDate; @@ -31,7 +33,9 @@ import org.apache.ignite.internal.schema.SchemaAware; import org.apache.ignite.internal.schema.SchemaDescriptor; import org.apache.ignite.internal.schema.row.Row; import org.apache.ignite.internal.util.IgniteUtils; +import org.apache.ignite.internal.util.TupleTypeCastUtils; import org.apache.ignite.lang.util.IgniteNameUtils; +import org.apache.ignite.sql.ColumnType; import org.apache.ignite.table.Tuple; import org.jetbrains.annotations.Nullable; @@ -115,6 +119,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public boolean booleanValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.BOOLEAN, col.type().spec(), columnName); + int binaryTupleIndex = correctIndex(col); IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); @@ -127,6 +133,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public boolean booleanValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.BOOLEAN, col.type().spec(), columnIndex); + int binaryTupleIndex = correctIndex(col); IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); @@ -141,9 +149,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); - - return row.byteValue(binaryTupleIndex); + return TupleTypeCastUtils.readByteValue(row, binaryTupleIndex, col.type().spec(), columnName); } /** {@inheritDoc} */ @@ -153,9 +159,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - - return row.byteValue(binaryTupleIndex); + return TupleTypeCastUtils.readByteValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -165,9 +169,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); - - return row.shortValue(binaryTupleIndex); + return TupleTypeCastUtils.readShortValue(row, binaryTupleIndex, col.type().spec(), columnName); } /** {@inheritDoc} */ @@ -177,9 +179,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - - return row.shortValue(binaryTupleIndex); + return TupleTypeCastUtils.readShortValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -189,9 +189,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); - - return row.intValue(binaryTupleIndex); + return TupleTypeCastUtils.readIntValue(row, binaryTupleIndex, col.type().spec(), columnName); } /** {@inheritDoc} */ @@ -201,9 +199,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - - return row.intValue(binaryTupleIndex); + return TupleTypeCastUtils.readIntValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -213,9 +209,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); - - return row.longValue(binaryTupleIndex); + return TupleTypeCastUtils.readLongValue(row, binaryTupleIndex, col.type().spec(), columnName); } /** {@inheritDoc} */ @@ -225,9 +219,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - - return row.longValue(binaryTupleIndex); + return TupleTypeCastUtils.readLongValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -237,9 +229,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); - - return row.floatValue(binaryTupleIndex); + return TupleTypeCastUtils.readFloatValue(row, binaryTupleIndex, col.type().spec(), columnName); } /** {@inheritDoc} */ @@ -251,7 +241,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - return row.floatValue(binaryTupleIndex); + return TupleTypeCastUtils.readFloatValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -261,9 +251,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnName); - - return row.doubleValue(binaryTupleIndex); + return TupleTypeCastUtils.readDoubleValue(row, binaryTupleIndex, col.type().spec(), columnName); } /** {@inheritDoc} */ @@ -273,9 +261,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - - return row.doubleValue(binaryTupleIndex); + return TupleTypeCastUtils.readDoubleValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -283,6 +269,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public BigDecimal decimalValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.DECIMAL, col.type().spec(), columnName); + return row.decimalValue(correctIndex(col)); } @@ -291,6 +279,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public BigDecimal decimalValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.DECIMAL, col.type().spec(), columnIndex); + return row.decimalValue(correctIndex(col)); } @@ -299,6 +289,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public String stringValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.STRING, col.type().spec(), columnName); + return row.stringValue(correctIndex(col)); } @@ -307,6 +299,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public String stringValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.STRING, col.type().spec(), columnIndex); + return row.stringValue(correctIndex(col)); } @@ -315,6 +309,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public byte[] bytesValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.BYTE_ARRAY, col.type().spec(), columnName); + return row.bytesValue(correctIndex(col)); } @@ -323,6 +319,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public byte[] bytesValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.BYTE_ARRAY, col.type().spec(), columnIndex); + return row.bytesValue(correctIndex(col)); } @@ -331,6 +329,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public UUID uuidValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.UUID, col.type().spec(), columnName); + return row.uuidValue(correctIndex(col)); } @@ -339,6 +339,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public UUID uuidValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.UUID, col.type().spec(), columnIndex); + return row.uuidValue(correctIndex(col)); } @@ -347,6 +349,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public LocalDate dateValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.DATE, col.type().spec(), columnName); + return row.dateValue(correctIndex(col)); } @@ -355,6 +359,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public LocalDate dateValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.DATE, col.type().spec(), columnIndex); + return row.dateValue(correctIndex(col)); } @@ -363,6 +369,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public LocalTime timeValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.TIME, col.type().spec(), columnName); + return row.timeValue(correctIndex(col)); } @@ -371,6 +379,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public LocalTime timeValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.TIME, col.type().spec(), columnIndex); + return row.timeValue(correctIndex(col)); } @@ -379,6 +389,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public LocalDateTime datetimeValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.DATETIME, col.type().spec(), columnName); + return row.dateTimeValue(correctIndex(col)); } @@ -387,6 +399,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public LocalDateTime datetimeValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.DATETIME, col.type().spec(), columnIndex); + return row.dateTimeValue(correctIndex(col)); } @@ -395,6 +409,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public Instant timestampValue(String columnName) { Column col = rowColumnByName(columnName); + validateColumnType(ColumnType.TIMESTAMP, col.type().spec(), columnName); + return row.timestampValue(correctIndex(col)); } @@ -403,6 +419,8 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { public Instant timestampValue(int columnIndex) { Column col = rowColumnByIndex(columnIndex); + validateColumnType(ColumnType.TIMESTAMP, col.type().spec(), columnIndex); + return row.timestampValue(correctIndex(col)); }
