This is an automated email from the ASF dual-hosted git repository.
tkalkirill pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 8bbb7571c5e IGNITE-28331 Fix SQL search by _key for a composite pk in
Calcite engine (#12926)
8bbb7571c5e is described below
commit 8bbb7571c5e8997d46c93ca3d5cd258d873620fe
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Tue Apr 7 08:03:56 2026 +0300
IGNITE-28331 Fix SQL search by _key for a composite pk in Calcite engine
(#12926)
---
.../query/calcite/exec/IndexWrappedKeyScan.java | 105 +++++
.../calcite/exec/exp/ExpressionFactoryImpl.java | 15 +-
.../query/calcite/exec/exp/IgniteSqlFunctions.java | 49 +++
.../query/calcite/exec/exp/RexImpTable.java | 2 +-
.../query/calcite/exec/exp/agg/Accumulators.java | 39 +-
.../query/calcite/schema/CacheIndexImpl.java | 118 ++++--
.../calcite/schema/CacheWrappedKeyIndexImpl.java | 100 +++++
.../query/calcite/schema/SchemaHolderImpl.java | 27 +-
.../processors/query/calcite/util/Commons.java | 30 ++
.../query/calcite/integration/DataTypesTest.java | 4 +-
.../calcite/integration/SelectByKeyFieldTest.java | 429 +++++++++++++++++++++
.../ignite/testsuites/IntegrationTestSuite.java | 3 +
.../query/schema/management/IndexDescriptor.java | 5 +
13 files changed, 868 insertions(+), 58 deletions(-)
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
new file mode 100644
index 00000000000..98b24622381
--- /dev/null
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java
@@ -0,0 +1,105 @@
+/*
+ * 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.processors.query.calcite.exec;
+
+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.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;
+import org.apache.ignite.internal.cache.query.index.sorted.IndexRow;
+import
org.apache.ignite.internal.cache.query.index.sorted.InlineIndexRowHandler;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
+import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKey;
+import
org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKeyFactory;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import
org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
+import
org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
+import
org.apache.ignite.internal.processors.query.calcite.schema.ColumnDescriptor;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
+import org.jetbrains.annotations.Nullable;
+
+/** Extension for column {@value QueryUtils#KEY_FIELD_NAME} in case of
composite primary key. */
+public class IndexWrappedKeyScan<Row> extends IndexScan<Row> {
+ /** */
+ public IndexWrappedKeyScan(
+ ExecutionContext<Row> ectx,
+ CacheTableDescriptor desc,
+ InlineIndex idx,
+ ImmutableIntList idxFieldMapping,
+ int[] parts,
+ RangeIterable<Row> ranges,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ super(ectx, desc, idx, idxFieldMapping, parts, ranges,
requiredColumns);
+ }
+
+ /** */
+ @Override protected IndexRow row2indexRow(Row bound) {
+ if (bound == null)
+ return null;
+
+ RowHandler<Row> rowHnd = ectx.rowHandler();
+
+ 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);
+
+ throw new IgniteException(String.format(
+ "Unsupported type for index boundary: [expected=%s, current=%s]",
+ BinaryObject.class.getName(), 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()];
+
+ int i = 0;
+ for (Map.Entry<String, IndexKeyDefinition> e :
idx.indexDefinition().indexKeyDefinitions().entrySet()) {
+ String keyName = e.getKey();
+
+ ColumnDescriptor fieldDesc = desc.columnDescriptor(keyName);
+ assert fieldDesc != null : String.format("idx=%s, o=%s,
keyName=%s", idx.name(), o, keyName);
+
+ Object field = o.field(keyName);
+ Object key = TypeUtils.fromInternal(ectx, field,
fieldDesc.storageType());
+
+ keys[i++] = wrapIndexKey(key, e.getValue().indexKeyType());
+ }
+
+ return new IndexPlainRowImpl(keys, idxRowHnd);
+ }
+
+ /** */
+ private IndexKey wrapIndexKey(Object key, IndexKeyType keyType) {
+ return IndexKeyFactory.wrap(key, keyType, cctx.cacheObjectContext(),
idx.indexDefinition().keyTypeSettings());
+ }
+}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
index 8e828f5720d..4af8a6bda03 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
@@ -33,7 +33,7 @@ import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Primitives;
import org.apache.calcite.DataContext;
@@ -46,6 +46,7 @@ import org.apache.calcite.linq4j.tree.MethodDeclaration;
import org.apache.calcite.linq4j.tree.ParameterExpression;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.type.RelDataType;
@@ -81,6 +82,8 @@ import
org.apache.ignite.internal.processors.query.calcite.util.IgniteMethod;
import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap;
import org.apache.ignite.internal.util.typedef.F;
+import static java.util.stream.Collectors.toList;
+
/**
* Implements rex expression into a function object. Uses JaninoRexCompiler
under the hood.
* Each expression compiles into a class and a wrapper over it is returned.
@@ -338,6 +341,14 @@ public class ExpressionFactoryImpl<Row> implements
ExpressionFactory<Row> {
List<RangeConditionImpl> ranges = new ArrayList<>();
+ if (collation.getKeys().isEmpty()) {
+ collation = RelCollations.of(IntStream.range(0,
searchBounds.size())
+ .filter(i -> searchBounds.get(i) != null)
+ .mapToObj(RelFieldCollation::new)
+ .collect(toList())
+ );
+ }
+
Comparator<Row> rowComparator = comparator(collation);
expandBounds(
@@ -1003,7 +1014,7 @@ public class ExpressionFactoryImpl<Row> implements
ExpressionFactory<Row> {
// should not affect ordering.
if (!sorted) {
ranges = ranges.stream().filter(r ->
!r.skip()).sorted(RangeConditionImpl::compareTo)
- .collect(Collectors.toList());
+ .collect(toList());
List<RangeConditionImpl> ranges0 = new
ArrayList<>(ranges.size());
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 cfca232e4c4..be9dc99330d 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
@@ -27,6 +27,7 @@ import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.schema.ScannableTable;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.Statistic;
@@ -293,4 +294,52 @@ public class IgniteSqlFunctions {
return true;
}
}
+
+ /** SQL >=. */
+ public static boolean geAny(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b) >= 0;
+
+ return SqlFunctions.geAny(a, b);
+ }
+
+ /** SQL >. */
+ public static boolean gtAny(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b) > 0;
+
+ return SqlFunctions.gtAny(a, b);
+ }
+
+ /** SQL <=. */
+ public static boolean leAny(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b) <= 0;
+
+ return SqlFunctions.leAny(a, b);
+ }
+
+ /** SQL <. */
+ public static boolean ltAny(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b) < 0;
+
+ return SqlFunctions.ltAny(a, b);
+ }
+
+ /** SQL =. */
+ public static boolean eqAny(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b) == 0;
+
+ return SqlFunctions.eqAny(a, b);
+ }
+
+ /** SQL <>. */
+ public static boolean neAny(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b) != 0;
+
+ return SqlFunctions.neAny(a, b);
+ }
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java
index f1b5802b702..0772d7287d4 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java
@@ -1298,7 +1298,7 @@ public class RexImpTable {
// one or both of parameter(s) is(are) ANY type
final Expression expression0 = maybeBox(expressions.get(0));
final Expression expression1 = maybeBox(expressions.get(1));
- return Expressions.call(SqlFunctions.class,
backupMethodNameForAnyType,
+ return Expressions.call(IgniteSqlFunctions.class,
backupMethodNameForAnyType,
expression0, expression1);
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
index 4dee4cc83b4..9c15e4a7a23 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
@@ -38,6 +38,7 @@ import org.apache.calcite.sql.type.SqlTypeName;
import
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.util.typedef.F;
import static org.apache.calcite.sql.type.SqlTypeName.ANY;
@@ -234,7 +235,11 @@ public class Accumulators {
return () -> new ComparableMinMax<Row, UUID>(call, hnd, true,
tf ->
tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.UUID), true));
case ANY:
- throw new UnsupportedOperationException("MIN() is not
supported for type '" + call.type + "'.");
+ return () -> new ComparableMinMax<>(call, hnd, true,
+ tf -> tf.createTypeWithNullability(tf.createSqlType(ANY),
true));
+ case OTHER:
+ return () -> new ComparableMinMax<>(call, hnd, true,
+ tf ->
tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.OTHER), true));
case BIGINT:
default:
return () -> new LongMinMax<>(call, hnd, true);
@@ -263,7 +268,11 @@ public class Accumulators {
return () -> new ComparableMinMax<Row, UUID>(call, hnd, false,
tf ->
tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.UUID), true));
case ANY:
- throw new UnsupportedOperationException("MAX() is not
supported for type '" + call.type + "'.");
+ return () -> new ComparableMinMax<>(call, hnd, false,
+ tf -> tf.createTypeWithNullability(tf.createSqlType(ANY),
true));
+ case OTHER:
+ return () -> new ComparableMinMax<>(call, hnd, false,
+ tf ->
tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.OTHER), true));
case BIGINT:
default:
return () -> new LongMinMax<>(call, hnd, false);
@@ -1116,7 +1125,7 @@ public class Accumulators {
}
/** */
- private static class ComparableMinMax<Row, T extends Comparable<T>>
extends AbstractAccumulator<Row> {
+ private static class ComparableMinMax<Row, T> extends
AbstractAccumulator<Row> {
/** */
private final boolean min;
@@ -1149,8 +1158,8 @@ public class Accumulators {
return;
val = empty ? in : min ?
- (val.compareTo(in) < 0 ? val : in) :
- (val.compareTo(in) < 0 ? in : val);
+ (compare(val, in) < 0 ? val : in) :
+ (compare(val, in) < 0 ? in : val);
empty = false;
}
@@ -1163,8 +1172,8 @@ public class Accumulators {
return;
val = empty ? other0.val : min ?
- (val.compareTo(other0.val) < 0 ? val : other0.val) :
- (val.compareTo(other0.val) < 0 ? other0.val : val);
+ (compare(val, other0.val) < 0 ? val : other0.val) :
+ (compare(val, other0.val) < 0 ? other0.val : val);
empty = false;
}
@@ -1183,6 +1192,22 @@ public class Accumulators {
@Override public RelDataType returnType(IgniteTypeFactory typeFactory)
{
return typeSupplier.apply(typeFactory);
}
+
+ /** */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private int compare(Object a, Object b) {
+ if (Commons.isBinaryComparable(a, b))
+ return Commons.compareBinary(a, b);
+
+ if (a.getClass() != b.getClass()) {
+ throw new UnsupportedOperationException(String.format(
+ "%s() is not supported for different value types:
[type0=%s, type1=%s]",
+ min ? "MIN" : "MAX", a.getClass().getName(),
b.getClass().getName()
+ ));
+ }
+
+ return ((Comparable)a).compareTo(b);
+ }
}
/** */
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheIndexImpl.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheIndexImpl.java
index 3806436eb87..992247f55fa 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheIndexImpl.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheIndexImpl.java
@@ -50,16 +50,16 @@ import org.jetbrains.annotations.Nullable;
*/
public class CacheIndexImpl implements IgniteIndex {
/** */
- private final RelCollation collation;
+ protected final RelCollation collation;
/** */
- private final String idxName;
+ protected final String idxName;
/** */
- private final @Nullable Index idx;
+ protected final @Nullable Index idx;
/** */
- private final IgniteCacheTable tbl;
+ protected final IgniteCacheTable tbl;
/** */
public CacheIndexImpl(RelCollation collation, String name, @Nullable Index
idx, IgniteCacheTable tbl) {
@@ -97,10 +97,8 @@ public class CacheIndexImpl implements IgniteIndex {
@Nullable ImmutableBitSet requiredColumns
) {
UUID locNodeId = execCtx.localNodeId();
- if (grp.nodeIds().contains(locNodeId) && idx != null) {
- return new IndexScan<>(execCtx, tbl.descriptor(),
idx.unwrap(InlineIndex.class), collation.getKeys(),
- grp.partitions(locNodeId), ranges, requiredColumns);
- }
+ if (grp.nodeIds().contains(locNodeId) && idx != null)
+ return createIndexScan(execCtx, grp, ranges, requiredColumns);
return Collections.emptyList();
}
@@ -113,18 +111,8 @@ public class CacheIndexImpl implements IgniteIndex {
@Nullable ImmutableBitSet requiredColumns
) {
UUID locNodeId = ectx.localNodeId();
-
- if (grp.nodeIds().contains(locNodeId) && idx != null) {
- return new IndexFirstLastScan<>(
- first,
- ectx,
- tbl.descriptor(),
- idx.unwrap(InlineIndexImpl.class),
- collation.getKeys(),
- grp.partitions(locNodeId),
- requiredColumns
- );
- }
+ if (grp.nodeIds().contains(locNodeId) && idx != null)
+ return createIndexFirstLastScan(first, ectx, grp, requiredColumns);
return Collections.emptyList();
}
@@ -147,24 +135,9 @@ public class CacheIndexImpl implements IgniteIndex {
@Nullable RexNode cond,
@Nullable ImmutableBitSet requiredColumns
) {
- RelCollation collation = this.collation;
RelDataType rowType = tbl.getRowType(cluster.getTypeFactory());
- if (requiredColumns != null)
- collation = collation.apply(Commons.mapping(requiredColumns,
rowType.getFieldCount()));
-
- if (!collation.getFieldCollations().isEmpty()) {
- return RexUtils.buildSortedSearchBounds(
- cluster,
- collation,
- cond,
- rowType,
- requiredColumns
- );
- }
-
- // Empty index find predicate.
- return null;
+ return buildSearchBounds(cluster, cond, rowType, requiredColumns);
}
/** {@inheritDoc} */
@@ -197,4 +170,77 @@ public class CacheIndexImpl implements IgniteIndex {
return true;
}
+
+ /** */
+ protected @Nullable List<SearchBounds> buildSearchBounds(
+ RelOptCluster cluster,
+ @Nullable RexNode cond,
+ RelDataType rowType,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ RelCollation collation = mapByRequireColumns(this.collation, rowType,
requiredColumns);
+
+ if (collation.getFieldCollations().isEmpty())
+ return null; // Empty index find predicate.
+
+ return RexUtils.buildSortedSearchBounds(cluster, collation, cond,
rowType, requiredColumns);
+ }
+
+ /** */
+ protected <Row> IndexScan<Row> createIndexScan(
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ RangeIterable<Row> ranges,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ return new IndexScan<>(
+ ectx,
+ tbl.descriptor(),
+ idx.unwrap(InlineIndex.class),
+ collation.getKeys(),
+ grp.partitions(ectx.localNodeId()),
+ ranges,
+ requiredColumns
+ );
+ }
+
+ /** */
+ protected <Row> IndexScan<Row> createIndexFirstLastScan(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ return new IndexFirstLastScan<>(
+ first,
+ ectx,
+ tbl.descriptor(),
+ idx.unwrap(InlineIndexImpl.class),
+ collation.getKeys(),
+ grp.partitions(ectx.localNodeId()),
+ requiredColumns
+ );
+ }
+
+ /** */
+ protected CacheIndexImpl copy(IgniteCacheTable newTbl) {
+ return new CacheIndexImpl(collation, idxName, idx, newTbl);
+ }
+
+ /** */
+ protected CacheIndexImpl copy(IgniteCacheTable newTbl, RelCollation
newCollation) {
+ return new CacheIndexImpl(newCollation, idxName, idx, newTbl);
+ }
+
+ /** */
+ static RelCollation mapByRequireColumns(
+ RelCollation collation,
+ RelDataType rowType,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ if (requiredColumns != null)
+ collation = collation.apply(Commons.mapping(requiredColumns,
rowType.getFieldCount()));
+
+ return collation;
+ }
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheWrappedKeyIndexImpl.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheWrappedKeyIndexImpl.java
new file mode 100644
index 00000000000..60c87ef5bbe
--- /dev/null
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheWrappedKeyIndexImpl.java
@@ -0,0 +1,100 @@
+/*
+ * 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.processors.query.calcite.schema;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.cache.query.index.Index;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.IndexScan;
+import
org.apache.ignite.internal.processors.query.calcite.exec.IndexWrappedKeyScan;
+import
org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
+import
org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
+import
org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
+import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
+import org.jetbrains.annotations.Nullable;
+
+/** Extension for column {@value QueryUtils#KEY_FIELD_NAME} in case of
composite primary key. */
+class CacheWrappedKeyIndexImpl extends CacheIndexImpl {
+ /** */
+ CacheWrappedKeyIndexImpl(RelCollation collation, String idxName, Index
idx, IgniteCacheTable tbl) {
+ super(collation, idxName, idx, tbl);
+ }
+
+ /** */
+ @Override protected @Nullable List<SearchBounds> buildSearchBounds(
+ RelOptCluster cluster,
+ @Nullable RexNode cond,
+ RelDataType rowType,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ if (cond != null) {
+ List<SearchBounds> bounds =
RexUtils.buildHashSearchBounds(cluster, cond, rowType, requiredColumns, true);
+
+ if (bounds != null && bounds.get(QueryUtils.KEY_COL) != null)
+ return bounds;
+ }
+
+ return null; // Empty index find predicate.
+ }
+
+ /** */
+ @Override protected <Row> IndexScan<Row> createIndexScan(
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ RangeIterable<Row> ranges,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ return new IndexWrappedKeyScan<>(
+ ectx,
+ tbl.descriptor(),
+ idx.unwrap(InlineIndex.class),
+ collation.getKeys(),
+ grp.partitions(ectx.localNodeId()),
+ ranges,
+ requiredColumns
+ );
+ }
+
+ /** */
+ @Override protected <Row> IndexScan<Row> createIndexFirstLastScan(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ throw new IgniteException(String.format("Should not be created for
wrappped %s index", QueryUtils.KEY_FIELD_NAME));
+ }
+
+ /** */
+ @Override protected CacheIndexImpl copy(IgniteCacheTable newTbl) {
+ return new CacheWrappedKeyIndexImpl(collation, idxName, idx, newTbl);
+ }
+
+ /** */
+ @Override protected CacheIndexImpl copy(IgniteCacheTable newTbl,
RelCollation newCollation) {
+ return new CacheWrappedKeyIndexImpl(collation, idxName, idx, newTbl);
+ }
+}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
index b9eae82520f..0c5b793a830 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SchemaHolderImpl.java
@@ -55,6 +55,7 @@ import
org.apache.ignite.internal.processors.query.calcite.util.AbstractService;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.processors.query.schema.SchemaChangeListener;
import
org.apache.ignite.internal.processors.query.schema.management.IndexDescriptor;
+import
org.apache.ignite.internal.processors.query.schema.management.SchemaManager;
import
org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.lang.IgnitePredicate;
@@ -203,16 +204,13 @@ public class SchemaHolderImpl extends AbstractService
implements SchemaHolder, S
List<QueryField> cols
) {
IgniteCacheTable oldTbl = table(schemaName, typeDesc.tableName());
- assert oldTbl != null;
+ assert oldTbl != null : String.format("schemaName=%s, tableName=%s",
schemaName, typeDesc.tableName());
IgniteCacheTable newTbl = createTable(typeDesc, cacheInfo);
// Recreate indexes for the new table without columns shift.
- for (IgniteIndex idx : oldTbl.indexes().values()) {
- CacheIndexImpl idx0 = (CacheIndexImpl)idx;
-
- newTbl.addIndex(new CacheIndexImpl(idx0.collation(), idx0.name(),
idx0.queryIndex(), newTbl));
- }
+ for (IgniteIndex idx : oldTbl.indexes().values())
+ newTbl.addIndex(((CacheIndexImpl)idx).copy(newTbl));
publishTable(schemaName, typeDesc.tableName(), newTbl);
}
@@ -225,7 +223,7 @@ public class SchemaHolderImpl extends AbstractService
implements SchemaHolder, S
List<String> cols
) {
IgniteCacheTable oldTbl = table(schemaName, typeDesc.tableName());
- assert oldTbl != null;
+ assert oldTbl != null : String.format("schemaName=%s, tableName=%s",
schemaName, typeDesc.tableName());
IgniteCacheTable newTbl = createTable(typeDesc, cacheInfo);
@@ -242,8 +240,9 @@ public class SchemaHolderImpl extends AbstractService
implements SchemaHolder, S
for (IgniteIndex idx : oldTbl.indexes().values()) {
CacheIndexImpl idx0 = (CacheIndexImpl)idx;
- newTbl.addIndex(new
CacheIndexImpl(RelCollations.permute(idx0.collation(), mapping), idx0.name(),
- idx0.queryIndex(), newTbl));
+ RelCollation newCollation =
RelCollations.permute(idx0.collation(), mapping);
+
+ newTbl.addIndex(idx0.copy(newTbl, newCollation));
}
publishTable(schemaName, typeDesc.tableName(), newTbl);
@@ -301,12 +300,20 @@ public class SchemaHolderImpl extends AbstractService
implements SchemaHolder, S
IndexDescriptor idxDesc
) {
IgniteCacheTable tbl = table(schemaName, tblName);
- assert tbl != null;
+ assert tbl != null : String.format("schemaName=%s, tableName=%s,
idxName=%s", schemaName, tblName, idxName);
RelCollation idxCollation = deriveSecondaryIndexCollation(idxDesc,
tbl);
IgniteIndex idx = new CacheIndexImpl(idxCollation, idxName,
idxDesc.index(), tbl);
tbl.addIndex(idx);
+
+ // For a composite PK index, we need to create another proxy index
that will expand the passed boundaries into
+ // index keys for BinaryObject/Key classes. That is, _key ->
idx_field_0, idx_field_1, etc.
+ if (idxDesc.isPk() && idxDesc.isComposite()) {
+ tbl.addIndex(new CacheWrappedKeyIndexImpl(
+ RelCollations.EMPTY,
SchemaManager.generateProxyIdxName(idxName), idxDesc.index(), tbl
+ ));
+ }
}
/**
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
index c0fc0697716..fc91eb44832 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/Commons.java
@@ -57,8 +57,10 @@ import org.apache.calcite.util.mapping.MappingType;
import org.apache.calcite.util.mapping.Mappings;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.internal.GridComponent;
import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.binary.BinaryUtils;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
@@ -74,6 +76,7 @@ import
org.apache.ignite.internal.processors.query.calcite.rel.IgniteProject;
import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.codehaus.commons.compiler.CompilerFactoryFactory;
import org.codehaus.commons.compiler.IClassBodyEvaluator;
@@ -539,4 +542,31 @@ public final class Commons {
public static @Nullable GridCacheVersion queryTransactionVersion(Context
ctx) {
return ctx.unwrap(GridCacheVersion.class);
}
+
+ /** Returns a string representation of the contents of the specified row.
*/
+ public static <Row> String toString(RowHandler<Row> hnd, @Nullable Row r) {
+ if (r == null)
+ return "null";
+
+ SB sb = new SB('[');
+
+ for (int i = 0; i < hnd.columnCount(r); i++) {
+ if (i != 0)
+ sb.a(", ");
+
+ sb.a(hnd.get(i, r));
+ }
+
+ return sb.a(']').toString();
+ }
+
+ /** Checks if objects can be compared as {@link BinaryObject} via {@link
#compareBinary(Object, Object)}. */
+ public static boolean isBinaryComparable(Object o1, Object o2) {
+ return BinaryUtils.isBinaryObjectImpl(o1) ||
BinaryUtils.isBinaryObjectImpl(o2);
+ }
+
+ /** Compares {@link BinaryObject} for DML operation. Before use, call
{@link #isBinaryComparable(Object, Object)}. */
+ public static int compareBinary(Object o1, Object o2) {
+ return BinaryUtils.binariesFactory.compareForDml(o1, o2);
+ }
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
index 8e0a973731a..e3f9d5e7cd9 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
@@ -168,10 +168,10 @@ public class DataTypesTest extends
AbstractBasicIntegrationTransactionalTest {
.check();
assertThrows("SELECT MIN(oth) FROM t",
UnsupportedOperationException.class,
- "MIN() is not supported for type 'OTHER'.");
+ "MIN() is not supported for different value types");
assertThrows("SELECT MAX(oth) FROM t",
UnsupportedOperationException.class,
- "MAX() is not supported for type 'OTHER'.");
+ "MAX() is not supported for different value types");
assertThrows("SELECT AVG(oth) from t",
UnsupportedOperationException.class,
"AVG() is not supported for type 'OTHER'.");
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
new file mode 100644
index 00000000000..9a3c56c98fe
--- /dev/null
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java
@@ -0,0 +1,429 @@
+/*
+ * 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.processors.query.calcite.integration;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.internal.binary.BinaryObjectImpl;
+import org.apache.ignite.internal.binary.BinaryUtils;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+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;
+
+/**
+ * Checks that using {@link QueryUtils#KEY_FIELD_NAME} in condition will use
+ * {@link QueryUtils#PRIMARY_KEY_INDEX pk index}.
+ */
+public class SelectByKeyFieldTest extends AbstractBasicIntegrationTest {
+ /** {@inheritDoc} */
+ @Override protected int nodeCount() {
+ return 1;
+ }
+
+ /** */
+ @Test
+ public void testSimplePk() {
+ checkSimplePk(null);
+ }
+
+ /** */
+ @Test
+ public void testSimplePkAfterAddColumn() {
+ checkSimplePk(this::executeAlterTableAddColumn);
+ }
+
+ /** */
+ @Test
+ 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<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<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<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) {
+ 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
+ );
+ }
+
+ List<List<?>> sqlRs = sql("select _key, id from PUBLIC.PERSON order by
id");
+ int _key = (Integer)sqlRs.get(7).get(0);
+ int id = (Integer)sqlRs.get(7).get(1);
+
+ assertEquals(7, _key);
+ assertEquals(7, id);
+
+ if (executeBeforeChecks != null)
+ executeBeforeChecks.run();
+
+ assertQuery("select id, name, age, _key from PUBLIC.PERSON where _key
= ?")
+ .withParams(_key)
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX))
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, "foo7", 25, _key)
+ .check();
+
+ // Let's check with a smaller number of columns.
+ assertQuery("select id, age, _key from PUBLIC.PERSON where _key = ?")
+ .withParams(_key)
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX))
+ .columnNames("ID", "AGE", KEY_FIELD_NAME)
+ .returns(id, 25, _key)
+ .check();
+
+ // Let's just make sure that PK search is not broken.
+ 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, "foo7", 25, _key)
+ .check();
+ }
+
+ /** */
+ private void checkCompositePk(
+ boolean setKeyTypeToCreateTblDdl,
+ boolean useBinaryObject,
+ @Nullable Runnable executeBeforeChecks
+ ) {
+ if (setKeyTypeToCreateTblDdl) {
+ // Order of the primary key columns has been deliberately changed.
+ sql(String.format(
+ "create table PUBLIC.PERSON(id int, name varchar, surname
varchar, age int, primary key(name, id)) " +
+ "with \"key_type=%s\"",
+ PersonCompositeKey.class.getName()
+ ));
+ }
+ 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
+ );
+ }
+
+ List<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);
+
+ assertEquals(6, id);
+ assertEquals("foo6", name);
+
+ if (executeBeforeChecks != null)
+ executeBeforeChecks.run();
+
+ assertQuery("select id, name, age, _key from PUBLIC.PERSON where _key
= ?")
+ .withParams(useBinaryObject ? _key : _key.deserialize())
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX + "_proxy"))
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+
+ // Let's check with a smaller number of columns.
+ assertQuery("select id, age, _key from PUBLIC.PERSON where _key = ?")
+ .withParams(useBinaryObject ? _key : _key.deserialize())
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX + "_proxy"))
+ .columnNames("ID", "AGE", KEY_FIELD_NAME)
+ .returns(id, 24, _key)
+ .check();
+
+ // Let's just make sure that PK search is not broken.
+ assertQuery("select id, name, age, _key from PUBLIC.PERSON where id =
? and name = ?")
+ .withParams(id, name)
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX))
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+ }
+
+ /** */
+ private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject) {
+ 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<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;
+
+ List<List<?>> expRows = sqlRs.stream()
+ .filter(objects ->
cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), _key8))
+ .collect(toList());
+
+ QueryChecker qryChecker = assertQuery(String.format(
+ "select id, name, age, _key from PUBLIC.PERSON where _key %s
?", cmpOp.sql
+ ))
+ .withParams(useBinaryObject ? _key8 : _key8.deserialize())
+ .matches(QueryChecker.containsTableScan("PUBLIC", "PERSON"))
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME);
+
+ expRows.forEach(objects ->
qryChecker.returns(objects.toArray(Object[]::new)));
+
+ qryChecker.check();
+ }
+ }
+
+ /** */
+ public static class PersonCompositeKey {
+ /** */
+ @GridToStringInclude
+ public int id;
+
+ /** */
+ @GridToStringInclude
+ public String name;
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(id, name);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (!(obj instanceof PersonCompositeKey))
+ return false;
+
+ PersonCompositeKey that = (PersonCompositeKey)obj;
+
+ return id == that.id && name.equals(that.name);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(PersonCompositeKey.class, this);
+ }
+ }
+
+ /** */
+ @FunctionalInterface
+ private interface ExpRowByKeyPredicate {
+ /** */
+ boolean test(BinaryObjectImpl rowKey, BinaryObjectImpl targetKey);
+ }
+
+ /** */
+ private enum CmpOp {
+ /** */
+ GE(">=", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey,
targetKey) >= 0),
+
+ /** */
+ GT(">", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey,
targetKey) > 0),
+
+ /** */
+ LE("<=", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey,
targetKey) <= 0),
+
+ /** */
+ LT("<", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey,
targetKey) < 0),
+
+ /** */
+ EQ("=", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey,
targetKey) == 0),
+
+ /** */
+ NE("<>", (rowKey, targetKey) -> binaryObjectCmpForDml(rowKey,
targetKey) != 0);
+
+ /** */
+ private final String sql;
+
+ /** */
+ private final ExpRowByKeyPredicate expRowByKeyPred;
+
+ /** */
+ CmpOp(String sql, ExpRowByKeyPredicate expRowByKeyPred) {
+ this.sql = sql;
+ this.expRowByKeyPred = expRowByKeyPred;
+ }
+ }
+
+ /** */
+ private void executeAlterTableAddColumn() {
+ sql("alter table PUBLIC.PERSON add email varchar");
+ }
+
+ /** */
+ private void executeAlterTableDropColumn() {
+ sql("alter table PUBLIC.PERSON drop column surname");
+ }
+
+ /** */
+ private static int binaryObjectCmpForDml(Object o1, Object o2) {
+ return BinaryUtils.binariesFactory.compareForDml(o1, o2);
+ }
+}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
index ccba0615464..6558537af07 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java
@@ -67,6 +67,7 @@ import
org.apache.ignite.internal.processors.query.calcite.integration.QueryMeta
import
org.apache.ignite.internal.processors.query.calcite.integration.QueryWithPartitionsIntegrationTest;
import
org.apache.ignite.internal.processors.query.calcite.integration.RunningQueriesIntegrationTest;
import
org.apache.ignite.internal.processors.query.calcite.integration.ScalarInIntegrationTest;
+import
org.apache.ignite.internal.processors.query.calcite.integration.SelectByKeyFieldTest;
import
org.apache.ignite.internal.processors.query.calcite.integration.ServerStatisticsIntegrationTest;
import
org.apache.ignite.internal.processors.query.calcite.integration.SetOpIntegrationTest;
import
org.apache.ignite.internal.processors.query.calcite.integration.SortAggregateIntegrationTest;
@@ -177,6 +178,8 @@ import org.junit.runners.Suite;
MultiDcQueryMappingTest.class,
TxWithExceptionalInterceptorTest.class,
CacheWithInterceptorIntegrationTest.class,
+ TxWithExceptionalInterceptorTest.class,
+ SelectByKeyFieldTest.class,
})
public class IntegrationTestSuite {
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/management/IndexDescriptor.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/management/IndexDescriptor.java
index 8174b5c4ddc..01fc5a383dc 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/management/IndexDescriptor.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/schema/management/IndexDescriptor.java
@@ -141,4 +141,9 @@ public class IndexDescriptor {
public TableDescriptor table() {
return tbl;
}
+
+ /** */
+ public boolean isComposite() {
+ return keyDefs.size() > 1;
+ }
}