This is an automated email from the ASF dual-hosted git repository. ppa pushed a commit to branch ignite-27612-tuple-implicit-casts in repository https://gitbox.apache.org/repos/asf/ignite-3.git
commit 24a0555bae989907d83ba553632937f56ba2e4ca Author: Pavel Pereslegin <[email protected]> AuthorDate: Tue Jan 20 15:17:24 2026 +0300 IGNITE-26491 Add support for reading from tuples with allowed type casting (#7400) --- .../java/org/apache/ignite/table/TupleImpl.java | 185 ++++++-- .../org/apache/ignite/table/TupleImplTest.java | 10 + .../ignite/table/AbstractImmutableTupleTest.java | 511 ++++++++++++++++++++- .../table/MutableTupleBinaryTupleAdapter.java | 133 +++--- .../requests/table/ClientHandlerTupleTests.java | 40 +- .../internal/client/sql/ClientSqlRowTest.java | 171 +++++-- .../ignite/internal/util/TupleTypeCastUtils.java | 457 ++++++++++++++++++ .../ignite/internal/schema/SchemaTestUtils.java | 32 -- modules/sql-engine/build.gradle | 1 + .../internal/sql/api/AsyncResultSetImpl.java | 90 +++- .../apache/ignite/internal/sql/api/SqlRowTest.java | 217 +++++++-- .../internal/table/AbstractRowTupleAdapter.java | 88 ++-- 12 files changed, 1649 insertions(+), 286 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..86fece7de09 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); + Object number = valueNotNull(columnName); + + return castToByte(number); } /** {@inheritDoc} */ @Override public byte byteValue(int columnIndex) { - return valueNotNull(columnIndex); + Object number = valueNotNull(columnIndex); + + return castToByte(number); } /** {@inheritDoc} */ @Override public short shortValue(String columnName) { - return valueNotNull(columnName); + Object number = valueNotNull(columnName); + + return castToShort(number); } /** {@inheritDoc} */ @Override public short shortValue(int columnIndex) { - return valueNotNull(columnIndex); + Object number = valueNotNull(columnIndex); + + return castToShort(number); } /** {@inheritDoc} */ @Override public int intValue(String columnName) { - return valueNotNull(columnName); + Object number = valueNotNull(columnName); + + return castToInt(number); } /** {@inheritDoc} */ @Override public int intValue(int columnIndex) { - return valueNotNull(columnIndex); + Object number = valueNotNull(columnIndex); + + return castToInt(number); } /** {@inheritDoc} */ @Override public long longValue(String columnName) { - return valueNotNull(columnName); + Object number = valueNotNull(columnName); + + return castToLong(number); } /** {@inheritDoc} */ @Override public long longValue(int columnIndex) { - return valueNotNull(columnIndex); + Object number = valueNotNull(columnIndex); + + return castToLong(number); } /** {@inheritDoc} */ @Override public float floatValue(String columnName) { - return valueNotNull(columnName); + Object number = valueNotNull(columnName); + + return castToFloat(number); } /** {@inheritDoc} */ @Override public float floatValue(int columnIndex) { - return valueNotNull(columnIndex); + Object number = valueNotNull(columnIndex); + + return castToFloat(number); } /** {@inheritDoc} */ @Override public double doubleValue(String columnName) { - return valueNotNull(columnName); + Object number = valueNotNull(columnName); + + return castToDouble(number); } /** {@inheritDoc} */ @Override public double doubleValue(int columnIndex) { - return valueNotNull(columnIndex); + Object number = valueNotNull(columnIndex); + + return castToDouble(number); } /** {@inheritDoc} */ @@ -419,25 +443,6 @@ class TupleImpl implements Tuple, Serializable { return (idx == null) ? def : (T) colValues.get(idx); } - /** {@inheritDoc} */ - @Override - public String toString() { - // Keep the same as IgniteToStringBuilder.toString(). - StringBuilder b = new StringBuilder(); - - b.append(getClass().getSimpleName()).append(" ["); - for (int i = 0; i < columnCount(); i++) { - if (i > 0) { - b.append(", "); - } - Object value = value(i); - b.append(columnName(i)).append('=').append(value); - } - b.append(']'); - - return b.toString(); - } - private <T> T valueNotNull(int columnIndex) { T value = value(columnIndex); @@ -459,4 +464,122 @@ class TupleImpl implements Tuple, Serializable { return value; } + + /** Casts a {@link Number} to {@code byte}. */ + private static byte castToByte(Object number) { + if (number instanceof Byte) { + return (byte) number; + } + + if (number instanceof Long || number instanceof Integer || number instanceof Short) { + long longVal = ((Number) number).longValue(); + byte byteVal = ((Number) number).byteValue(); + + if (longVal == byteVal) { + return byteVal; + } + + throw new ArithmeticException("Byte value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + byte.class); + } + + /** Casts a {@link Number} to {@code short}. */ + private static short castToShort(Object number) { + if (number instanceof Short) { + return (short) number; + } + + if (number instanceof Long || number instanceof Integer || number instanceof Byte) { + long longVal = ((Number) number).longValue(); + short shortVal = ((Number) number).shortValue(); + + if (longVal == shortVal) { + return shortVal; + } + + throw new ArithmeticException("Short value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + short.class); + } + + /** Casts a {@link Number} to {@code int}. */ + private static int castToInt(Object number) { + if (number instanceof Integer) { + return (int) number; + } + + if (number instanceof Long || number instanceof Short || number instanceof Byte) { + long longVal = ((Number) number).longValue(); + int intVal = ((Number) number).intValue(); + + if (longVal == intVal) { + return intVal; + } + + throw new ArithmeticException("Int value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + int.class); + } + + /** Casts a {@link Number} to {@code long}. */ + private static long castToLong(Object number) { + if (number instanceof Long || number instanceof Integer || number instanceof Short || number instanceof Byte) { + return ((Number) number).longValue(); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + long.class); + } + + /** Casts a {@link Number} to {@code float}. */ + private static float castToFloat(Object number) { + if (number instanceof Float) { + return (float) number; + } + + if (number instanceof Double) { + double doubleVal = ((Number) number).doubleValue(); + float floatVal = ((Number) number).floatValue(); + + //noinspection FloatingPointEquality + if (doubleVal == floatVal || Double.isNaN(doubleVal)) { + return floatVal; + } + + throw new ArithmeticException("Float value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + float.class); + } + + /** Casts a {@link Number} to {@code double}. */ + private static double castToDouble(Object number) { + if (number instanceof Double || number instanceof Float) { + return ((Number) number).doubleValue(); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + double.class); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + // Keep the same as IgniteToStringBuilder.toString(). + StringBuilder b = new StringBuilder(); + + b.append(getClass().getSimpleName()).append(" ["); + for (int i = 0; i < columnCount(); i++) { + if (i > 0) { + b.append(", "); + } + Object value = value(i); + b.append(columnName(i)).append('=').append(value); + } + b.append(']'); + + return b.toString(); + } } diff --git a/modules/api/src/test/java/org/apache/ignite/table/TupleImplTest.java b/modules/api/src/test/java/org/apache/ignite/table/TupleImplTest.java index eda4b5076f5..dafaa832fe7 100644 --- a/modules/api/src/test/java/org/apache/ignite/table/TupleImplTest.java +++ b/modules/api/src/test/java/org/apache/ignite/table/TupleImplTest.java @@ -22,7 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Map; import java.util.function.Function; import org.apache.ignite.sql.ColumnType; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; /** * Tests server tuple builder implementation. @@ -91,4 +93,12 @@ public class TupleImplTest extends AbstractMutableTupleTest { // must be found by non normalized name, regular method does normalization assertEquals("non-normalized", tuple.valueOrDefault("\"Name\"", "default")); } + + @Disabled("https://issues.apache.org/jira/browse/IGNITE-27577") + @ParameterizedTest + @Override + @SuppressWarnings("JUnitMalformedDeclaration") + public void allTypesUnsupportedConversion(ColumnType from, ColumnType to) { + super.allTypesUnsupportedConversion(from, to); + } } 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..4904d0cba34 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,10 @@ 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.containsString; +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 +42,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 +341,386 @@ public abstract class AbstractImmutableTupleTest { assertEquals(String.format(NULL_TO_PRIMITIVE_NAMED_ERROR_MESSAGE, "VAL"), err.getMessage()); } + @Test + void testReadAsByte() { + 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 testReadAsShort() { + // 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: " + value)); + + 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: " + value)); + + assertThat(tuple.shortValue(0), is(value)); + assertThat(tuple.intValue(0), is((int) value)); + assertThat(tuple.longValue(0), is((long) value)); + } + } + + @Test + void testReadAsInt() { + { + 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: " + value)); + + 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: " + value)); + + 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: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.shortValue(columnName)); + assertThat(ex.getMessage(), equalTo("Short value overflow: " + value)); + } + + 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: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.shortValue(0)); + assertThat(ex.getMessage(), equalTo("Short value overflow: " + value)); + } + + assertThat(tuple.intValue(0), is(value)); + assertThat(tuple.longValue(0), is((long) value)); + } + } + + @Test + void testReadAsLong() { + { + 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: " + value)); + + 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: " + value)); + + 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: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.shortValue(columnName)); + assertThat(ex.getMessage(), equalTo("Short value overflow: " + value)); + } + + 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: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.shortValue(0)); + assertThat(ex.getMessage(), equalTo("Short value overflow: " + value)); + } + + 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: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.shortValue(columnName)); + assertThat(ex.getMessage(), equalTo("Short value overflow: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.intValue(columnName)); + assertThat(ex.getMessage(), equalTo("Int value overflow: " + value)); + } + + assertThat(tuple.longValue(columnName), is(value)); + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.byteValue(0)); + assertThat(ex.getMessage(), equalTo("Byte value overflow: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.shortValue(0)); + assertThat(ex.getMessage(), equalTo("Short value overflow: " + value)); + } + + { + ArithmeticException ex = assertThrows(ArithmeticException.class, () -> tuple.intValue(0)); + assertThat(ex.getMessage(), equalTo("Int value overflow: " + value)); + } + + assertThat(tuple.longValue(0), is(value)); + } + } + + @Test + void testReadAsFloat() { + { + 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)); + } + + // NaN + { + Tuple tuple = createTupleOfSingleColumn(ColumnType.FLOAT, "FLOAT", Float.NaN); + + assertThat(Float.isNaN(tuple.floatValue("FLOAT")), is(true)); + assertThat(Double.isNaN(tuple.doubleValue("FLOAT")), is(true)); + + assertThat(Float.isNaN(tuple.floatValue(0)), is(true)); + assertThat(Double.isNaN(tuple.doubleValue(0)), is(true)); + } + + // Positive infinity + { + Tuple tuple = createTupleOfSingleColumn(ColumnType.FLOAT, "FLOAT", Float.POSITIVE_INFINITY); + + assertThat(tuple.floatValue("FLOAT"), is(Float.POSITIVE_INFINITY)); + assertThat(tuple.doubleValue("FLOAT"), is(Double.POSITIVE_INFINITY)); + + assertThat(tuple.floatValue(0), is(Float.POSITIVE_INFINITY)); + assertThat(tuple.doubleValue(0), is(Double.POSITIVE_INFINITY)); + } + + // Negative infinity + { + Tuple tuple = createTupleOfSingleColumn(ColumnType.FLOAT, "FLOAT", Float.NEGATIVE_INFINITY); + + assertThat(tuple.floatValue("FLOAT"), is(Float.NEGATIVE_INFINITY)); + assertThat(tuple.doubleValue("FLOAT"), is(Double.NEGATIVE_INFINITY)); + + assertThat(tuple.floatValue(0), is(Float.NEGATIVE_INFINITY)); + assertThat(tuple.doubleValue(0), is(Double.NEGATIVE_INFINITY)); + } + } + + @Test + void testReadAsDouble() { + String columnName = "DOUBLE"; + + // The field value can be represented as float. + { + double value = Float.MAX_VALUE; + 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; + Tuple tuple = createTupleOfSingleColumn(ColumnType.DOUBLE, columnName, value); + + ArithmeticException ex0 = assertThrows(ArithmeticException.class, () -> tuple.floatValue(columnName)); + assertThat(ex0.getMessage(), equalTo("Float value overflow: " + value)); + + ArithmeticException ex1 = assertThrows(ArithmeticException.class, () -> tuple.floatValue(0)); + assertThat(ex1.getMessage(), equalTo("Float value overflow: " + value)); + + assertThat(tuple.doubleValue(columnName), is(value)); + assertThat(tuple.doubleValue(0), is(value)); + } + + // NaN + { + Tuple tuple = createTupleOfSingleColumn(ColumnType.DOUBLE, columnName, Double.NaN); + + assertThat(Float.isNaN(tuple.floatValue(columnName)), is(true)); + assertThat(Double.isNaN(tuple.doubleValue(columnName)), is(true)); + + assertThat(Float.isNaN(tuple.floatValue(0)), is(true)); + assertThat(Double.isNaN(tuple.doubleValue(0)), is(true)); + } + + // Positive infinity + { + Tuple tuple = createTupleOfSingleColumn(ColumnType.DOUBLE, columnName, Double.POSITIVE_INFINITY); + + assertThat(tuple.floatValue(columnName), is(Float.POSITIVE_INFINITY)); + assertThat(tuple.doubleValue(columnName), is(Double.POSITIVE_INFINITY)); + + assertThat(tuple.floatValue(0), is(Float.POSITIVE_INFINITY)); + assertThat(tuple.doubleValue(0), is(Double.POSITIVE_INFINITY)); + } + + // Negative infinity + { + Tuple tuple = createTupleOfSingleColumn(ColumnType.DOUBLE, columnName, Double.NEGATIVE_INFINITY); + + assertThat(tuple.floatValue(columnName), is(Float.NEGATIVE_INFINITY)); + assertThat(tuple.doubleValue(columnName), is(Double.NEGATIVE_INFINITY)); + + assertThat(tuple.floatValue(0), is(Float.NEGATIVE_INFINITY)); + assertThat(tuple.doubleValue(0), is(Double.NEGATIVE_INFINITY)); + } + } + + @ParameterizedTest(name = "{0} -> {1}") + @MethodSource("allTypesUnsupportedConversionArgs") + public void allTypesUnsupportedConversion(ColumnType from, ColumnType to) { + Object value = generateMaxValue(from); + String columnName = "VALUE"; + Tuple tuple = createTupleOfSingleColumn(from, columnName, value); + + { + ClassCastException ex = assertThrows(ClassCastException.class, () -> readValue(tuple, to, columnName, null)); + String template = "Column with name '%s' has type %s but %s was requested"; + assertThat(ex.getMessage(), containsString(String.format(template, columnName, from.name(), to.name()))); + } + + { + ClassCastException ex = assertThrows(ClassCastException.class, () -> readValue(tuple, to, null, 0)); + String template = "Column with index %d has type %s but %s was requested"; + assertThat(ex.getMessage(), containsString(String.format(template, 0, from.name(), to.name()))); + } + } + + @ParameterizedTest(name = "{0} -> {1}") + @MethodSource("allTypesDowncastOverflowArgs") + public 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, null)); + assertThrows(ArithmeticException.class, () -> readValue(tuple, to, null, 0)); + } + /** * Adds sample values for columns of default schema: id (long), simpleName (string), "QuotedName" (string), noValue (null). * @@ -417,7 +804,7 @@ public abstract class AbstractImmutableTupleTest { return new BigInteger("10").pow(NANOS_IN_SECOND - precision).intValue(); } - private static @Nullable Object generateValue(Random rnd, ColumnType type) { + protected @Nullable Object generateValue(Random rnd, ColumnType type) { switch (type) { case NULL: return null; @@ -475,6 +862,25 @@ public abstract class AbstractImmutableTupleTest { } } + private 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 +913,107 @@ public abstract class AbstractImmutableTupleTest { Arguments.of(ColumnType.DOUBLE, (BiConsumer<Tuple, String>) Tuple::doubleValue) ); } + + private static Object readValue(Tuple tuple, ColumnType type, @Nullable String colName, @Nullable Integer index) { + assert colName == null ^ index == null; + + switch (type) { + case TIME: + return colName == null ? tuple.timeValue(index) : tuple.timeValue(colName); + case TIMESTAMP: + return colName == null ? tuple.timestampValue(index) : tuple.timestampValue(colName); + case DATE: + return colName == null ? tuple.dateValue(index) : tuple.dateValue(colName); + case DATETIME: + return colName == null ? tuple.datetimeValue(index) : tuple.datetimeValue(colName); + case INT8: + return colName == null ? tuple.byteValue(index) : tuple.byteValue(colName); + case INT16: + return colName == null ? tuple.shortValue(index) : tuple.shortValue(colName); + case INT32: + return colName == null ? tuple.intValue(index) : tuple.intValue(colName); + case INT64: + return colName == null ? tuple.longValue(index) : tuple.longValue(colName); + case FLOAT: + return colName == null ? tuple.floatValue(index) : tuple.floatValue(colName); + case DOUBLE: + return colName == null ? tuple.doubleValue(index) : tuple.doubleValue(colName); + case UUID: + return colName == null ? tuple.uuidValue(index) : tuple.uuidValue(colName); + case STRING: + return colName == null ? tuple.stringValue(index) : tuple.stringValue(colName); + case BOOLEAN: + return colName == null ? tuple.booleanValue(index) : tuple.booleanValue(colName); + case DECIMAL: + return colName == null ? tuple.decimalValue(index) : tuple.decimalValue(colName); + case BYTE_ARRAY: + return colName == null ? tuple.bytesValue(index) : tuple.bytesValue(colName); + 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/client-handler/src/test/java/org/apache/ignite/client/handler/requests/table/ClientHandlerTupleTests.java b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/requests/table/ClientHandlerTupleTests.java index abf338e5dbe..0293df2c50a 100644 --- a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/requests/table/ClientHandlerTupleTests.java +++ b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/requests/table/ClientHandlerTupleTests.java @@ -17,6 +17,7 @@ package org.apache.ignite.client.handler.requests.table; +import static org.apache.ignite.internal.type.NativeTypes.BOOLEAN; import static org.apache.ignite.internal.type.NativeTypes.BYTES; import static org.apache.ignite.internal.type.NativeTypes.DOUBLE; import static org.apache.ignite.internal.type.NativeTypes.FLOAT; @@ -37,9 +38,12 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; +import java.util.IdentityHashMap; import java.util.Random; import java.util.UUID; import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.IntConsumer; import java.util.stream.Stream; import org.apache.ignite.internal.binarytuple.BinaryTupleReader; import org.apache.ignite.internal.lang.IgniteStringFormatter; @@ -77,6 +81,7 @@ public class ClientHandlerTupleTests { private final SchemaDescriptor fullSchema = new SchemaDescriptor(42, new Column[]{new Column("keyUuidCol".toUpperCase(), NativeTypes.UUID, false)}, new Column[]{ + new Column("valBooleanCol".toUpperCase(), BOOLEAN, true), new Column("valByteCol".toUpperCase(), INT8, true), new Column("valShortCol".toUpperCase(), INT16, true), new Column("valIntCol".toUpperCase(), INT32, true), @@ -136,8 +141,9 @@ public class ClientHandlerTupleTests { public void testValueReturnsValueByIndex() { Tuple tuple = createTuple(); - assertEquals(1, (byte) tuple.value(0)); - assertEquals(4L, tuple.longValue(3)); + assertEquals(true, tuple.value(0)); + assertEquals(1, (byte) tuple.value(1)); + assertEquals(4L, tuple.longValue(4)); assertThrows(IndexOutOfBoundsException.class, () -> tuple.value(123)); } @@ -153,7 +159,7 @@ public class ClientHandlerTupleTests { @Test public void testColumnCount() { - assertEquals(14, createTuple().columnCount()); + assertEquals(15, createTuple().columnCount()); assertEquals(1, createKeyTuple().columnCount()); } @@ -161,8 +167,9 @@ public class ClientHandlerTupleTests { public void testColumnIndex() { Tuple tuple = createTuple(); - assertEquals(0, tuple.columnIndex("valByteCol")); - assertEquals(3, tuple.columnIndex("valLongCol")); + assertEquals(0, tuple.columnIndex("valBooleanCol")); + assertEquals(1, tuple.columnIndex("valByteCol")); + assertEquals(4, tuple.columnIndex("valLongCol")); assertEquals(-1, tuple.columnIndex("bad_name")); } @@ -218,6 +225,7 @@ public class ClientHandlerTupleTests { Random rnd = new Random(); return Tuple.create() + .set("valBooleanCol", true) .set("valByteCol", (byte) 1) .set("valShortCol", (short) 2) .set("valIntCol", 3) @@ -235,7 +243,27 @@ public class ClientHandlerTupleTests { } private static Stream<Arguments> primitiveAccessors() { - return SchemaTestUtils.PRIMITIVE_ACCESSORS.entrySet().stream() + IdentityHashMap<NativeType, BiConsumer<Tuple, Object>> map = new IdentityHashMap<>(); + + map.put(BOOLEAN, (tuple, index) -> invoke(index, tuple::booleanValue, tuple::booleanValue)); + map.put(INT8, (tuple, index) -> invoke(index, tuple::byteValue, tuple::byteValue)); + map.put(INT16, (tuple, index) -> invoke(index, tuple::shortValue, tuple::shortValue)); + map.put(INT32, (tuple, index) -> invoke(index, tuple::intValue, tuple::intValue)); + map.put(INT64, (tuple, index) -> invoke(index, tuple::longValue, tuple::longValue)); + map.put(FLOAT, (tuple, index) -> invoke(index, tuple::floatValue, tuple::floatValue)); + map.put(DOUBLE, (tuple, index) -> invoke(index, tuple::doubleValue, tuple::doubleValue)); + + return map.entrySet().stream() .map(e -> Arguments.of(e.getKey(), e.getValue())); } + + private static void invoke(Object index, IntConsumer intConsumer, Consumer<String> strConsumer) { + if (index instanceof Integer) { + intConsumer.accept((int) index); + } else { + assert index instanceof String : index.getClass(); + + strConsumer.accept((String) index); + } + } } diff --git a/modules/client/src/test/java/org/apache/ignite/internal/client/sql/ClientSqlRowTest.java b/modules/client/src/test/java/org/apache/ignite/internal/client/sql/ClientSqlRowTest.java index 3d8f6a10e83..bef512ca671 100644 --- a/modules/client/src/test/java/org/apache/ignite/internal/client/sql/ClientSqlRowTest.java +++ b/modules/client/src/test/java/org/apache/ignite/internal/client/sql/ClientSqlRowTest.java @@ -17,69 +17,106 @@ package org.apache.ignite.internal.client.sql; +import static org.apache.ignite.internal.type.NativeTypes.BOOLEAN; +import static org.apache.ignite.internal.type.NativeTypes.BYTES; +import static org.apache.ignite.internal.type.NativeTypes.DATE; +import static org.apache.ignite.internal.type.NativeTypes.DOUBLE; +import static org.apache.ignite.internal.type.NativeTypes.FLOAT; +import static org.apache.ignite.internal.type.NativeTypes.INT16; import static org.apache.ignite.internal.type.NativeTypes.INT32; +import static org.apache.ignite.internal.type.NativeTypes.INT64; +import static org.apache.ignite.internal.type.NativeTypes.INT8; +import static org.apache.ignite.internal.type.NativeTypes.STRING; +import static org.apache.ignite.internal.type.NativeTypes.datetime; +import static org.apache.ignite.internal.type.NativeTypes.time; +import static org.apache.ignite.internal.type.NativeTypes.timestamp; +import java.util.ArrayList; import java.util.List; -import java.util.function.BiConsumer; -import java.util.stream.Stream; +import java.util.function.Function; +import org.apache.ignite.client.handler.requests.table.ClientTableCommon; +import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder; import org.apache.ignite.internal.binarytuple.BinaryTupleReader; -import org.apache.ignite.internal.lang.IgniteStringFormatter; import org.apache.ignite.internal.schema.BinaryRow; +import org.apache.ignite.internal.schema.BinaryTupleSchema; import org.apache.ignite.internal.schema.Column; import org.apache.ignite.internal.schema.SchemaDescriptor; import org.apache.ignite.internal.schema.SchemaTestUtils; import org.apache.ignite.internal.sql.ColumnMetadataImpl; import org.apache.ignite.internal.sql.ResultSetMetadataImpl; -import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; -import org.apache.ignite.internal.testframework.IgniteTestUtils; import org.apache.ignite.internal.type.NativeType; -import org.apache.ignite.internal.util.IgniteUtils; +import org.apache.ignite.internal.type.NativeTypes; +import org.apache.ignite.lang.util.IgniteNameUtils; +import org.apache.ignite.sql.ColumnMetadata; import org.apache.ignite.sql.ColumnType; import org.apache.ignite.sql.ResultSetMetadata; +import org.apache.ignite.table.AbstractImmutableTupleTest; import org.apache.ignite.table.Tuple; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; /** - * Ensures that reading a {@code null} value as primitive from the - * {@link ClientSqlRow} instance produces a {@link NullPointerException}. + * Tests {@link ClientSqlRow} tuple implementation. */ -public class ClientSqlRowTest extends BaseIgniteAbstractTest { - @ParameterizedTest(name = "{0}") - @MethodSource("primitiveAccessors") - @SuppressWarnings("ThrowableNotThrown") - void nullPointerWhenReadingNullAsPrimitive( - NativeType type, - BiConsumer<Tuple, Object> fieldAccessor - ) { - String valueColumn = "VAL"; - Tuple row = createRow(valueColumn, type); - - IgniteTestUtils.assertThrows( - NullPointerException.class, - () -> fieldAccessor.accept(row, 1), - IgniteStringFormatter.format(IgniteUtils.NULL_TO_PRIMITIVE_ERROR_MESSAGE, 1) - ); +public class ClientSqlRowTest extends AbstractImmutableTupleTest { + /** Schema descriptor for default test tuple. */ + private final SchemaDescriptor schema = new SchemaDescriptor( + 42, + List.of( + new Column("ID", INT64, false), + new Column("SIMPLENAME", STRING, true), + new Column("QuotedName", STRING, true), + new Column("NOVALUE", STRING, true) + ), + List.of("ID"), + null + ); - IgniteTestUtils.assertThrows( - NullPointerException.class, - () -> fieldAccessor.accept(row, valueColumn), - IgniteStringFormatter.format(IgniteUtils.NULL_TO_PRIMITIVE_NAMED_ERROR_MESSAGE, valueColumn) - ); + /** Schema descriptor for tuple with columns of all the supported types. */ + private final SchemaDescriptor fullSchema = new SchemaDescriptor(42, + List.of( + new Column("valBoolCol".toUpperCase(), BOOLEAN, true), + new Column("valByteCol".toUpperCase(), INT8, true), + new Column("valShortCol".toUpperCase(), INT16, true), + new Column("valIntCol".toUpperCase(), INT32, true), + new Column("valLongCol".toUpperCase(), INT64, true), + new Column("valFloatCol".toUpperCase(), FLOAT, true), + new Column("valDoubleCol".toUpperCase(), DOUBLE, true), + new Column("valDateCol".toUpperCase(), DATE, true), + new Column("keyUuidCol".toUpperCase(), NativeTypes.UUID, false), + new Column("valUuidCol".toUpperCase(), NativeTypes.UUID, false), + new Column("valTimeCol".toUpperCase(), time(TIME_PRECISION), true), + new Column("valDateTimeCol".toUpperCase(), datetime(TIMESTAMP_PRECISION), true), + new Column("valTimeStampCol".toUpperCase(), timestamp(TIMESTAMP_PRECISION), true), + new Column("valBytesCol".toUpperCase(), BYTES, false), + new Column("valStringCol".toUpperCase(), STRING, false), + new Column("valDecimalCol".toUpperCase(), NativeTypes.decimalOf(25, 5), false) + ), + List.of("keyUuidCol".toUpperCase()), + null + ); - IgniteTestUtils.assertThrows( - UnsupportedOperationException.class, - () -> row.set("NEW", null), - null - ); + @Test + @Override + public void testSerialization() { + Assumptions.abort(ClientSqlRow.class.getSimpleName() + " is not serializable."); + } + + @Override + protected Tuple createTuple(Function<Tuple, Tuple> transformer) { + Tuple tuple = Tuple.create().set("ID", 1L); + + tuple = transformer.apply(tuple); + + return createClientSqlRow(schema, tuple); } - private static Tuple createRow(String columnName, NativeType type) { + @Override + protected Tuple createNullValueTuple(ColumnType valueType) { SchemaDescriptor schema = new SchemaDescriptor( 1, new Column[]{new Column("ID", INT32, false)}, - new Column[]{new Column(columnName, type, true)} + new Column[]{new Column("VAL", SchemaTestUtils.specToType(valueType), true)} ); BinaryRow binaryRow = SchemaTestUtils.binaryRow(schema, 1, null); @@ -87,14 +124,62 @@ public class ClientSqlRowTest extends BaseIgniteAbstractTest { ResultSetMetadata resultSetMetadata = new ResultSetMetadataImpl(List.of( new ColumnMetadataImpl("ID", ColumnType.INT32, 0, 0, false, null), - new ColumnMetadataImpl(columnName, type.spec(), 0, 0, true, null) + new ColumnMetadataImpl("VAL", valueType, 0, 0, true, null) )); return new ClientSqlRow(binaryTuple, resultSetMetadata); } - private static Stream<Arguments> primitiveAccessors() { - return SchemaTestUtils.PRIMITIVE_ACCESSORS.entrySet().stream() - .map(e -> Arguments.of(e.getKey(), e.getValue())); + @Override + protected Tuple getTuple() { + Tuple tuple = Tuple.create(); + + tuple = addColumnsForDefaultSchema(tuple); + + return createClientSqlRow(schema, tuple); + } + + @Override + protected Tuple getTupleWithColumnOfAllTypes() { + Tuple tuple = Tuple.create().set("keyUuidCol", UUID_VALUE); + + tuple = addColumnOfAllTypes(tuple); + + return createClientSqlRow(fullSchema, tuple); + } + + @Override + protected Tuple createTupleOfSingleColumn(ColumnType type, String columnName, Object value) { + NativeType nativeType = NativeTypes.fromObject(value); + SchemaDescriptor schema = new SchemaDescriptor(42, + new Column[]{new Column(columnName.toUpperCase(), nativeType, false)}, + new Column[]{} + ); + + Tuple tuple = Tuple.create().set(columnName, value); + + return createClientSqlRow(schema, tuple); + } + + private static Tuple createClientSqlRow(SchemaDescriptor schema, Tuple valuesTuple) { + List<ColumnMetadata> columnsMeta = new ArrayList<>(valuesTuple.columnCount()); + BinaryTupleBuilder binaryTupleBuilder = new BinaryTupleBuilder(valuesTuple.columnCount()); + BinaryTupleSchema binaryTupleSchema = BinaryTupleSchema.createRowSchema(schema); + + for (int i = 0; i < valuesTuple.columnCount(); i++) { + Column field = schema.columns().get(i); + Object value = valuesTuple.value(IgniteNameUtils.quoteIfNeeded(field.name())); + + binaryTupleSchema.appendValue(binaryTupleBuilder, i, value); + + int precision = ClientTableCommon.getPrecision(field.type()); + int scale = ClientTableCommon.getDecimalScale(field.type()); + + columnsMeta.add(new ColumnMetadataImpl(field.name(), field.type().spec(), precision, scale, field.nullable(), null)); + } + + BinaryTupleReader binaryTuple = new BinaryTupleReader(valuesTuple.columnCount(), binaryTupleBuilder.build()); + + return new ClientSqlRow(binaryTuple, new ResultSetMetadataImpl(columnsMeta)); } } 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..845a69990ac --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/TupleTypeCastUtils.java @@ -0,0 +1,457 @@ +/* + * 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 org.apache.ignite.internal.lang.IgniteStringFormatter; +import org.apache.ignite.internal.lang.InternalTuple; +import org.apache.ignite.sql.ColumnType; +import org.apache.ignite.table.Tuple; + +/** + * Helper methods that perform conversions between numeric types. These methods are used when reading + * and writing the primitive numeric values of a {@link Tuple tuple}. + * + * <p>The following conversions are supported: + * <ul> + * <li>Any integer type to any other integer type with range checks (e.g. {@link ColumnType#INT64} to {@link ColumnType#INT8} + * may throw {@link ArithmeticException} if the value doesn't fit into {@link ColumnType#INT8}).</li> + * <li>{@link ColumnType#FLOAT} to {@link ColumnType#DOUBLE} are always allowed.</li> + * <li>{@link ColumnType#DOUBLE} to {@link ColumnType#FLOAT} with precision checks (may throw {@link ArithmeticException} + * if the value cannot be represented as FLOAT without precision loss).</li> + * </ul> + */ +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 int INT_COLUMN_TYPES_BITMASK = buildIntegerTypesBitMask(); + + /** Reads a value from the tuple and converts it to a byte if possible. */ + public static byte readByteValue(InternalTuple binaryTuple, int binaryTupleIndex, ColumnType actualType, int columnIndex) { + if (!integerType(actualType)) { + throwClassCastException(ColumnType.INT8, actualType, columnIndex); + } + + IgniteUtils.ensureNotNull(binaryTuple, binaryTupleIndex, columnIndex); + + return toByte(binaryTuple, binaryTupleIndex, actualType); + } + + /** Reads a value from the tuple and converts it to a byte if possible. */ + 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 toByte(binaryTuple, binaryTupleIndex, actualType); + } + + /** Reads a value from the tuple and converts it to a short if possible. */ + 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 toShort(binaryTuple, binaryTupleIndex, actualType); + } + + /** Reads a value from the tuple and converts it to a short if possible. */ + 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 toShort(binaryTuple, binaryTupleIndex, actualType); + } + + /** Reads a value from the tuple and converts it to an int if possible. */ + 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 toInt(binaryTuple, binaryTupleIndex, actualType); + } + + /** Reads a value from the tuple and converts it to an int if possible. */ + 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 toInt(binaryTuple, binaryTupleIndex, actualType); + } + + /** Reads a value from the tuple and returns it as a long. Only integer column types are allowed. */ + 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. */ + 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. */ + 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 toFloat(binaryTuple, binaryTupleIndex, actualType); + } + + throw newClassCastException(ColumnType.FLOAT, actualType, columnIndex); + } + + /** Reads a value from the tuple and converts it to a float if possible. */ + 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 toFloat(binaryTuple, binaryTupleIndex, actualType); + } + + throw newClassCastException(ColumnType.FLOAT, actualType, columnName); + } + + /** Reads a value from the tuple and returns it as a double. */ + 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.DOUBLE, actualType, columnIndex); + } + + /** Reads a value from the tuple and returns it as a double. */ + 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.DOUBLE, actualType, columnName); + } + + /** + * Validates that the requested column type matches the actual type and throws {@link ClassCastException} + * otherwise. + */ + 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. + */ + public static void validateColumnType(ColumnType requestedType, ColumnType actualType, String columnName) { + if (requestedType != actualType) { + throwClassCastException(requestedType, actualType, columnName); + } + } + + /** Casts an object to {@code byte} if possible. */ + public static byte castToByte(Object number) { + if (number instanceof Byte) { + return (byte) number; + } + + if (number instanceof Long || number instanceof Integer || number instanceof Short) { + long longVal = ((Number) number).longValue(); + byte byteVal = ((Number) number).byteValue(); + + if (longVal == byteVal) { + return byteVal; + } + + throw new ArithmeticException("Byte value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + byte.class); + } + + /** Casts an object to {@code short} if possible. */ + public static short castToShort(Object number) { + if (number instanceof Short) { + return (short) number; + } + + if (number instanceof Long || number instanceof Integer || number instanceof Byte) { + long longVal = ((Number) number).longValue(); + short shortVal = ((Number) number).shortValue(); + + if (longVal == shortVal) { + return shortVal; + } + + throw new ArithmeticException("Short value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + short.class); + } + + /** Casts an object to {@code int} if possible. */ + public static int castToInt(Object number) { + if (number instanceof Integer) { + return (int) number; + } + + if (number instanceof Long || number instanceof Short || number instanceof Byte) { + long longVal = ((Number) number).longValue(); + int intVal = ((Number) number).intValue(); + + if (longVal == intVal) { + return intVal; + } + + throw new ArithmeticException("Int value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + int.class); + } + + /** Casts an object to {@code long} if possible. */ + public static long castToLong(Object number) { + if (number instanceof Long || number instanceof Integer || number instanceof Short || number instanceof Byte) { + return ((Number) number).longValue(); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + long.class); + } + + /** Casts an object to {@code float} if possible. */ + public static float castToFloat(Object number) { + if (number instanceof Float) { + return (float) number; + } + + if (number instanceof Double) { + double doubleVal = ((Number) number).doubleValue(); + float floatVal = ((Number) number).floatValue(); + + //noinspection FloatingPointEquality + if (doubleVal == floatVal || Double.isNaN(doubleVal)) { + return floatVal; + } + + throw new ArithmeticException("Float value overflow: " + number); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + float.class); + } + + /** Casts an object to {@code double} if possible. */ + public static double castToDouble(Object number) { + if (number instanceof Double || number instanceof Float) { + return ((Number) number).doubleValue(); + } + + throw new ClassCastException(number.getClass() + " cannot be cast to " + double.class); + } + + /** Casts an integer value from the tuple to {@code byte} performing range checks. */ + @SuppressWarnings("NumericCastThatLosesPrecision") + private static byte toByte(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; + } + + throw new ArithmeticException("Byte value overflow: " + value); + } + case INT32: { + int value = binaryTuple.intValue(binaryTupleIndex); + byte byteValue = (byte) value; + + if (byteValue == value) { + return byteValue; + } + + throw new ArithmeticException("Byte value overflow: " + value); + } + case INT64: { + long value = binaryTuple.longValue(binaryTupleIndex); + byte byteValue = (byte) value; + + if (byteValue == value) { + return byteValue; + } + + throw new ArithmeticException("Byte value overflow: " + value); + } + + default: + } + + throw new IllegalArgumentException("invalid type: " + valueType); + } + + /** Casts an integer value from the tuple to {@code short} performing range checks. */ + @SuppressWarnings("NumericCastThatLosesPrecision") + private static short toShort(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; + } + + throw new ArithmeticException("Short value overflow: " + value); + } + case INT64: { + long value = binaryTuple.longValue(binaryTupleIndex); + short shortValue = (short) value; + + if (shortValue == value) { + return shortValue; + } + + throw new ArithmeticException("Short value overflow: " + value); + } + + default: + } + + throw new IllegalArgumentException("invalid type: " + valueType); + } + + /** Casts an integer value from the tuple to {@code int} performing range checks. */ + @SuppressWarnings("NumericCastThatLosesPrecision") + private static int toInt(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; + } + + throw new ArithmeticException("Int value overflow: " + value); + } + + default: + assert false : valueType; + } + + throw new IllegalArgumentException("invalid type: " + valueType); + } + + /** Casts a floating-point value from the tuple to {@code float} performing precision checks. */ + @SuppressWarnings({"NumericCastThatLosesPrecision", "FloatingPointEquality"}) + private static float toFloat(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 || Double.isNaN(doubleValue)) { + return floatValue; + } + + throw new ArithmeticException("Float value overflow: " + doubleValue); + } + + 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)); + } + + private static boolean integerType(ColumnType type) { + return (INT_COLUMN_TYPES_BITMASK & (1 << type.ordinal())) != 0; + } + + private static int buildIntegerTypesBitMask() { + assert ColumnType.values().length < Integer.SIZE : "Too many column types to fit in an integer bitmask"; + + ColumnType[] intTypes = { + ColumnType.INT8, + ColumnType.INT16, + ColumnType.INT32, + ColumnType.INT64 + }; + + int elements = 0; + + for (ColumnType e : intTypes) { + elements |= (1 << e.ordinal()); + } + + return elements; + } +} diff --git a/modules/schema/src/testFixtures/java/org/apache/ignite/internal/schema/SchemaTestUtils.java b/modules/schema/src/testFixtures/java/org/apache/ignite/internal/schema/SchemaTestUtils.java index 9691e3d2615..acb04c4cace 100644 --- a/modules/schema/src/testFixtures/java/org/apache/ignite/internal/schema/SchemaTestUtils.java +++ b/modules/schema/src/testFixtures/java/org/apache/ignite/internal/schema/SchemaTestUtils.java @@ -30,15 +30,10 @@ import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; -import java.util.IdentityHashMap; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.IntConsumer; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.ignite.internal.schema.row.RowAssembler; @@ -48,7 +43,6 @@ import org.apache.ignite.internal.type.NativeType; import org.apache.ignite.internal.type.NativeTypes; import org.apache.ignite.internal.type.TemporalNativeType; import org.apache.ignite.sql.ColumnType; -import org.apache.ignite.table.Tuple; /** * Test utility class. @@ -78,8 +72,6 @@ public final class SchemaTestUtils { NativeTypes.BYTES, NativeTypes.STRING); - public static final Map<NativeType, BiConsumer<Tuple, Object>> PRIMITIVE_ACCESSORS = makePrimitiveAccessorsMap(); - /** * Generates random value of given type. * @@ -221,28 +213,4 @@ public final class SchemaTestUtils { return Instant.ofEpochMilli(minTs + (long) (rnd.nextDouble() * (maxTs - minTs))).truncatedTo(ChronoUnit.SECONDS) .plusNanos(normalizeNanos(rnd.nextInt(1_000_000_000), type.precision())); } - - private static Map<NativeType, BiConsumer<Tuple, Object>> makePrimitiveAccessorsMap() { - IdentityHashMap<NativeType, BiConsumer<Tuple, Object>> map = new IdentityHashMap<>(); - - map.put(NativeTypes.BOOLEAN, (tuple, index) -> invoke(index, tuple::booleanValue, tuple::booleanValue)); - map.put(NativeTypes.INT8, (tuple, index) -> invoke(index, tuple::byteValue, tuple::byteValue)); - map.put(NativeTypes.INT16, (tuple, index) -> invoke(index, tuple::shortValue, tuple::shortValue)); - map.put(NativeTypes.INT32, (tuple, index) -> invoke(index, tuple::intValue, tuple::intValue)); - map.put(NativeTypes.INT64, (tuple, index) -> invoke(index, tuple::longValue, tuple::longValue)); - map.put(NativeTypes.FLOAT, (tuple, index) -> invoke(index, tuple::floatValue, tuple::floatValue)); - map.put(NativeTypes.DOUBLE, (tuple, index) -> invoke(index, tuple::doubleValue, tuple::doubleValue)); - - return Collections.unmodifiableMap(map); - } - - private static void invoke(Object index, IntConsumer intConsumer, Consumer<String> strConsumer) { - if (index instanceof Integer) { - intConsumer.accept((int) index); - } else { - assert index instanceof String : index.getClass(); - - strConsumer.accept((String) index); - } - } } diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle index 3aa7c06e7fd..6f637a479e6 100644 --- a/modules/sql-engine/build.gradle +++ b/modules/sql-engine/build.gradle @@ -101,6 +101,7 @@ dependencies { testImplementation project(':ignite-placement-driver') testImplementation libs.jmh.core testImplementation libs.awaitility + testImplementation testFixtures(project(':ignite-api')) testImplementation testFixtures(project(':ignite-core')) testImplementation testFixtures(project(':ignite-configuration')) testImplementation testFixtures(project(':ignite-schema')) diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java index 9b792bbf7bb..3d1fe18b9ec 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java @@ -37,6 +37,7 @@ import org.apache.ignite.internal.tostring.S; import org.apache.ignite.internal.util.AsyncCursor.BatchedResult; import org.apache.ignite.internal.util.IgniteUtils; import org.apache.ignite.internal.util.TransformingIterator; +import org.apache.ignite.internal.util.TupleTypeCastUtils; import org.apache.ignite.sql.NoRowSetExpectedException; import org.apache.ignite.sql.ResultSetMetadata; import org.apache.ignite.sql.SqlRow; @@ -216,9 +217,13 @@ public class AsyncResultSetImpl<T> implements AsyncResultSet<T> { /** {@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); } /** {@inheritDoc} */ @@ -254,73 +259,97 @@ public class AsyncResultSetImpl<T> implements AsyncResultSet<T> { /** {@inheritDoc} */ @Override public byte byteValue(String columnName) { - return (byte) getValueNotNull(columnName); + Object number = getValueNotNull(columnName); + + return TupleTypeCastUtils.castToByte(number); } /** {@inheritDoc} */ @Override public byte byteValue(int columnIndex) { - return (byte) getValueNotNull(columnIndex); + Object number = getValueNotNull(columnIndex); + + return TupleTypeCastUtils.castToByte(number); } /** {@inheritDoc} */ @Override public short shortValue(String columnName) { - return (short) getValueNotNull(columnName); + Object number = getValueNotNull(columnName); + + return TupleTypeCastUtils.castToShort(number); } /** {@inheritDoc} */ @Override public short shortValue(int columnIndex) { - return (short) getValueNotNull(columnIndex); + Object number = getValueNotNull(columnIndex); + + return TupleTypeCastUtils.castToShort(number); } /** {@inheritDoc} */ @Override public int intValue(String columnName) { - return (int) getValueNotNull(columnName); + Object number = getValueNotNull(columnName); + + return TupleTypeCastUtils.castToInt(number); } /** {@inheritDoc} */ @Override public int intValue(int columnIndex) { - return (int) getValueNotNull(columnIndex); + Object number = getValueNotNull(columnIndex); + + return TupleTypeCastUtils.castToInt(number); } /** {@inheritDoc} */ @Override public long longValue(String columnName) { - return (long) getValueNotNull(columnName); + Object number = getValueNotNull(columnName); + + return TupleTypeCastUtils.castToLong(number); } /** {@inheritDoc} */ @Override public long longValue(int columnIndex) { - return (long) getValueNotNull(columnIndex); + Object number = getValueNotNull(columnIndex); + + return TupleTypeCastUtils.castToLong(number); } /** {@inheritDoc} */ @Override public float floatValue(String columnName) { - return (float) getValueNotNull(columnName); + Object number = getValueNotNull(columnName); + + return TupleTypeCastUtils.castToFloat(number); } /** {@inheritDoc} */ @Override public float floatValue(int columnIndex) { - return (float) getValueNotNull(columnIndex); + Object number = getValueNotNull(columnIndex); + + return TupleTypeCastUtils.castToFloat(number); } /** {@inheritDoc} */ @Override public double doubleValue(String columnName) { - return (double) getValueNotNull(columnName); + Object number = getValueNotNull(columnName); + + return TupleTypeCastUtils.castToDouble(number); } /** {@inheritDoc} */ @Override public double doubleValue(int columnIndex) { - return (double) getValueNotNull(columnIndex); + Object number = getValueNotNull(columnIndex); + + return TupleTypeCastUtils.castToDouble(number); } /** {@inheritDoc} */ @@ -432,28 +461,49 @@ public class AsyncResultSetImpl<T> implements AsyncResultSet<T> { /** {@inheritDoc} */ @Override - public String toString() { - return S.tupleToString(this); + public int hashCode() { + return Tuple.hashCode(this); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + //noinspection SimplifiableIfStatement + if (obj instanceof Tuple) { + return Tuple.equals(this, (Tuple) obj); + } + + return false; } - 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; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return S.tupleToString(this); } } } diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SqlRowTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SqlRowTest.java index 42916a58846..3fc28ca75a7 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SqlRowTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/api/SqlRowTest.java @@ -17,13 +17,32 @@ package org.apache.ignite.internal.sql.api; -import static org.apache.ignite.internal.sql.engine.util.TypeUtils.IDENTITY_ROW_CONVERTER; +import static org.apache.ignite.internal.type.NativeTypes.BOOLEAN; +import static org.apache.ignite.internal.type.NativeTypes.BYTES; +import static org.apache.ignite.internal.type.NativeTypes.DATE; +import static org.apache.ignite.internal.type.NativeTypes.DOUBLE; +import static org.apache.ignite.internal.type.NativeTypes.FLOAT; +import static org.apache.ignite.internal.type.NativeTypes.INT16; +import static org.apache.ignite.internal.type.NativeTypes.INT32; +import static org.apache.ignite.internal.type.NativeTypes.INT64; +import static org.apache.ignite.internal.type.NativeTypes.INT8; +import static org.apache.ignite.internal.type.NativeTypes.STRING; +import static org.apache.ignite.internal.type.NativeTypes.datetime; +import static org.apache.ignite.internal.type.NativeTypes.time; +import static org.apache.ignite.internal.type.NativeTypes.timestamp; +import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; -import java.util.function.BiConsumer; -import java.util.stream.Stream; -import org.apache.ignite.internal.lang.IgniteStringFormatter; +import java.util.Random; +import java.util.function.Function; +import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder; +import org.apache.ignite.internal.binarytuple.BinaryTupleReader; +import org.apache.ignite.internal.schema.BinaryTupleSchema; +import org.apache.ignite.internal.schema.Column; +import org.apache.ignite.internal.schema.SchemaDescriptor; import org.apache.ignite.internal.schema.SchemaTestUtils; +import org.apache.ignite.internal.schema.marshaller.TupleMarshaller; import org.apache.ignite.internal.sql.ColumnMetadataImpl; import org.apache.ignite.internal.sql.ResultSetMetadataImpl; import org.apache.ignite.internal.sql.api.AsyncResultSetImpl.SqlRowImpl; @@ -31,70 +50,166 @@ import org.apache.ignite.internal.sql.engine.InternalSqlRowImpl; import org.apache.ignite.internal.sql.engine.api.expressions.RowFactory; import org.apache.ignite.internal.sql.engine.exec.SqlRowHandler; import org.apache.ignite.internal.sql.engine.exec.SqlRowHandler.RowWrapper; -import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest; -import org.apache.ignite.internal.testframework.IgniteTestUtils; -import org.apache.ignite.internal.type.NativeType; +import org.apache.ignite.internal.sql.engine.util.TypeUtils; +import org.apache.ignite.internal.table.KeyValueTestUtils; +import org.apache.ignite.internal.table.TableRow; import org.apache.ignite.internal.type.NativeTypes; -import org.apache.ignite.internal.type.StructNativeType; -import org.apache.ignite.internal.util.IgniteUtils; +import org.apache.ignite.internal.type.NativeTypes.StructTypeBuilder; +import org.apache.ignite.lang.util.IgniteNameUtils; +import org.apache.ignite.sql.ColumnMetadata; +import org.apache.ignite.sql.ColumnType; import org.apache.ignite.sql.SqlRow; +import org.apache.ignite.table.AbstractImmutableTupleTest; import org.apache.ignite.table.Tuple; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; /** - * Ensures that reading a {@code null} value as primitive from the - * {@link SqlRow} instance produces a {@link NullPointerException}. + * Tests {@link SqlRow} tuple implementation. */ -public class SqlRowTest extends BaseIgniteAbstractTest { - @ParameterizedTest(name = "{0}") - @MethodSource("primitiveAccessors") - @SuppressWarnings("ThrowableNotThrown") - void nullPointerWhenReadingNullAsPrimitive( - NativeType type, - BiConsumer<Tuple, Object> fieldAccessor - ) { - String columnName = "VAL"; - Tuple row = createRow(columnName, type); - - IgniteTestUtils.assertThrows( - NullPointerException.class, - () -> fieldAccessor.accept(row, 0), - IgniteStringFormatter.format(IgniteUtils.NULL_TO_PRIMITIVE_ERROR_MESSAGE, 0) - ); +public class SqlRowTest extends AbstractImmutableTupleTest { + /** Schema descriptor for default test tuple. */ + private final SchemaDescriptor schema = new SchemaDescriptor( + 42, + List.of( + new Column("ID", INT64, false), + new Column("SIMPLENAME", STRING, true), + new Column("QuotedName", STRING, true), + new Column("NOVALUE", STRING, true) + ), + List.of("ID"), + null + ); + + /** Schema descriptor for tuple with columns of all the supported types. */ + private final SchemaDescriptor fullSchema = new SchemaDescriptor(42, + List.of( + new Column("valBoolCol".toUpperCase(), BOOLEAN, true), + new Column("valByteCol".toUpperCase(), INT8, true), + new Column("valShortCol".toUpperCase(), INT16, true), + new Column("valIntCol".toUpperCase(), INT32, true), + new Column("valLongCol".toUpperCase(), INT64, true), + new Column("valFloatCol".toUpperCase(), FLOAT, true), + new Column("valDoubleCol".toUpperCase(), DOUBLE, true), + new Column("valDateCol".toUpperCase(), DATE, true), + new Column("keyUuidCol".toUpperCase(), NativeTypes.UUID, false), + new Column("valUuidCol".toUpperCase(), NativeTypes.UUID, false), + new Column("valTimeCol".toUpperCase(), time(TIME_PRECISION), true), + new Column("valDateTimeCol".toUpperCase(), datetime(TIMESTAMP_PRECISION), true), + new Column("valTimeStampCol".toUpperCase(), timestamp(TIMESTAMP_PRECISION), true), + new Column("valBytesCol".toUpperCase(), BYTES, false), + new Column("valStringCol".toUpperCase(), STRING, false), + new Column("valDecimalCol".toUpperCase(), NativeTypes.decimalOf(25, 5), false) + ), + List.of("keyUuidCol".toUpperCase()), + null + ); + + @Override + protected Tuple createTuple(Function<Tuple, Tuple> transformer) { + Tuple tuple = Tuple.create().set("ID", 1L); + + tuple = transformer.apply(tuple); - IgniteTestUtils.assertThrows( - NullPointerException.class, - () -> fieldAccessor.accept(row, columnName), - IgniteStringFormatter.format(IgniteUtils.NULL_TO_PRIMITIVE_NAMED_ERROR_MESSAGE, columnName) + TupleMarshaller marshaller = KeyValueTestUtils.createMarshaller(schema); + + return TableRow.tuple(marshaller.marshal(tuple)); + } + + @Override + protected Tuple createNullValueTuple(ColumnType valueType) { + SchemaDescriptor schema = new SchemaDescriptor( + 1, + new Column[]{new Column("ID", INT32, false)}, + new Column[]{new Column("VAL", SchemaTestUtils.specToType(valueType), true)} ); - IgniteTestUtils.assertThrows( - UnsupportedOperationException.class, - () -> row.set("NEW", null), - null + return createSqlRow(schema, Tuple.create().set("ID", 1).set("VAL", null)); + } + + @Test + @Override + public void testSerialization() { + Assumptions.abort(SqlRow.class.getSimpleName() + " is not serializable."); + } + + @Disabled("https://issues.apache.org/jira/browse/IGNITE-27577") + @ParameterizedTest + @Override + @SuppressWarnings("JUnitMalformedDeclaration") + public void allTypesUnsupportedConversion(ColumnType from, ColumnType to) { + super.allTypesUnsupportedConversion(from, to); + } + + @Override + protected Tuple getTuple() { + Tuple tuple = Tuple.create(); + + tuple = addColumnsForDefaultSchema(tuple); + + return createSqlRow(schema, tuple); + } + + @Override + protected Tuple getTupleWithColumnOfAllTypes() { + Tuple tuple = Tuple.create().set("keyUuidCol", UUID_VALUE); + + tuple = addColumnOfAllTypes(tuple); + + return createSqlRow(fullSchema, tuple); + } + + @Override + protected Tuple createTupleOfSingleColumn(ColumnType type, String columnName, Object value) { + SchemaDescriptor schema = new SchemaDescriptor(42, + new Column[]{new Column(columnName.toUpperCase(), NativeTypes.fromObject(value), false)}, + new Column[]{} ); + + Tuple tuple = Tuple.create().set(columnName, value); + + return createSqlRow(schema, tuple); } - private static Tuple createRow(String columnName, NativeType type) { + @Override + protected @Nullable Object generateValue(Random rnd, ColumnType type) { + if (type == ColumnType.TIME) { + // SQL currently support only milliseconds. + return LocalTime.of(rnd.nextInt(24), rnd.nextInt(60), rnd.nextInt(60), + rnd.nextInt(1_000) * 1_000_000); + } + + return super.generateValue(rnd, type); + } + + private static Tuple createSqlRow(SchemaDescriptor descriptor, Tuple valuesTuple) { SqlRowHandler handler = SqlRowHandler.INSTANCE; - StructNativeType schema = NativeTypes.structBuilder() - .addField(columnName, type, true) - .build(); + StructTypeBuilder structTypeBuilder = NativeTypes.structBuilder(); + List<ColumnMetadata> columnsMeta = new ArrayList<>(valuesTuple.columnCount()); + BinaryTupleBuilder binaryTupleBuilder = new BinaryTupleBuilder(valuesTuple.columnCount()); + BinaryTupleSchema binaryTupleSchema = BinaryTupleSchema.createRowSchema(descriptor); - RowFactory<RowWrapper> factory = handler.create(schema); - RowWrapper binaryTupleRow = factory.create(handler.toBinaryTuple(factory.create(new Object[]{null}))); + for (int i = 0; i < valuesTuple.columnCount(); i++) { + Column column = descriptor.columns().get(i); + Object value = valuesTuple.value(IgniteNameUtils.quoteIfNeeded(column.name())); - InternalSqlRowImpl<RowWrapper> internalSqlRow = - new InternalSqlRowImpl<>(binaryTupleRow, handler, IDENTITY_ROW_CONVERTER); + binaryTupleSchema.appendValue(binaryTupleBuilder, i, value); + structTypeBuilder.addField(column.name(), column.type(), column.nullable()); + columnsMeta.add(new ColumnMetadataImpl(column.name(), column.type().spec(), -1, -1, column.nullable(), null)); + } - return new SqlRowImpl(internalSqlRow, new ResultSetMetadataImpl(List.of( - new ColumnMetadataImpl(columnName, type.spec(), 0, 0, true, null)))); - } + BinaryTupleReader binaryTuple = new BinaryTupleReader(valuesTuple.columnCount(), binaryTupleBuilder.build()); + + RowFactory<RowWrapper> factory = handler.create(structTypeBuilder.build()); + RowWrapper binaryTupleRow = factory.create(binaryTuple); + + InternalSqlRowImpl<RowWrapper> internalSqlRow = + new InternalSqlRowImpl<>(binaryTupleRow, SqlRowHandler.INSTANCE, (index, val) -> + val == null ? null : TypeUtils.fromInternal(val, descriptor.column(index).type().spec())); - private static Stream<Arguments> primitiveAccessors() { - return SchemaTestUtils.PRIMITIVE_ACCESSORS.entrySet().stream() - .map(e -> Arguments.of(e.getKey(), e.getValue())); + return new SqlRowImpl(internalSqlRow, new ResultSetMetadataImpl(columnsMeta)); } } 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..4c5279ddd9e 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} */ @@ -249,9 +239,7 @@ public abstract class AbstractRowTupleAdapter implements Tuple, SchemaAware { int binaryTupleIndex = correctIndex(col); - IgniteUtils.ensureNotNull(row, binaryTupleIndex, columnIndex); - - return row.floatValue(binaryTupleIndex); + return TupleTypeCastUtils.readFloatValue(row, binaryTupleIndex, col.type().spec(), columnIndex); } /** {@inheritDoc} */ @@ -261,9 +249,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 +259,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 +267,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 +277,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 +287,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 +297,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 +307,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 +317,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 +327,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 +337,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 +347,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 +357,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 +367,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 +377,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 +387,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 +397,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 +407,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 +417,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)); }
