This is an automated email from the ASF dual-hosted git repository.
ppa pushed a commit to branch ignite-26491-temp
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/ignite-26491-temp by this push:
new 06d4712573f IGNITE-26491 Add support for reading from tuples with
allowed type casting (#7400)
06d4712573f is described below
commit 06d4712573ff1a6f2e603904dbe55d408524602b
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));
}