diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java index 6dff007ff7997..cb6c9593f2e0f 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExchangeServiceImpl.java @@ -360,6 +360,10 @@ private ExecutionContext baseInboxContext(UUID nodeId, UUID qryId, long fragm NoOpIoTracker.INSTANCE, 0, ImmutableMap.of(), - null); + null, + obj -> { + throw new UnsupportedOperationException("Unexpected method call."); + } + ); } } diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java index 41a477ef083c9..5f2a74639c32f 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java @@ -37,6 +37,7 @@ import org.apache.calcite.schema.SchemaPlus; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; +import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheObject; import org.apache.ignite.internal.processors.cache.GridCacheContext; @@ -148,10 +149,14 @@ public class ExecutionContext extends AbstractQueryContext implements DataC /** Entries holder per execution thread. */ private static final ThreadLocal> txEntriesHolder = new ThreadLocal<>(); + /** Binary marshaller. */ + private final Function binaryMarshaller; + /** * @param qctx Parent base query context. * @param qryId Query ID. * @param fragmentDesc Partitions information. + * @param binaryMarshaller Binary marshaller. * @param params Parameters. */ @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType") @@ -169,7 +174,8 @@ public ExecutionContext( IoTracker ioTracker, long timeout, Map params, - @Nullable Collection qryTxEntries + @Nullable Collection qryTxEntries, + Function binaryMarshaller ) { super(qctx); @@ -186,6 +192,7 @@ public ExecutionContext( this.params = params; this.timeout = timeout; this.qryTxEntries = qryTxEntries == null ? txEntriesHolder.get() : qryTxEntries; + this.binaryMarshaller = binaryMarshaller; startTs = U.currentTimeMillis(); @@ -199,6 +206,11 @@ public ExecutionContext( ); } + /** Binary marshaller. */ + public Function binaryMarshaller() { + return binaryMarshaller; + } + /** * @return Query ID. */ @@ -302,11 +314,16 @@ public IgniteLogger logger() { return baseDataContext.get(name); } - /** */ + /** Returns a parameter with internal representation or {@link BinaryObject}. */ public Object getParameter(String name, Type storageType) { assert name.startsWith("?") : name; - return TypeUtils.toInternal(this, params.get(name), storageType); + Object param = params.get(name); + + if (param != null && storageType == java.lang.Object.class && !(param instanceof BinaryObject)) + return binaryMarshaller.apply(param); + + return TypeUtils.toInternal(this, param, storageType); } /** diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java index 19fbeadb46396..bb8a9607338bd 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java @@ -210,6 +210,9 @@ public class ExecutionServiceImpl extends AbstractService implements Execut /** */ private final Map fragmentPlanCache = new GridBoundedConcurrentLinkedHashMap<>(1024); + /** Binary marshaller. */ + private Function binaryMarshaller; + /** * @param ctx Kernal. */ @@ -472,6 +475,10 @@ public void injectService(InjectResourcesService injectSvc) { udfQryLimit.set(ctx.config().getQueryThreadPoolSize() - 1); + binaryMarshaller = obj -> { + return ctx.cacheObjects().binary().toBinary(obj); + }; + init(); } @@ -683,7 +690,9 @@ private ListFieldsQueryCursor mapAndExecutePlan( createIoTracker(locNodeId, qry.localQueryId()), timeout, qryParams, - userTx == null ? null : ExecutionContext.transactionChanges(userTx.writeEntries())); + userTx == null ? null : ExecutionContext.transactionChanges(userTx.writeEntries()), + binaryMarshaller + ); Node node = new LogicalRelImplementor<>(ectx, partitionService(), mailboxRegistry(), exchangeService(), failureProcessor()).go(fragment.root()); @@ -960,7 +969,8 @@ private void onMessage(UUID nodeId, final QueryStartRequest msg) { createIoTracker(nodeId, msg.originatingQueryId()), msg.timeout(), Commons.parametersMap(msg.parameters()), - msg.queryTransactionEntries() + msg.queryTransactionEntries(), + binaryMarshaller ); executeFragment(qry, fragmentPlan, ectx); diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java index 38034bc93caa0..3903b4a3c126a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java @@ -146,9 +146,9 @@ private int[] fieldToInlinedKeysMapping() { for (InlineIndexKeyType keyType : inlinedKeys) { // Variable length types can be not fully inlined, so it's probably better to directly read full cache row - // instead of trying to read inlined value and than falllback to cache row reading. + // instead of trying to read inlined value and then fallback to cache row reading. // Inlined JAVA_OBJECT can't be compared with fill cache row in case of hash collision, this can lead to - // issues when processing the next index page in cursor if current page was concurrently splitted. + // issues when processing the next index page in cursor if current page was concurrently splited. if (keyType.keySize() < 0 || keyType.type() == IndexKeyType.JAVA_OBJECT) return null; } @@ -204,7 +204,7 @@ protected TreeIndex treeIndex() { } /** From Row to IndexRow convertor. */ - protected IndexRow row2indexRow(Row bound) { + protected @Nullable IndexRow row2indexRow(Row bound) { if (bound == null) return null; diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java index 98b2462238108..9cee57020f5b2 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java @@ -20,8 +20,8 @@ import java.util.Map; import org.apache.calcite.util.ImmutableBitSet; import org.apache.calcite.util.ImmutableIntList; -import org.apache.ignite.IgniteException; import org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.binary.BinaryObjectException; import org.apache.ignite.internal.cache.query.index.sorted.IndexKeyDefinition; import org.apache.ignite.internal.cache.query.index.sorted.IndexKeyType; import org.apache.ignite.internal.cache.query.index.sorted.IndexPlainRowImpl; @@ -54,7 +54,7 @@ public IndexWrappedKeyScan( } /** */ - @Override protected IndexRow row2indexRow(Row bound) { + @Override @Nullable protected IndexRow row2indexRow(Row bound) { if (bound == null) return null; @@ -63,22 +63,29 @@ public IndexWrappedKeyScan( Object key = rowHnd.get(QueryUtils.KEY_COL, bound); assert key != null : String.format("idxName=%s, bound=%s", idx.name(), Commons.toString(rowHnd, bound)); - if (key instanceof BinaryObject) - return binaryObject2indexRow((BinaryObject)key); + String idxTypeName = idx.indexDefinition().typeDescriptor().keyTypeName(); - throw new IgniteException(String.format( - "Unsupported type for index boundary: [expected=%s, current=%s]", - BinaryObject.class.getName(), key.getClass().getName() - )); + if (key instanceof BinaryObject bo) { + try { + String searchTypeName = bo.type().typeName(); + + if (!idxTypeName.equals(searchTypeName)) + // The same behavior as for table scan. + return null; + } + catch (BinaryObjectException ex) { + // The same behavior as for table scan. + return null; + } + + return binaryObject2indexRow(bo); + } + else + throw new AssertionError("Invalid types for comparison: %s %s".formatted(idxTypeName, key.getClass().getName())); } /** */ private IndexRow binaryObject2indexRow(BinaryObject o) { - assert o.type().typeName().equals(idx.indexDefinition().typeDescriptor().keyTypeName()) : String.format( - "idx=%s, o=%s, oType=%s, idxKeyType=%s", - idx.name(), o, o.type().typeName(), idx.indexDefinition().typeDescriptor().keyTypeName() - ); - InlineIndexRowHandler idxRowHnd = idx.segment(0).rowHandler(); IndexKey[] keys = new IndexKey[idx.indexDefinition().indexKeyDefinitions().size()]; diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java index 3ff4bc544504c..5eff8d4016156 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java @@ -743,7 +743,7 @@ else if (rel instanceof Intersect) RowFactory rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType); - return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(rowType, dataSupplier, rowFactory)); + return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(rowType, dataSupplier, rowFactory, ctx.binaryMarshaller())); } /** {@inheritDoc} */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java index b29f91d6a7fe8..ebe3e372be068 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Iterator; +import java.util.function.Function; import java.util.function.Supplier; import org.apache.calcite.rel.type.RelDataType; import org.apache.ignite.internal.processors.query.IgniteSQLException; @@ -36,15 +37,24 @@ public class TableFunctionScan implements Iterable { /** */ private final RowFactory rowFactory; + /** */ + Function binaryMarshaller; + + /** */ + private static final String ERR_SIZE_TEMPLATE = "Unable to process table function data: row length [%d]" + + " doesn't match defined columns number [%d]."; + /** */ public TableFunctionScan( RelDataType rowType, Supplier> dataSupplier, - RowFactory rowFactory + RowFactory rowFactory, + Function marshaller ) { this.rowType = rowType; this.dataSupplier = dataSupplier; this.rowFactory = rowFactory; + binaryMarshaller = marshaller; } /** {@inheritDoc} */ @@ -52,18 +62,32 @@ public TableFunctionScan( return F.iterator(dataSupplier.get(), this::convertToRow, true); } + /** */ + private static void rowSizeChecker(int rowSize, int fldCount) { + if (rowSize != fldCount) + throw new IgniteSQLException(ERR_SIZE_TEMPLATE.formatted(rowSize, fldCount)); + } + /** */ private Row convertToRow(Object rowContainer) { if (rowContainer.getClass() != Object[].class && !Collection.class.isAssignableFrom(rowContainer.getClass())) throw new IgniteSQLException("Unable to process table function data: row type is neither Collection or Object[]."); - Object[] rowArr = rowContainer.getClass() == Object[].class - ? (Object[])rowContainer - : ((Collection)rowContainer).toArray(); + if (rowContainer instanceof Object[]) + rowSizeChecker(((Object[])rowContainer).length, rowType.getFieldCount()); + else + rowSizeChecker(((Collection)rowContainer).size(), rowType.getFieldCount()); - if (rowArr.length != rowType.getFieldCount()) { - throw new IgniteSQLException("Unable to process table function data: row length [" + rowArr.length - + "] doesn't match defined columns number [" + rowType.getFieldCount() + "]."); + Object[] rowArr; + + if (rowContainer.getClass().isArray()) { + rowArr = (Object[])rowContainer; + for (int pos = 0; pos < rowArr.length; ++pos) + rowArr[pos] = binaryMarshaller.apply(rowArr[pos]); + } + else { + Collection coll = (Collection)rowContainer; + rowArr = coll.stream().map(e -> binaryMarshaller.apply(e)).toArray(); } return rowFactory.create(rowArr); diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java index be9dc99330df0..aa5b0d18916b0 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java @@ -119,7 +119,7 @@ public static BigDecimal toBigDecimal(boolean val, int precision, int scale) { } /** CAST(VARCHAR AS DECIMAL). */ - public static BigDecimal toBigDecimal(String s, int precision, int scale) { + public static @Nullable BigDecimal toBigDecimal(String s, int precision, int scale) { if (s == null) return null; @@ -127,7 +127,7 @@ public static BigDecimal toBigDecimal(String s, int precision, int scale) { } /** Converts {@code val} to a {@link BigDecimal} with the given {@code precision} and {@code scale}. */ - public static BigDecimal toBigDecimal(Number val, int precision, int scale) { + public static @Nullable BigDecimal toBigDecimal(Number val, int precision, int scale) { assert precision > 0 : "Invalid precision: " + precision; assert scale >= 0 : "Invalid scale: " + scale; @@ -146,7 +146,7 @@ public static BigDecimal toBigDecimal(Number val, int precision, int scale) { } /** Cast object depending on type to DECIMAL. */ - public static BigDecimal toBigDecimal(Object o, int precision, int scale) { + public static @Nullable BigDecimal toBigDecimal(Object o, int precision, int scale) { if (o == null) return null; diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java index 2cd164bc81570..6483e99f56b2e 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableDescriptorImpl.java @@ -395,7 +395,7 @@ private Object insertVal(Row row, ExecutionContext ectx) throws Ignit } /** */ - private Object newVal(String typeName) throws IgniteCheckedException { + private Object newVal(String typeName) { GridCacheContext cctx = cacheContext(); BinaryObjectBuilder builder = cctx.grid().binary().builder(typeName); diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java index 6056f2bbc7c30..91ae3bcd8d60f 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java @@ -355,12 +355,12 @@ public static boolean hasScale(RelDataType type) { } /** */ - public static Object toInternal(DataContext ctx, Object val) { + public static @Nullable Object toInternal(DataContext ctx, Object val) { return val == null ? null : toInternal(ctx, val, val.getClass()); } /** */ - public static Object toInternal(DataContext ctx, Object val, Type storageType) { + public static @Nullable Object toInternal(DataContext ctx, Object val, Type storageType) { if (val == null) return null; else if (storageType == java.sql.Date.class) diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java index 06a00e8622f8d..43ea02d52d4ea 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java @@ -133,7 +133,10 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest { NoOpIoTracker.INSTANCE, 0, null, - null + null, + obj -> { + throw new UnsupportedOperationException("Unexpected method call."); + } ) { @Override public ColocationGroup group(long srcId) { return ColocationGroup.forNodes(Collections.emptyList()); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndexTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndexTest.java index 66dfd58362f74..d8eeb5e1d0285 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndexTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndexTest.java @@ -121,7 +121,10 @@ private RuntimeSortedIndex generate(RelDataType rowType, final List { + throw new UnsupportedOperationException("Unexpected method call."); + }), RelCollations.of(ImmutableIntList.copyOf(idxCols)), (o1, o2) -> { for (int colIdx : idxCols) { diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java index b7f5880259be2..82f5b3dddea1c 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractExecutionTest.java @@ -364,7 +364,10 @@ protected ExecutionContext executionContext(UUID nodeId, UUID qryId, l NoOpIoTracker.INSTANCE, 0, ImmutableMap.of(), - null + null, + obj -> { + throw new UnsupportedOperationException("Unexpected method call."); + } ); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java index 9a3c56c98fe2b..a35ac54aca1a5 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java @@ -26,12 +26,8 @@ import org.apache.ignite.internal.processors.query.calcite.QueryChecker; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.internal.S; -import org.hamcrest.CoreMatchers; -import org.jetbrains.annotations.Nullable; -import org.junit.Ignore; import org.junit.Test; -import static java.util.stream.Collectors.toList; import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_FIELD_NAME; import static org.apache.ignite.internal.processors.query.QueryUtils.PRIMARY_KEY_INDEX; @@ -40,15 +36,18 @@ * {@link QueryUtils#PRIMARY_KEY_INDEX pk index}. */ public class SelectByKeyFieldTest extends AbstractBasicIntegrationTest { + /** Table size. */ + private static final int TABLE_SIZE = 10; + /** {@inheritDoc} */ @Override protected int nodeCount() { - return 1; + return 2; } /** */ @Test public void testSimplePk() { - checkSimplePk(null); + checkSimplePk(() -> {}); } /** */ @@ -63,152 +62,19 @@ public void testSimplePkAfterDropColumn() { checkSimplePk(this::executeAlterTableDropColumn); } - /** */ - @Test - public void testCompositePk() { - checkCompositePk(false, true, null); - } - - /** */ - @Test - public void testCompositePkSearchByPartOfKey() { - sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); - - for (int i = 0; i < 10; i++) { - sql( - "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", - i, "foo" + i, "bar" + i, 18 + i - ); - } - - List> sqlRs = sql("select _key, id, name from PUBLIC.PERSON order by id"); - BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0); - int id = (Integer)sqlRs.get(6).get(1); - String name = (String)sqlRs.get(6).get(2); - - assertQuery("select id, name, age, _key from PUBLIC.PERSON where id = ?") - .withParams(id) - .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX)) - .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME) - .returns(id, name, 24, _key) - .check(); - - assertQuery("select id, name, age, _key from PUBLIC.PERSON where name = ?") - .withParams(name) - .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) - .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME) - .returns(id, name, 24, _key) - .check(); - - assertQuery("select name, age from PUBLIC.PERSON where name = ?") - .withParams(name) - .matches(CoreMatchers.not(QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX))) - .columnNames("NAME", "AGE") - .returns(name, 24) - .check(); - } - - /** */ - @Test - public void testCompositePkAfterAddColumn() { - checkCompositePk(false, true, this::executeAlterTableAddColumn); - } - - /** */ - @Test - public void testCompositePkAfterDropColumn() { - checkCompositePk(false, true, this::executeAlterTableDropColumn); - } - - /** */ - @Test - public void testCompositePkWithKeyTypeAndBinaryObject() { - checkCompositePk(true, true, null); - } - - /** */ - @Test - @Ignore("https://issues.apache.org/jira/browse/IGNITE-28374") - public void testCompositePkWithKeyTypeAndPersonCompositeKey() { - checkCompositePk(true, false, null); - } - - /** */ - @Test - public void testCompositePkWithOrderByKey() { - sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); - - for (int i = 0; i < 10; i++) { - sql( - "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", - i, "foo" + i, "bar" + i, 18 + i - ); - } - - List> sqlRs = sql("select id, name, age, _key from PUBLIC.PERSON"); - sqlRs.sort((o1, o2) -> binaryObjectCmpForDml(o1.get(3), o2.get(3))); - - QueryChecker qryChecker = assertQuery("select id, name, age, _key from PUBLIC.PERSON order by _key") - .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) - .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME); - - sqlRs.forEach(objects -> qryChecker.returns(objects.toArray(Object[]::new))); - - qryChecker.check(); - } - - /** */ - @Test - public void testCompositePkWithDifferentCmpOperations() { - checkCompositePkWithDifferentCmpOperations(true); - } - - /** */ - @Test - @Ignore("https://issues.apache.org/jira/browse/IGNITE-28374") - public void testCompositePkWithPersonCompositeKeyAndDifferentCmpOperations() { - checkCompositePkWithDifferentCmpOperations(false); - } - - /** */ - @Test - public void testCompositePkWithMinMaxByKey() { - sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); - - for (int i = 0; i < 10; i++) { - sql( - "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", - i, "foo" + i, "bar" + i, 18 + i - ); - } - - List> sqlRs = sql("select _key from PUBLIC.PERSON order by id"); - List min = sqlRs.stream().min((o1, o2) -> binaryObjectCmpForDml(o1.get(0), o2.get(0))).orElseThrow(); - List max = sqlRs.stream().max((o1, o2) -> binaryObjectCmpForDml(o1.get(0), o2.get(0))).orElseThrow(); - - assertQuery("select min(_key) from PUBLIC.PERSON") - .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) - .columnNames("MIN(_KEY)") - .returns(min.get(0)) - .check(); - - assertQuery("select max(_key) from PUBLIC.PERSON") - .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) - .columnNames("MAX(_KEY)") - .returns(max.get(0)) - .check(); - } - - /** */ - private void checkSimplePk(@Nullable Runnable executeBeforeChecks) { + /** + * Checks simple primary key search functionality. + * Creates a table with a simple primary key on {@code id} column, fills it with test data, + * and verifies that queries using {@code _key} or {@code id} correctly use index scan + * and return expected results. + * + * @param executeBeforeChecks Runnable to execute before performing checks, allowing + * for custom setup or validation logic. + */ + private void checkSimplePk(Runnable executeBeforeChecks) { sql("create table PUBLIC.PERSON(id int primary key, name varchar, surname varchar, age int)"); - for (int i = 0; i < 10; i++) { - sql( - "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", - i, "foo" + i, "bar" + i, 18 + i - ); - } + fillTable(); List> sqlRs = sql("select _key, id from PUBLIC.PERSON order by id"); int _key = (Integer)sqlRs.get(7).get(0); @@ -217,8 +83,7 @@ private void checkSimplePk(@Nullable Runnable executeBeforeChecks) { assertEquals(7, _key); assertEquals(7, id); - if (executeBeforeChecks != null) - executeBeforeChecks.run(); + executeBeforeChecks.run(); assertQuery("select id, name, age, _key from PUBLIC.PERSON where _key = ?") .withParams(_key) @@ -244,11 +109,41 @@ private void checkSimplePk(@Nullable Runnable executeBeforeChecks) { .check(); } + /** */ + @Test + public void testCompositePk() { + checkCompositePk(false, true, () -> {}); + } + + /** */ + @Test + public void testCompositePkAfterAddColumn() { + checkCompositePk(false, true, this::executeAlterTableAddColumn); + } + + /** */ + @Test + public void testCompositePkAfterDropColumn() { + checkCompositePk(false, true, this::executeAlterTableDropColumn); + } + + /** */ + @Test + public void testCompositePkWithKeyTypeAndBinaryObject() { + checkCompositePk(true, true, () -> {}); + } + + /** */ + @Test + public void testCompositePkWithKeyTypeAndPersonCompositeKey() { + checkCompositePk(true, false, () -> {}); + } + /** */ private void checkCompositePk( boolean setKeyTypeToCreateTblDdl, boolean useBinaryObject, - @Nullable Runnable executeBeforeChecks + Runnable executeBeforeChecks ) { if (setKeyTypeToCreateTblDdl) { // Order of the primary key columns has been deliberately changed. @@ -261,12 +156,7 @@ private void checkCompositePk( else sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); - for (int i = 0; i < 10; i++) { - sql( - "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", - i, "foo" + i, "bar" + i, 18 + i - ); - } + fillTable(); List> sqlRs = sql("select _key, id, name from PUBLIC.PERSON order by id"); BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0); @@ -276,8 +166,7 @@ private void checkCompositePk( assertEquals(6, id); assertEquals("foo6", name); - if (executeBeforeChecks != null) - executeBeforeChecks.run(); + executeBeforeChecks.run(); assertQuery("select id, name, age, _key from PUBLIC.PERSON where _key = ?") .withParams(useBinaryObject ? _key : _key.deserialize()) @@ -304,32 +193,158 @@ private void checkCompositePk( } /** */ - private void checkCompositePkWithDifferentCmpOperations(boolean useBinaryObject) { + @Test + public void testCompositePkSearchByPartOfKeyTableScan() { + compositePkEqualitySearchByPartOfKey(true); + } + + /** */ + @Test + public void testCompositePkSearchByPartOfKeyIdxScan() { + compositePkEqualitySearchByPartOfKey(false); + } + + /** + * Tests composite primary key equality search using only part of the key (single column). + * Verifies that queries with equality comparison on either the {@code id} or {@code name} column + * return correct results using either table scan or index scan depending on the {@code tableScan} flag. + * + * @param tableScan {@code true} to test with table scan, {@code false} to test with index scan. + */ + public void compositePkEqualitySearchByPartOfKey(boolean tableScan) { sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); - for (int i = 0; i < 10; i++) { - sql( - "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", - i, "foo" + i, "bar" + i, 18 + i - ); - } + fillTable(); + + List> sqlRs = sql("select _key, id, name from PUBLIC.PERSON order by id"); + BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0); + int id = (Integer)sqlRs.get(6).get(1); + String name = (String)sqlRs.get(6).get(2); + + // Select by uniq id. + assertQuery("select /*+ DISABLE_RULE('" + (tableScan ? "LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") + + "') */ id, name, age, _key from PUBLIC.PERSON where id = ?") + .withParams(id) + .matches(tableScan ? + QueryChecker.containsTableScan("PUBLIC", "PERSON") : + QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX) + ) + .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME) + .returns(id, name, 24, _key) + .check(); + + // Select by uniq name. + assertQuery("select /*+ DISABLE_RULE('" + (tableScan ? "LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") + + "') */ id, name, age, _key from PUBLIC.PERSON where name = ?") + .withParams(name) + .matches(tableScan ? + QueryChecker.containsTableScan("PUBLIC", "PERSON") : + QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX) + ) + .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME) + .returns(id, name, 24, _key) + .check(); + } + + /** */ + @Test + public void testCompositePkWithOrderByKeyTableScan() { + compositePkWithOrderByKey(true); + } + + /** */ + @Test + public void testCompositePkWithOrderByKeyIdxScan() { + compositePkWithOrderByKey(false); + } + + /** + * Tests composite primary key ordering by {@code _key}. + * Verifies that ordering by the composite primary key produces correct results + * when comparing binary objects using {@link #binaryObjectCmpForDml}. + */ + public void compositePkWithOrderByKey(boolean tableScan) { + sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); + + fillTable(); + + List> sqlRs = sql("select id, name, age, _key from PUBLIC.PERSON"); + sqlRs.sort((o1, o2) -> binaryObjectCmpForDml(o1.get(3), o2.get(3))); + + QueryChecker qryChecker = assertQuery("select /*+ DISABLE_RULE('" + + (tableScan ? "LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") + + "') */ id, name, age, _key from PUBLIC.PERSON order by _key") + .matches(tableScan ? + QueryChecker.containsTableScan("PUBLIC", "PERSON") : + QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX) + ) + .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME); + + sqlRs.forEach(objects -> qryChecker.returns(objects.toArray(Object[]::new))); + + qryChecker.check(); + } + + /** */ + @Test + public void testBinaryCompositePkComparisonsWithTableScan() { + checkCompositePkWithDifferentCmpOperations(true, true); + } + + /** */ + @Test + public void testBinaryCompositePkComparisonsWithIdxScan() { + checkCompositePkWithDifferentCmpOperations(true, false); + } + + /** */ + @Test + public void testJavaObjCompositePkComparisonsWithTableScan() { + checkCompositePkWithDifferentCmpOperations(false, true); + } + + /** */ + @Test + public void testJavaObjCompositePkComparisonsWithIdxScan() { + checkCompositePkWithDifferentCmpOperations(false, false); + } + + /** + * Checks composite primary key comparisons with different comparison operations. + * Creates a table with composite key (id, name) using {@link PersonCompositeKey}, inserts test data, + * and verifies that all comparison operations (EQ, NE, LT, LE, GT, GE) work correctly with both table scan + * and index scan query strategies. + * + * @param useBinaryObject {@code true} to use binary object representation for key comparison, + * {@code false} to use deserialized object. + * @param tableScan {@code true} to test with table scan, {@code false} to test with index scan. + */ + private void checkCompositePkWithDifferentCmpOperations(boolean useBinaryObject, boolean tableScan) { + sql(String.format( + "create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name)) with \"key_type=%s\"", + PersonCompositeKey.class.getName() + )); + + fillTable(); List> sqlRs = sql("select id, name, age, _key from PUBLIC.PERSON order by id"); - BinaryObjectImpl _key8 = (BinaryObjectImpl)sqlRs.get(8).get(3); - for (CmpOp cmpOp : CmpOp.values()) { - if (cmpOp == CmpOp.EQ) - continue; + Object key8 = sqlRs.get(8).get(3); + for (CmpOp cmpOp : CmpOp.values()) { List> expRows = sqlRs.stream() - .filter(objects -> cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), _key8)) - .collect(toList()); + .filter(objects -> cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), key8)) + .toList(); QueryChecker qryChecker = assertQuery(String.format( - "select id, name, age, _key from PUBLIC.PERSON where _key %s ?", cmpOp.sql + "select /*+ DISABLE_RULE('" + (tableScan ? "LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") + + "') */ id, name, age, _key from PUBLIC.PERSON where _key %s ?", cmpOp.comp )) - .withParams(useBinaryObject ? _key8 : _key8.deserialize()) - .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) + .withParams(useBinaryObject ? key8 : ((BinaryObject)key8).deserialize()) + .matches(tableScan ? + QueryChecker.containsTableScan("PUBLIC", "PERSON") : + QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX) + ) .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME); expRows.forEach(objects -> qryChecker.returns(objects.toArray(Object[]::new))); @@ -338,6 +353,104 @@ private void checkCompositePkWithDifferentCmpOperations(boolean useBinaryObject) } } + /** */ + @Test + public void testResultsWithUnexpectedParam() { + sql( + "create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name)) with \"key_type=%s\"" + .formatted("UndefinedClassName") + ); + + checkResultsWithUnexpectedKey(new Object()); + } + + /** */ + @Test + public void testResultsWithoutKeyTypeWithUnexpectedParam() { + sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); + + checkResultsWithUnexpectedKey(new Object()); + } + + /** */ + @Test + public void testResultsWithoutKeyTypeWithUnexpectedBOAsParam() { + sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); + + Object arg = client.binary().builder("UserDefinedBinary").build(); + + checkResultsWithUnexpectedKey(arg); + } + + /** Check results with search for _key different from defined in schema. */ + private void checkResultsWithUnexpectedKey(Object arg) { + String qryTemplate = "select /*+ DISABLE_RULE('%s') */ _key from PUBLIC.PERSON where _key %s ? ORDER BY _key"; + + fillTable(); + + for (CmpOp cmpOp : CmpOp.values()) { + List> res = null; + + for (boolean tableScan : List.of(true, false)) { + String qry = qryTemplate.formatted(tableScan ? + "LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule", cmpOp.comp); + + if (res == null) + res = sql(qry, arg); + else { + List> secondRes = sql(qry, arg); + + assertEquals("Different results size", res.size(), secondRes.size()); + + for (int pos = 0; pos < secondRes.size(); pos++) + assertEquals(res.get(pos).get(0), secondRes.get(pos).get(0)); + } + + assertQuery(qry) + .withParams(arg) + .matches(tableScan ? + QueryChecker.containsTableScan("PUBLIC", "PERSON") : + QueryChecker.containsIndexScan("PUBLIC", "PERSON", PRIMARY_KEY_INDEX) + ) + .check(); + } + } + } + + /** */ + @Test + public void testCompositePkWithMinMaxByKey() { + sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar, age int, primary key(id, name))"); + + fillTable(); + + List> sqlRs = sql("select _key from PUBLIC.PERSON order by id"); + List min = sqlRs.stream().min((o1, o2) -> binaryObjectCmpForDml(o1.get(0), o2.get(0))).orElseThrow(); + List max = sqlRs.stream().max((o1, o2) -> binaryObjectCmpForDml(o1.get(0), o2.get(0))).orElseThrow(); + + assertQuery("select min(_key) from PUBLIC.PERSON") + .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) + .columnNames("MIN(_KEY)") + .returns(min.get(0)) + .check(); + + assertQuery("select max(_key) from PUBLIC.PERSON") + .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON")) + .columnNames("MAX(_KEY)") + .returns(max.get(0)) + .check(); + } + + /** */ + private void fillTable() { + for (int i = 0; i < TABLE_SIZE; i++) { + sql( + "insert into PUBLIC.PERSON(id, name, surname, age) values (?, ?, ?, ?)", + i, "foo" + i, "bar" + i, 18 + i + ); + } + } + /** */ public static class PersonCompositeKey { /** */ @@ -376,7 +489,7 @@ public static class PersonCompositeKey { @FunctionalInterface private interface ExpRowByKeyPredicate { /** */ - boolean test(BinaryObjectImpl rowKey, BinaryObjectImpl targetKey); + boolean test(BinaryObjectImpl rowKey, Object targetKey); } /** */ @@ -400,14 +513,14 @@ private enum CmpOp { NE("<>", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey, targetKey) != 0); /** */ - private final String sql; + private final String comp; /** */ private final ExpRowByKeyPredicate expRowByKeyPred; /** */ - CmpOp(String sql, ExpRowByKeyPredicate expRowByKeyPred) { - this.sql = sql; + CmpOp(String comp, ExpRowByKeyPredicate expRowByKeyPred) { + this.comp = comp; this.expRowByKeyPred = expRowByKeyPred; } } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java index 278b3bac52f7d..19b72f7e8e954 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java @@ -332,15 +332,18 @@ public void testTableFunctions() throws Exception { assertThrows("SELECT * from raiseException(?, ?, ?)", RuntimeException.class, "Test exception", 1, "test", true); + var empObj1 = new Employer("emp1", 1000d); + var empObj10 = new Employer("emp10", 10000d); + // Object type. - assertQuery("SELECT * from withObjectType(1)") - .returns(1, new Employer("emp1", 1000d)) - .returns(10, new Employer("emp10", 10000d)) + assertQuery("SELECT * from withObjectType(1) ORDER BY ID") + .returns(1, client.binary().toBinary(empObj1)) + .returns(10, client.binary().toBinary(empObj10)) .check(); assertQuery("SELECT * from withObjectType(1) where EMP=?") - .withParams(new Employer("emp10", 10000d)) - .returns(10, new Employer("emp10", 10000d)) + .withParams(empObj10) + .returns(10, client.binary().toBinary(empObj10)) .check(); } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java index 75530ae030454..e0b13b092ddba 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java @@ -364,7 +364,10 @@ private Node implementFragment( NoOpIoTracker.INSTANCE, 0, Commons.parametersMap(ctx.parameters()), - null + null, + obj -> { + throw new UnsupportedOperationException("Unexpected method call."); + } ); return new LogicalRelImplementor<>(ectx, c -> r -> 0, mailboxRegistry, exchangeSvc, diff --git a/modules/core/src/main/java/org/apache/ignite/internal/plugin/IgniteLogInfoProviderImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/plugin/IgniteLogInfoProviderImpl.java index 8678793f62478..1d897cfd6a911 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/plugin/IgniteLogInfoProviderImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/plugin/IgniteLogInfoProviderImpl.java @@ -178,7 +178,7 @@ void ackAsciiLogo(IgniteLogger log, IgniteConfiguration cfg, RuntimeMXBean rtBea " __________ ________________ ", " / _/ ___/ |/ / _/_ __/ __/ ", " _/ // (7 7 // / / / / _/ ", - "/___/\\___/_/|_/___/ /_/ /x___/ ", + "/___/\\___/_/|_/___/ /_/ /___/ ", "", ver, COPYRIGHT,