This is an automated email from the ASF dual-hosted git repository. amashenkov pushed a commit to branch gg-20650 in repository https://gitbox.apache.org/repos/asf/ignite-3.git
commit 1288724cf9e4749f09cb55ea4f8613a35831b7cd Author: amashenkov <[email protected]> AuthorDate: Thu May 30 15:07:20 2024 +0300 Tuple tests refactoring. --- .../main/java/org/apache/ignite/table/Tuple.java | 3 +- .../java/org/apache/ignite/table/TupleImpl.java | 3 +- .../ignite/internal/table/ItColumnMappingTest.java | 180 ++++++++ .../schema/marshaller/TupleMarshallerImpl.java | 3 +- .../apache/ignite/table/AbstractTupleSelfTest.java | 456 +++++++++++++++++++++ .../org/apache/ignite/table/TupleImplTest.java | 275 +------------ 6 files changed, 647 insertions(+), 273 deletions(-) diff --git a/modules/api/src/main/java/org/apache/ignite/table/Tuple.java b/modules/api/src/main/java/org/apache/ignite/table/Tuple.java index 4526f9335b..545b4c47bb 100644 --- a/modules/api/src/main/java/org/apache/ignite/table/Tuple.java +++ b/modules/api/src/main/java/org/apache/ignite/table/Tuple.java @@ -155,7 +155,8 @@ public interface Tuple extends Iterable<Object> { } for (int idx = 0; idx < columns; idx++) { - int idx2 = secondTuple.columnIndex(firstTuple.columnName(idx)); + // fix this hack + int idx2 = secondTuple.columnIndex("\""+ firstTuple.columnName(idx) +"\""); if (idx2 < 0) { return false; 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 998afb2b51..6c7fd8ee9c 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 @@ -78,7 +78,8 @@ class TupleImpl implements Tuple, Serializable { this(tuple.columnCount()); for (int i = 0, len = tuple.columnCount(); i < len; i++) { - set(tuple.columnName(i), tuple.value(i)); + // fix this hack + set("\"" + tuple.columnName(i) + "\"", tuple.value(i)); } } diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItColumnMappingTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItColumnMappingTest.java new file mode 100644 index 0000000000..6f0080f449 --- /dev/null +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItColumnMappingTest.java @@ -0,0 +1,180 @@ +/* + * 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. + */ + +/* + * 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.table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import javax.annotation.Nullable; +import org.apache.ignite.internal.ClusterPerClassIntegrationTest; +import org.apache.ignite.internal.app.IgniteImpl; +import org.apache.ignite.lang.MarshallerException; +import org.apache.ignite.table.KeyValueView; +import org.apache.ignite.table.RecordView; +import org.apache.ignite.table.Table; +import org.apache.ignite.table.Tuple; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class ItColumnMappingTest extends ClusterPerClassIntegrationTest { + @AfterEach + public void dropTables() { + for (Table t : CLUSTER.aliveNode().tables().tables()) { + sql("DROP TABLE " + t.name()); + } + } + + @Test + public void accessUnquotedColumnViaKvView() { + sql("CREATE TABLE test (" + + "id BIGINT PRIMARY KEY, " + + "ColumnName INT" + + ")"); + + IgniteImpl node = CLUSTER.aliveNode(); + Table table = node.tables().table("test"); + + KeyValueView<Tuple, Tuple> view = table.keyValueView(); + + view.put(null, Tuple.create().set("id", 1L), Tuple.create().set("columnname", 1)); + view.put(null, Tuple.create().set("id", 2L), Tuple.create().set("COLUMNNAME", 2)); + view.put(null, Tuple.create().set("id", 3L), Tuple.create().set("ColumnName", 3)); + + checkUnquotedColumn(1, view.get(null, Tuple.create().set("id", 1L))); + checkUnquotedColumn(2, view.get(null, Tuple.create().set("id", 2L))); + checkUnquotedColumn(3, view.get(null, Tuple.create().set("id", 3L))); + + view.put(null, Tuple.create().set("id", 4L), Tuple.create().set("\"COLUMNNAME\"", 4)); + checkUnquotedColumn(4, view.get(null, Tuple.create().set("id", 4L))); + + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 5L), Tuple.create().set("\"columnname\"", 5))); + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 6L), Tuple.create().set("\"columnName\"", 6))); + } + + @Disabled + @Test + public void accessUnquotedColumnViaRecordView() { + sql("CREATE TABLE test (" + + "id BIGINT PRIMARY KEY, " + + "ColumnName INT" + + ")"); + + IgniteImpl node = CLUSTER.aliveNode(); + Table table = node.tables().table("test"); + + RecordView<Tuple> view = table.recordView(); + + view.insert(null, Tuple.create().set("id", 1L).set("columnname", 1)); + view.insert(null, Tuple.create().set("id", 2L).set("COLUMNAME", 2)); + view.insert(null, Tuple.create().set("id", 3L).set("ColumnName", 3)); + + checkUnquotedColumn(1, view.get(null, Tuple.create().set("id", 1L))); + checkUnquotedColumn(2, view.get(null, Tuple.create().set("id", 2L))); + checkUnquotedColumn(3, view.get(null, Tuple.create().set("id", 3L))); + + view.insert(null, Tuple.create().set("id", 4L).set("\"columnname\"", 4)); + view.insert(null, Tuple.create().set("id", 5L).set("\"ColumnName\"", 5)); + view.insert(null, Tuple.create().set("id", 6L).set("\"COLUMNAME\"", 6)); + + checkUnquotedColumn(null, view.get(null, Tuple.create().set("id", 4L))); + checkUnquotedColumn(null, view.get(null, Tuple.create().set("id", 5L))); + checkUnquotedColumn(6, view.get(null, Tuple.create().set("id", 6L))); + } + + @Test + public void accessQuotedColumnViaKvView() { + sql("CREATE TABLE test (" + + "id BIGINT PRIMARY KEY, " + + "\"ColumnName\" INT" + + ")"); + + IgniteImpl node = CLUSTER.aliveNode(); + Table table = node.tables().table("test"); + + KeyValueView<Tuple, Tuple> view = table.keyValueView(); + + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 1L), Tuple.create().set("columnname", 1))); + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 2L), Tuple.create().set("COLUMNNAME", 2))); + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 3L), Tuple.create().set("ColumnName", 3))); + + assertNull(view.get(null, Tuple.create().set("id", 1L))); + assertNull(view.get(null, Tuple.create().set("id", 2L))); + assertNull(view.get(null, Tuple.create().set("id", 3L))); + + view.put(null, Tuple.create().set("id", 4L), Tuple.create().set("\"ColumnName\"", 4)); + + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 5L), Tuple.create().set("\"columnname\"", 5))); + assertThrows(MarshallerException.class, () -> + view.put(null, Tuple.create().set("id", 6L), Tuple.create().set("\"COLUMNNAME\"", 6))); + + checkQuotedColumn(4, view.get(null, Tuple.create().set("id", 4L))); + assertNull(view.get(null, Tuple.create().set("id", 5L))); + assertNull(view.get(null, Tuple.create().set("id", 6L))); + } + + private static void checkQuotedColumn(@Nullable Object expectedValue, @Nullable Tuple tuple) { + assertNotNull(tuple); + + assertNull(tuple.valueOrDefault("columnname", null)); + assertNull(tuple.valueOrDefault("COLUMNNAME", null)); + assertNull(tuple.valueOrDefault("ColumnName", null)); + + assertNull(tuple.valueOrDefault("\"columnname\"", null)); + assertNull(tuple.valueOrDefault("\"COLUMNNAME\"", null)); + assertEquals(expectedValue, tuple.valueOrDefault("\"ColumnName\"", null)); + } + + private static void checkUnquotedColumn(@Nullable Object expectedValue, @Nullable Tuple tuple) { + assertNotNull(tuple); + + assertEquals(expectedValue, tuple.valueOrDefault("columnname", null)); + assertEquals(expectedValue, tuple.valueOrDefault("COLUMNNAME", null)); + assertEquals(expectedValue, tuple.valueOrDefault("ColumnName", null)); + ; + + assertNull(tuple.valueOrDefault("\"columnname\"", null)); + assertEquals(expectedValue, tuple.valueOrDefault("\"COLUMNNAME\"", null)); + assertNull(tuple.valueOrDefault("\"ColumnName\"", null)); + } +} diff --git a/modules/table/src/main/java/org/apache/ignite/internal/schema/marshaller/TupleMarshallerImpl.java b/modules/table/src/main/java/org/apache/ignite/internal/schema/marshaller/TupleMarshallerImpl.java index 36b9b25e74..02a84b3181 100644 --- a/modules/table/src/main/java/org/apache/ignite/internal/schema/marshaller/TupleMarshallerImpl.java +++ b/modules/table/src/main/java/org/apache/ignite/internal/schema/marshaller/TupleMarshallerImpl.java @@ -174,7 +174,8 @@ public class TupleMarshallerImpl implements TupleMarshaller { for (Column col : columns) { NativeType colType = col.type(); - Object val = tuple.valueOrDefault(col.name(), POISON_OBJECT); + // fix this hack + Object val = tuple.valueOrDefault("\"" + col.name() + "\"", POISON_OBJECT); if (val == POISON_OBJECT && col.positionInKey() != -1) { throw new SchemaMismatchException("Missed key column: " + col.name()); diff --git a/modules/table/src/test/java/org/apache/ignite/table/AbstractTupleSelfTest.java b/modules/table/src/test/java/org/apache/ignite/table/AbstractTupleSelfTest.java new file mode 100644 index 0000000000..d1cc1fa53c --- /dev/null +++ b/modules/table/src/test/java/org/apache/ignite/table/AbstractTupleSelfTest.java @@ -0,0 +1,456 @@ +/* + * 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.table; + +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.temporal.Temporal; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.apache.ignite.internal.testframework.IgniteTestUtils; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Tuple interface test. + */ +public abstract class AbstractTupleSelfTest { + private static final int NANOS_IN_SECOND = 9; + private static final int TIMESTAMP_PRECISION = 6; + private static final int TIME_PRECISION = 0; + + private final Instant now; + private final BitSet bitSet; + private final byte[] bytes; + private final String string; + private final BigInteger bigInteger; + private final BigDecimal bigDecimal; + + { + Random rnd = new Random(); + + now = Instant.now(); + bitSet = randomBitSet(rnd, 12); + string = IgniteTestUtils.randomString(rnd, 14); + bytes = IgniteTestUtils.randomBytes(rnd, 13); + bigInteger = BigInteger.valueOf(rnd.nextLong()); + bigDecimal = BigDecimal.valueOf(rnd.nextLong(), 5); + } + + protected abstract Tuple createTuple(); + + /** + * Return {@code true} if tuple implementation is mutable, {@code false} otherwise. + * Note: when the method return {@code false}, then tests, which assumes relies on tuple mutability, will be skipped. + */ + protected boolean isMutable() { + return true; + } + + @Test + public void testValueReturnsValueByName() { + assertEquals(3L, (Long) getTuple().value("id")); + assertNull(getTuple().value("noValue")); + + // Ensure unquoted name is case-insensitive + assertEquals("simple", getTuple().value("simplename")); + assertEquals("simple", getTuple().value("SimpleName")); + assertEquals("simple", getTuple().value("SIMPLENAME")); + + assertEquals("simple", getTuple().value("\"SIMPLENAME\"")); + assertThrows(IllegalArgumentException.class, () -> getTuple().value("\"simplename\"")); + assertThrows(IllegalArgumentException.class, () -> getTuple().value("\"SimpleName\"")); + + // Ensure quoted name is case-sensitive + assertEquals("quoted", getTuple().value("\"QuotedName\"")); + assertThrows(IllegalArgumentException.class, () -> getTuple().value("\"Quotedname\"")); + assertThrows(IllegalArgumentException.class, () -> getTuple().value("\"QUOTEDNAME\"")); + + assertThrows(IllegalArgumentException.class, () -> getTuple().value("quotedname")); + assertThrows(IllegalArgumentException.class, () -> getTuple().value("QuotedName")); + assertThrows(IllegalArgumentException.class, () -> getTuple().value("QUOTEDNAME")); + } + + @Test + public void testValueOrDefault() { + assertEquals(3L, getTuple().valueOrDefault("id", 1L)); + assertNull(getTuple().valueOrDefault("noValue", "default")); + + // Ensure unquoted name is case-insensitive + assertEquals("simple", getTuple().valueOrDefault("simplename", "default")); + assertEquals("simple", getTuple().valueOrDefault("SimpleName", "default")); + ; + assertEquals("simple", getTuple().valueOrDefault("SIMPLENAME", "default")); + + assertEquals("simple", getTuple().valueOrDefault("\"SIMPLENAME\"", "default")); + assertEquals("default", getTuple().valueOrDefault("\"simplename\"", "default")); + assertEquals("default", getTuple().valueOrDefault("\"SimpleName\"", "default")); + + // Ensure quoted name is case-sensitive + assertEquals("quoted", getTuple().valueOrDefault("\"QuotedName\"", "default")); + assertEquals("default", getTuple().valueOrDefault("\"Quotedname\"", "default")); + assertEquals("default", getTuple().valueOrDefault("\"QUOTEDNAME\"", "default")); + + assertEquals("default", getTuple().valueOrDefault("quotedname", "default")); + assertEquals("default", getTuple().valueOrDefault("QuotedName", "default")); + assertEquals("default", getTuple().valueOrDefault("QUOTEDNAME", "default")); + } + + @Test + public void testValueThrowsOnInvalidColumnName() { + var ex = assertThrows(IllegalArgumentException.class, () -> getTuple().value("x")); + assertEquals("Column doesn't exist [name=x]", ex.getMessage()); + } + + @Test + public void testValueReturnsValueByIndex() { + assertEquals(3L, (Long) getTuple().value(0)); + assertEquals("simple", getTuple().value(1)); + assertEquals("quoted", getTuple().value(2)); + assertNull(getTuple().value(3)); + } + + @Test + public void testValueThrowsOnInvalidIndex() { + var ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().value(-1)); + assertEquals("Index -1 out of bounds for length 4", ex.getMessage()); + + ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().value(4)); + assertEquals("Index 4 out of bounds for length 4", ex.getMessage()); + } + + @Test + public void testValueOrDefaultReturnsValueByName() { + assertEquals(3L, getTuple().valueOrDefault("id", -1L)); + assertEquals("simple", getTuple().valueOrDefault("SimpleName", "y")); + assertNull(getTuple().valueOrDefault("noValue", "foo")); + } + + @Test + public void testValueOrDefaultReturnsDefaultWhenColumnIsNotSet() { + assertEquals("foo", getTuple().valueOrDefault("x", "foo")); + assertNull(getTuple().valueOrDefault("x", null)); + } + + @Test + public void testValueReturnsOverwrittenValue() { + Assumptions.assumeTrue(isMutable(), "Immutable Tuple implementation."); + + assertEquals("foo", getTuple().set("SimpleName", "foo").value("SimpleName")); + assertEquals("foo", getTuple().set("SimpleName", "foo").valueOrDefault("SimpleName", "bar")); + } + + @Test + public void testValueOrDefaultReturnsOverwrittenValue() { + Assumptions.assumeTrue(isMutable(), "Immutable Tuple implementation."); + + assertEquals("foo", getTuple().set("SimpleName", "foo").valueOrDefault("SimpleName", "bar")); + assertNull(getTuple().set("SimpleName", null).valueOrDefault("SimpleName", "foo")); + } + + @Test + public void testColumnCount() { + Tuple tuple = getTuple(); + + assertEquals(4, tuple.columnCount()); + assertEquals(4, tuple.set("id", -1).columnCount()); + + tuple.valueOrDefault("SimpleName", "foo"); + assertEquals(4, tuple.columnCount()); + + tuple.valueOrDefault("foo", "bar"); + assertEquals(4, tuple.columnCount()); + + } + + @Test + public void testColumnCountReturnsActualSchemaSize() { + Assumptions.assumeTrue(isMutable(), "Immutable Tuple implementation."); + + Tuple tuple = getTuple(); + + assertEquals(4, tuple.columnCount()); + + tuple.valueOrDefault("SimpleName", "foo"); + assertEquals(4, tuple.columnCount()); + + tuple.valueOrDefault("foo", "bar"); + assertEquals(4, tuple.columnCount()); + + tuple.set("foo", "bar"); + assertEquals(5, tuple.columnCount()); + } + + @Test + public void testColumnNameReturnsNameByIndex() { + assertEquals("ID", getTuple().columnName(0)); + assertEquals("SIMPLENAME", getTuple().columnName(1)); + assertEquals("QuotedName", getTuple().columnName(2)); + assertEquals("NOVALUE", getTuple().columnName(3)); + } + + @Test + public void testColumnNameThrowsOnInvalidIndex() { + var ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().columnName(-1)); + assertEquals("Index -1 out of bounds for length 4", ex.getMessage()); + + ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().columnName(4)); + assertEquals("Index 4 out of bounds for length 4", ex.getMessage()); + } + + @Test + public void testColumnIndexReturnsIndexByName() { + assertEquals(0, getTuple().columnIndex("id")); + assertEquals(1, getTuple().columnIndex("simplename")); + assertEquals(1, getTuple().columnIndex("SimpleName")); + assertEquals(1, getTuple().columnIndex("SIMPLENAME")); + assertEquals(1, getTuple().columnIndex("\"SIMPLENAME\"")); + assertEquals(2, getTuple().columnIndex("\"QuotedName\"")); + } + + @Disabled + @Test + public void testColumnResolvableName() { + Tuple tuple = getTuple(); + + assertEquals(0, tuple.columnIndex(tuple.columnName(0))); + assertEquals(1, tuple.columnIndex(tuple.columnName(1))); + assertEquals(2, tuple.columnIndex(tuple.columnName(2))); + assertEquals(3, tuple.columnIndex(tuple.columnName(3))); + } + + @Test + public void testColumnIndexForMissingColumns() { + assertEquals(-1, getTuple().columnIndex("foo")); + + assertEquals(-1, getTuple().columnIndex("\"simplename\"")); + assertEquals(-1, getTuple().columnIndex("\"SimpleName\"")); + + assertEquals(-1, getTuple().columnIndex("quotedname")); + assertEquals(-1, getTuple().columnIndex("QuotedName")); + assertEquals(-1, getTuple().columnIndex("QUOTEDNAME")); + assertEquals(-1, getTuple().columnIndex("\"quotedname\"")); + assertEquals(-1, getTuple().columnIndex("\"QUTEDNAME\"")); + } + + @Test + public void testBasicTupleEquality() { + assertEquals(getTuple(), getTuple()); + assertEquals(getTupleWithColumnOfAllTypes(), getTupleWithColumnOfAllTypes()); + } + + @Test + public void testMutableTupleEquality() { + Assumptions.assumeTrue(isMutable(), "Immutable Tuple implementation."); + + assertEquals(createTuple(), createTuple()); + assertEquals(createTuple().hashCode(), createTuple().hashCode()); + + assertEquals(createTuple().set("foo", null), createTuple().set("foo", null)); + assertEquals(createTuple().set("foo", null).hashCode(), createTuple().set("foo", null).hashCode()); + + assertEquals(createTuple().set("foo", "bar"), createTuple().set("foo", "bar")); + assertEquals(createTuple().set("foo", "bar").hashCode(), createTuple().set("foo", "bar").hashCode()); + + assertNotEquals(createTuple().set("foo", null), createTuple().set("bar", null)); + assertNotEquals(createTuple().set("foo", "foo"), createTuple().set("bar", "bar")); + + assertEquals(createTuple().set("foo", "bar"), createTuple().set("FOO", "bar")); + assertEquals(createTuple().set("foo", "bar"), createTuple().set("\"FOO\"", "bar")); + assertEquals(createTuple().set("\"foo\"", "bar"), createTuple().set("\"foo\"", "bar")); + + assertNotEquals(createTuple().set("foo", "foo"), createTuple().set("\"foo\"", "bar")); + assertNotEquals(createTuple().set("FOO", "foo"), createTuple().set("\"foo\"", "bar")); + + Tuple tuple = createTuple(); + Tuple tuple2 = createTuple(); + + assertEquals(tuple, tuple); + + tuple.set("foo", "bar"); + + assertEquals(tuple, tuple); + assertNotEquals(tuple, tuple2); + assertNotEquals(tuple2, tuple); + + tuple2.set("foo", "baz"); + + assertNotEquals(tuple, tuple2); + assertNotEquals(tuple2, tuple); + + tuple2.set("foo", "bar"); + + assertEquals(tuple, tuple2); + assertEquals(tuple2, tuple); + } + + @Test + public void testColumnNameCaseSensitivity() { + Tuple tuple = createTuple().set("foo", "bar").set("\"foo\"", "baz").set("\"Foo\"", null); + + assertEquals(3, tuple.columnCount()); + assertEquals("bar", tuple.value("foo")); + assertEquals("bar", tuple.value("Foo")); + assertEquals("bar", tuple.value("FOO")); + assertEquals("baz", tuple.value("\"foo\"")); + assertNull(tuple.value("\"Foo\"")); + assertNull(tuple.valueOrDefault("\"Foo\"", "baz")); + } + + @Test + public void testVariousColumnTypes() { + Random rnd = new Random(); + + Tuple tuple = getTupleWithColumnOfAllTypes(); + + for (int i = 0; i < tuple.columnCount(); i++) { + String name = tuple.columnName(i); + + if (tuple.value(i) instanceof byte[]) { + assertArrayEquals((byte[]) tuple.value(i), tuple.value(tuple.columnIndex(name)), "columnIdx=" + i); + } else { + assertEquals((Object) tuple.value(i), tuple.value(tuple.columnIndex(name)), "columnIdx=" + i); + } + } + } + + @Test + public void testTupleEqualityDifferentColumnOrder() { + Assumptions.assumeTrue(isMutable(), "Immutable Tuple implementation."); + + Random rnd = new Random(); + + Tuple tuple = getTupleWithColumnOfAllTypes(); + + List<Integer> randomIdx = IntStream.range(0, tuple.columnCount()).boxed().collect(Collectors.toList()); + + Collections.shuffle(randomIdx, rnd); + + Tuple shuffledTuple = createTuple(); + + for (Integer i : randomIdx) { + shuffledTuple.set(tuple.columnName(i), tuple.value(i)); + } + + assertEquals(tuple, shuffledTuple); + assertEquals(tuple.hashCode(), shuffledTuple.hashCode()); + } + + @Test + public void testSerialization() throws Exception { + Tuple tup1 = getTupleWithColumnOfAllTypes(); + Tuple tup2; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + try (ObjectOutputStream os = new ObjectOutputStream(baos)) { + os.writeObject(tup1); + } + + try (ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { + tup2 = (Tuple) is.readObject(); + } + + assertEquals(tup1, tup2); + } + + protected Tuple getTuple() { + return createTuple() + .set("id", 3L) + .set("SimpleName", "simple") + .set("\"QuotedName\"", "quoted") + .set("noValue", null); + } + + protected Tuple getTupleWithColumnOfAllTypes() { + return createTuple().set("valByteCol", (byte) 1) + .set("valShortCol", (short) 2) + .set("valIntCol", 3) + .set("valLongCol", 4L) + .set("valFloatCol", 0.055f) + .set("valDoubleCol", 0.066d) + .set("valDateCol", LocalDate.ofInstant(now, ZoneId.systemDefault())) + .set("valDateTimeCol", truncateToDefaultPrecision(LocalDateTime.ofInstant(now, ZoneId.systemDefault()))) + .set("valTimeCol", truncateToDefaultPrecision(LocalTime.ofInstant(now, ZoneId.systemDefault()))) + .set("valTimeStampCol", truncateToDefaultPrecision(now)) + .set("valBitmask1Col", bitSet) + .set("valBytesCol", bytes) + .set("valStringCol", string) + .set("valNumberCol", bigInteger) + .set("valDecimalCol", bigDecimal); + } + + /** + * Returns random BitSet. + * + * @param rnd Random generator. + * @param bits Amount of bits in bitset. + */ + private static BitSet randomBitSet(Random rnd, int bits) { + BitSet set = new BitSet(); + + for (int i = 0; i < bits; i++) { + if (rnd.nextBoolean()) { + set.set(i); + } + } + + return set; + } + + private <T extends Temporal> T truncateToDefaultPrecision(T temporal) { + int precision = temporal instanceof Instant ? TIMESTAMP_PRECISION + : TIME_PRECISION; + + return (T) temporal.with(NANO_OF_SECOND, + truncatePrecision(temporal.get(NANO_OF_SECOND), tailFactor(precision))); + } + + private int truncatePrecision(int nanos, int factor) { + return nanos / factor * factor; + } + + private int tailFactor(int precision) { + if (precision >= NANOS_IN_SECOND) { + return 1; + } + + return new BigInteger("10").pow(NANOS_IN_SECOND - precision).intValue(); + } +} diff --git a/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java b/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java index 910fa27c45..0a5d8a89ff 100644 --- a/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java +++ b/modules/table/src/test/java/org/apache/ignite/table/TupleImplTest.java @@ -17,32 +17,9 @@ package org.apache.ignite.table; -import static org.apache.ignite.internal.testframework.IgniteTestUtils.randomBitSet; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.Random; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.apache.ignite.internal.testframework.IgniteTestUtils; import org.junit.jupiter.api.Test; /** @@ -50,244 +27,10 @@ import org.junit.jupiter.api.Test; * * <p>Should be in sync with org.apache.ignite.client.ClientTupleBuilderTest. */ -public class TupleImplTest { - @Test - public void testValueReturnsValueByName() { - assertEquals(3L, (Long) getTuple().value("id")); - assertEquals("Shirt", getTuple().value("name")); - } - - @Test - public void testValueThrowsOnInvalidColumnName() { - var ex = assertThrows(IllegalArgumentException.class, () -> getTuple().value("x")); - assertEquals("Column doesn't exist [name=x]", ex.getMessage()); - } - - @Test - public void testValueReturnsValueByIndex() { - assertEquals(3L, (Long) getTuple().value(0)); - assertEquals("Shirt", getTuple().value(1)); - } - - @Test - public void testValueThrowsOnInvalidIndex() { - var ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().value(-1)); - assertEquals("Index -1 out of bounds for length 2", ex.getMessage()); - - ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().value(3)); - assertEquals("Index 3 out of bounds for length 2", ex.getMessage()); - } - - @Test - public void testValueOrDefaultReturnsValueByName() { - assertEquals(3L, getTuple().valueOrDefault("id", -1L)); - assertEquals("Shirt", getTuple().valueOrDefault("name", "y")); - } - - @Test - public void testValueOrDefaultReturnsDefaultWhenColumnIsNotSet() { - assertEquals("foo", createTuple().valueOrDefault("x", "foo")); - } - - @Test - public void testValueReturnsOverwrittenValue() { - assertEquals("foo", createTuple().set("name", "foo").value("name")); - assertEquals("foo", createTuple().set("name", "foo").valueOrDefault("name", "bar")); - } - - @Test - public void testValueOrDefaultReturnsNullWhenColumnIsSetToNull() { - assertNull(createTuple().set("name", null).valueOrDefault("name", "foo")); - - // Overwritten column. - assertNull(getTuple().set("name", null).valueOrDefault("name", "foo")); - } - - @Test - public void testColumnCountReturnsSchemaSize() { - assertEquals(0, createTuple().columnCount()); - - Tuple tuple = getTuple(); - - assertEquals(2, tuple.columnCount()); - assertEquals(2, tuple.set("id", -1).columnCount()); - - tuple.valueOrDefault("name", "foo"); - assertEquals(2, tuple.columnCount()); - - tuple.valueOrDefault("foo", "bar"); - assertEquals(2, tuple.columnCount()); - - tuple.set("foo", "bar"); - assertEquals(3, tuple.columnCount()); - } - - @Test - public void testColumnNameReturnsNameByIndex() { - assertEquals("ID", getTuple().columnName(0)); - assertEquals("NAME", getTuple().columnName(1)); - } - - @Test - public void testColumnNameThrowsOnInvalidIndex() { - var ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().columnName(-1)); - assertEquals("Index -1 out of bounds for length 2", ex.getMessage()); - - ex = assertThrows(IndexOutOfBoundsException.class, () -> getTuple().columnName(3)); - assertEquals("Index 3 out of bounds for length 2", ex.getMessage()); - } - - @Test - public void testColumnIndexReturnsIndexByName() { - assertEquals(0, getTuple().columnIndex("id")); - assertEquals(1, getTuple().columnIndex("name")); - } - - @Test - public void testColumnIndexForMissingColumns() { - assertEquals(-1, getTuple().columnIndex("foo")); - } - - @Test - public void testVariousColumnTypes() { - Random rnd = new Random(); - - Tuple tuple = new TupleImpl() - .set("valByteCol", (byte) 1) - .set("valShortCol", (short) 2) - .set("valIntCol", 3) - .set("valLongCol", 4L) - .set("valFloatCol", 0.055f) - .set("valDoubleCol", 0.066d) - .set("keyUuidCol", UUID.randomUUID()) - .set("valDateCol", LocalDate.now()) - .set("valDateTimeCol", LocalDateTime.now()) - .set("valTimeCol", LocalTime.now()) - .set("valTimeStampCol", Instant.now()) - .set("valBitmask1Col", randomBitSet(rnd, 12)) - .set("valBytesCol", IgniteTestUtils.randomBytes(rnd, 13)) - .set("valStringCol", IgniteTestUtils.randomString(rnd, 14)) - .set("valNumberCol", BigInteger.valueOf(rnd.nextLong())) - .set("valDecimalCol", BigDecimal.valueOf(rnd.nextLong(), 5)); - - for (int i = 0; i < tuple.columnCount(); i++) { - String name = tuple.columnName(i); - - if (tuple.value(i) instanceof byte[]) { - assertArrayEquals((byte[]) tuple.value(i), tuple.value(tuple.columnIndex(name)), "columnIdx=" + i); - } else { - assertEquals((Object) tuple.value(i), tuple.value(tuple.columnIndex(name)), "columnIdx=" + i); - } - } - } - - @Test - public void testBasicTupleEquality() { - assertEquals(new TupleImpl(), new TupleImpl()); - assertEquals(new TupleImpl().hashCode(), new TupleImpl().hashCode()); - - assertEquals(new TupleImpl().set("foo", null), new TupleImpl().set("foo", null)); - assertEquals(new TupleImpl().set("foo", null).hashCode(), new TupleImpl().set("foo", null).hashCode()); - - assertEquals(new TupleImpl().set("foo", "bar"), new TupleImpl().set("foo", "bar")); - assertEquals(new TupleImpl().set("foo", "bar").hashCode(), new TupleImpl().set("foo", "bar").hashCode()); - - assertNotEquals(new TupleImpl().set("foo", null), new TupleImpl().set("bar", null)); - assertNotEquals(new TupleImpl().set("foo", "foo"), new TupleImpl().set("bar", "bar")); - - TupleImpl tuple = new TupleImpl(); - final TupleImpl tuple2 = new TupleImpl(); - - assertEquals(tuple, tuple); - - tuple.set("foo", "bar"); - - assertEquals(tuple, tuple); - assertNotEquals(tuple, tuple2); - assertNotEquals(tuple2, tuple); - - tuple2.set("foo", "baz"); - - assertNotEquals(tuple, tuple2); - assertNotEquals(tuple2, tuple); - - tuple2.set("foo", "bar"); - - assertEquals(tuple, tuple2); - assertEquals(tuple2, tuple); - } - - @Test - public void testTupleEquality() { - Random rnd = new Random(); - - Tuple tuple = new TupleImpl() - .set("valByteCol", (byte) 1) - .set("valShortCol", (short) 2) - .set("valIntCol", 3) - .set("valLongCol", 4L) - .set("valFloatCol", 0.055f) - .set("valDoubleCol", 0.066d) - .set("keyUuidCol", UUID.randomUUID()) - .set("valDateCol", LocalDate.now()) - .set("valDateTimeCol", LocalDateTime.now()) - .set("valTimeCol", LocalTime.now()) - .set("valTimeStampCol", Instant.now()) - .set("valBitmask1Col", randomBitSet(rnd, 12)) - .set("valBytesCol", IgniteTestUtils.randomBytes(rnd, 13)) - .set("valStringCol", IgniteTestUtils.randomString(rnd, 14)) - .set("valNumberCol", BigInteger.valueOf(rnd.nextLong())) - .set("valDecimalCol", BigDecimal.valueOf(rnd.nextLong(), 5)); - - List<Integer> randomIdx = IntStream.range(0, tuple.columnCount()).boxed().collect(Collectors.toList()); - - Collections.shuffle(randomIdx, rnd); - - Tuple shuffledTuple = new TupleImpl(); - - for (Integer i : randomIdx) { - shuffledTuple.set(tuple.columnName(i), tuple.value(i)); - } - - assertEquals(tuple, shuffledTuple); - assertEquals(tuple.hashCode(), shuffledTuple.hashCode()); - } - - @Test - public void testSerialization() throws IOException, ClassNotFoundException { - Random rnd = new Random(); - - Tuple tup1 = new TupleImpl() - .set("valByteCol", (byte) 1) - .set("valShortCol", (short) 2) - .set("valIntCol", 3) - .set("valLongCol", 4L) - .set("valFloatCol", 0.055f) - .set("valDoubleCol", 0.066d) - .set("keyUuidCol", UUID.randomUUID()) - .set("valDateCol", LocalDate.now()) - .set("valDateTimeCol", LocalDateTime.now()) - .set("valTimeCol", LocalTime.now()) - .set("valTimeStampCol", Instant.now()) - .set("valBitmask1Col", randomBitSet(rnd, 12)) - .set("valBytesCol", IgniteTestUtils.randomBytes(rnd, 13)) - .set("valStringCol", IgniteTestUtils.randomString(rnd, 14)) - .set("valNumberCol", BigInteger.valueOf(rnd.nextLong())) - .set("valDecimalCol", BigDecimal.valueOf(rnd.nextLong(), 5)); - - Tuple tup2; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - try (ObjectOutputStream os = new ObjectOutputStream(baos)) { - os.writeObject(tup1); - } - - try (ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { - tup2 = (Tuple) is.readObject(); - } - - assertEquals(tup1, tup2); +public class TupleImplTest extends AbstractTupleSelfTest { + @Override + protected Tuple createTuple() { + return new TupleImpl(); } @Test @@ -300,15 +43,7 @@ public class TupleImplTest { assertEquals(Tuple.create().set("id", 42L).set("name", "universe"), Tuple.create(Tuple.create().set("id", 42L).set("name", "universe"))); - } - - private static TupleImpl createTuple() { - return new TupleImpl(); - } - private static Tuple getTuple() { - return new TupleImpl() - .set("id", 3L) - .set("name", "Shirt"); + assertEquals(getTuple(), Tuple.create(getTuple())); } }
