Skip to content

Commit 8bbb757

Browse files
authored
IGNITE-28331 Fix SQL search by _key for a composite pk in Calcite engine (#12926)
1 parent 74af609 commit 8bbb757

13 files changed

Lines changed: 868 additions & 58 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal.processors.query.calcite.exec;
19+
20+
import java.util.Map;
21+
import org.apache.calcite.util.ImmutableBitSet;
22+
import org.apache.calcite.util.ImmutableIntList;
23+
import org.apache.ignite.IgniteException;
24+
import org.apache.ignite.binary.BinaryObject;
25+
import org.apache.ignite.internal.cache.query.index.sorted.IndexKeyDefinition;
26+
import org.apache.ignite.internal.cache.query.index.sorted.IndexKeyType;
27+
import org.apache.ignite.internal.cache.query.index.sorted.IndexPlainRowImpl;
28+
import org.apache.ignite.internal.cache.query.index.sorted.IndexRow;
29+
import org.apache.ignite.internal.cache.query.index.sorted.InlineIndexRowHandler;
30+
import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
31+
import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKey;
32+
import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKeyFactory;
33+
import org.apache.ignite.internal.processors.query.QueryUtils;
34+
import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
35+
import org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
36+
import org.apache.ignite.internal.processors.query.calcite.schema.ColumnDescriptor;
37+
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
38+
import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
39+
import org.jetbrains.annotations.Nullable;
40+
41+
/** Extension for column {@value QueryUtils#KEY_FIELD_NAME} in case of composite primary key. */
42+
public class IndexWrappedKeyScan<Row> extends IndexScan<Row> {
43+
/** */
44+
public IndexWrappedKeyScan(
45+
ExecutionContext<Row> ectx,
46+
CacheTableDescriptor desc,
47+
InlineIndex idx,
48+
ImmutableIntList idxFieldMapping,
49+
int[] parts,
50+
RangeIterable<Row> ranges,
51+
@Nullable ImmutableBitSet requiredColumns
52+
) {
53+
super(ectx, desc, idx, idxFieldMapping, parts, ranges, requiredColumns);
54+
}
55+
56+
/** */
57+
@Override protected IndexRow row2indexRow(Row bound) {
58+
if (bound == null)
59+
return null;
60+
61+
RowHandler<Row> rowHnd = ectx.rowHandler();
62+
63+
Object key = rowHnd.get(QueryUtils.KEY_COL, bound);
64+
assert key != null : String.format("idxName=%s, bound=%s", idx.name(), Commons.toString(rowHnd, bound));
65+
66+
if (key instanceof BinaryObject)
67+
return binaryObject2indexRow((BinaryObject)key);
68+
69+
throw new IgniteException(String.format(
70+
"Unsupported type for index boundary: [expected=%s, current=%s]",
71+
BinaryObject.class.getName(), key.getClass().getName()
72+
));
73+
}
74+
75+
/** */
76+
private IndexRow binaryObject2indexRow(BinaryObject o) {
77+
assert o.type().typeName().equals(idx.indexDefinition().typeDescriptor().keyTypeName()) : String.format(
78+
"idx=%s, o=%s, oType=%s, idxKeyType=%s",
79+
idx.name(), o, o.type().typeName(), idx.indexDefinition().typeDescriptor().keyTypeName()
80+
);
81+
82+
InlineIndexRowHandler idxRowHnd = idx.segment(0).rowHandler();
83+
IndexKey[] keys = new IndexKey[idx.indexDefinition().indexKeyDefinitions().size()];
84+
85+
int i = 0;
86+
for (Map.Entry<String, IndexKeyDefinition> e : idx.indexDefinition().indexKeyDefinitions().entrySet()) {
87+
String keyName = e.getKey();
88+
89+
ColumnDescriptor fieldDesc = desc.columnDescriptor(keyName);
90+
assert fieldDesc != null : String.format("idx=%s, o=%s, keyName=%s", idx.name(), o, keyName);
91+
92+
Object field = o.field(keyName);
93+
Object key = TypeUtils.fromInternal(ectx, field, fieldDesc.storageType());
94+
95+
keys[i++] = wrapIndexKey(key, e.getValue().indexKeyType());
96+
}
97+
98+
return new IndexPlainRowImpl(keys, idxRowHnd);
99+
}
100+
101+
/** */
102+
private IndexKey wrapIndexKey(Object key, IndexKeyType keyType) {
103+
return IndexKeyFactory.wrap(key, keyType, cctx.cacheObjectContext(), idx.indexDefinition().keyTypeSettings());
104+
}
105+
}

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import java.util.function.Function;
3434
import java.util.function.Predicate;
3535
import java.util.function.Supplier;
36-
import java.util.stream.Collectors;
36+
import java.util.stream.IntStream;
3737
import com.google.common.collect.ImmutableList;
3838
import com.google.common.primitives.Primitives;
3939
import org.apache.calcite.DataContext;
@@ -46,6 +46,7 @@
4646
import org.apache.calcite.linq4j.tree.ParameterExpression;
4747
import org.apache.calcite.plan.RelOptUtil;
4848
import org.apache.calcite.rel.RelCollation;
49+
import org.apache.calcite.rel.RelCollations;
4950
import org.apache.calcite.rel.RelFieldCollation;
5051
import org.apache.calcite.rel.core.AggregateCall;
5152
import org.apache.calcite.rel.type.RelDataType;
@@ -81,6 +82,8 @@
8182
import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap;
8283
import org.apache.ignite.internal.util.typedef.F;
8384

85+
import static java.util.stream.Collectors.toList;
86+
8487
/**
8588
* Implements rex expression into a function object. Uses JaninoRexCompiler under the hood.
8689
* Each expression compiles into a class and a wrapper over it is returned.
@@ -338,6 +341,14 @@ else if (o2 == null)
338341

339342
List<RangeConditionImpl> ranges = new ArrayList<>();
340343

344+
if (collation.getKeys().isEmpty()) {
345+
collation = RelCollations.of(IntStream.range(0, searchBounds.size())
346+
.filter(i -> searchBounds.get(i) != null)
347+
.mapToObj(RelFieldCollation::new)
348+
.collect(toList())
349+
);
350+
}
351+
341352
Comparator<Row> rowComparator = comparator(collation);
342353

343354
expandBounds(
@@ -1003,7 +1014,7 @@ public RangeIterableImpl(List<RangeConditionImpl> ranges) {
10031014
// should not affect ordering.
10041015
if (!sorted) {
10051016
ranges = ranges.stream().filter(r -> !r.skip()).sorted(RangeConditionImpl::compareTo)
1006-
.collect(Collectors.toList());
1017+
.collect(toList());
10071018

10081019
List<RangeConditionImpl> ranges0 = new ArrayList<>(ranges.size());
10091020

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.calcite.linq4j.Linq4j;
2828
import org.apache.calcite.rel.type.RelDataType;
2929
import org.apache.calcite.rel.type.RelDataTypeFactory;
30+
import org.apache.calcite.runtime.SqlFunctions;
3031
import org.apache.calcite.schema.ScannableTable;
3132
import org.apache.calcite.schema.Schema;
3233
import org.apache.calcite.schema.Statistic;
@@ -293,4 +294,52 @@ private long convertToLongArg(Object val, String name) {
293294
return true;
294295
}
295296
}
297+
298+
/** SQL >=. */
299+
public static boolean geAny(Object a, Object b) {
300+
if (Commons.isBinaryComparable(a, b))
301+
return Commons.compareBinary(a, b) >= 0;
302+
303+
return SqlFunctions.geAny(a, b);
304+
}
305+
306+
/** SQL >. */
307+
public static boolean gtAny(Object a, Object b) {
308+
if (Commons.isBinaryComparable(a, b))
309+
return Commons.compareBinary(a, b) > 0;
310+
311+
return SqlFunctions.gtAny(a, b);
312+
}
313+
314+
/** SQL <=. */
315+
public static boolean leAny(Object a, Object b) {
316+
if (Commons.isBinaryComparable(a, b))
317+
return Commons.compareBinary(a, b) <= 0;
318+
319+
return SqlFunctions.leAny(a, b);
320+
}
321+
322+
/** SQL <. */
323+
public static boolean ltAny(Object a, Object b) {
324+
if (Commons.isBinaryComparable(a, b))
325+
return Commons.compareBinary(a, b) < 0;
326+
327+
return SqlFunctions.ltAny(a, b);
328+
}
329+
330+
/** SQL =. */
331+
public static boolean eqAny(Object a, Object b) {
332+
if (Commons.isBinaryComparable(a, b))
333+
return Commons.compareBinary(a, b) == 0;
334+
335+
return SqlFunctions.eqAny(a, b);
336+
}
337+
338+
/** SQL <>. */
339+
public static boolean neAny(Object a, Object b) {
340+
if (Commons.isBinaryComparable(a, b))
341+
return Commons.compareBinary(a, b) != 0;
342+
343+
return SqlFunctions.neAny(a, b);
344+
}
296345
}

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ private Expression callBackupMethodAnyType(RexToLixTranslator translator,
12981298
// one or both of parameter(s) is(are) ANY type
12991299
final Expression expression0 = maybeBox(expressions.get(0));
13001300
final Expression expression1 = maybeBox(expressions.get(1));
1301-
return Expressions.call(SqlFunctions.class, backupMethodNameForAnyType,
1301+
return Expressions.call(IgniteSqlFunctions.class, backupMethodNameForAnyType,
13021302
expression0, expression1);
13031303
}
13041304

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
3939
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
4040
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
41+
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
4142
import org.apache.ignite.internal.util.typedef.F;
4243

4344
import static org.apache.calcite.sql.type.SqlTypeName.ANY;
@@ -234,7 +235,11 @@ private static <Row> Supplier<Accumulator<Row>> minFactory(AggregateCall call, R
234235
return () -> new ComparableMinMax<Row, UUID>(call, hnd, true,
235236
tf -> tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.UUID), true));
236237
case ANY:
237-
throw new UnsupportedOperationException("MIN() is not supported for type '" + call.type + "'.");
238+
return () -> new ComparableMinMax<>(call, hnd, true,
239+
tf -> tf.createTypeWithNullability(tf.createSqlType(ANY), true));
240+
case OTHER:
241+
return () -> new ComparableMinMax<>(call, hnd, true,
242+
tf -> tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.OTHER), true));
238243
case BIGINT:
239244
default:
240245
return () -> new LongMinMax<>(call, hnd, true);
@@ -263,7 +268,11 @@ private static <Row> Supplier<Accumulator<Row>> maxFactory(AggregateCall call, R
263268
return () -> new ComparableMinMax<Row, UUID>(call, hnd, false,
264269
tf -> tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.UUID), true));
265270
case ANY:
266-
throw new UnsupportedOperationException("MAX() is not supported for type '" + call.type + "'.");
271+
return () -> new ComparableMinMax<>(call, hnd, false,
272+
tf -> tf.createTypeWithNullability(tf.createSqlType(ANY), true));
273+
case OTHER:
274+
return () -> new ComparableMinMax<>(call, hnd, false,
275+
tf -> tf.createTypeWithNullability(tf.createSqlType(SqlTypeName.OTHER), true));
267276
case BIGINT:
268277
default:
269278
return () -> new LongMinMax<>(call, hnd, false);
@@ -1116,7 +1125,7 @@ private DecimalMinMax(AggregateCall aggCall, RowHandler<Row> hnd, boolean min) {
11161125
}
11171126

11181127
/** */
1119-
private static class ComparableMinMax<Row, T extends Comparable<T>> extends AbstractAccumulator<Row> {
1128+
private static class ComparableMinMax<Row, T> extends AbstractAccumulator<Row> {
11201129
/** */
11211130
private final boolean min;
11221131

@@ -1149,8 +1158,8 @@ private ComparableMinMax(
11491158
return;
11501159

11511160
val = empty ? in : min ?
1152-
(val.compareTo(in) < 0 ? val : in) :
1153-
(val.compareTo(in) < 0 ? in : val);
1161+
(compare(val, in) < 0 ? val : in) :
1162+
(compare(val, in) < 0 ? in : val);
11541163

11551164
empty = false;
11561165
}
@@ -1163,8 +1172,8 @@ private ComparableMinMax(
11631172
return;
11641173

11651174
val = empty ? other0.val : min ?
1166-
(val.compareTo(other0.val) < 0 ? val : other0.val) :
1167-
(val.compareTo(other0.val) < 0 ? other0.val : val);
1175+
(compare(val, other0.val) < 0 ? val : other0.val) :
1176+
(compare(val, other0.val) < 0 ? other0.val : val);
11681177

11691178
empty = false;
11701179
}
@@ -1183,6 +1192,22 @@ private ComparableMinMax(
11831192
@Override public RelDataType returnType(IgniteTypeFactory typeFactory) {
11841193
return typeSupplier.apply(typeFactory);
11851194
}
1195+
1196+
/** */
1197+
@SuppressWarnings({"rawtypes", "unchecked"})
1198+
private int compare(Object a, Object b) {
1199+
if (Commons.isBinaryComparable(a, b))
1200+
return Commons.compareBinary(a, b);
1201+
1202+
if (a.getClass() != b.getClass()) {
1203+
throw new UnsupportedOperationException(String.format(
1204+
"%s() is not supported for different value types: [type0=%s, type1=%s]",
1205+
min ? "MIN" : "MAX", a.getClass().getName(), b.getClass().getName()
1206+
));
1207+
}
1208+
1209+
return ((Comparable)a).compareTo(b);
1210+
}
11861211
}
11871212

11881213
/** */

0 commit comments

Comments
 (0)