You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2022/09/26 10:32:00 UTC
[ignite] branch master updated: IGNITE-15425 SQL Calcite: Add native support for SEARCH/SARG operator - Fixes #10251.
This is an automated email from the ASF dual-hosted git repository.
alexpl 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 20fbf9d519d IGNITE-15425 SQL Calcite: Add native support for SEARCH/SARG operator - Fixes #10251.
20fbf9d519d is described below
commit 20fbf9d519d4472b338e96241da80840ad773e86
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Mon Sep 26 13:19:31 2022 +0300
IGNITE-15425 SQL Calcite: Add native support for SEARCH/SARG operator - Fixes #10251.
Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
.../query/calcite/exec/AbstractIndexScan.java | 39 +-
.../processors/query/calcite/exec/IndexScan.java | 21 +-
.../query/calcite/exec/LogicalRelImplementor.java | 52 +--
.../query/calcite/exec/RuntimeSortedIndex.java | 46 ++-
.../query/calcite/exec/SystemViewScan.java | 18 +-
.../processors/query/calcite/exec/TreeIndex.java | 4 +-
.../query/calcite/exec/exp/ExpressionFactory.java | 13 +
.../calcite/exec/exp/ExpressionFactoryImpl.java | 241 +++++++++++-
.../exp/RangeCondition.java} | 30 +-
.../exp/RangeIterable.java} | 19 +-
.../query/calcite/exec/rel/IndexSpoolNode.java | 8 +-
.../query/calcite/externalize/RelInputEx.java | 9 +
.../query/calcite/externalize/RelJson.java | 62 ++-
.../query/calcite/externalize/RelJsonReader.java | 6 +
.../calcite/metadata/IgniteMdSelectivity.java | 7 +
.../query/calcite/prepare/IgnitePrograms.java | 2 +
.../query/calcite/prepare/bounds/ExactBounds.java | 70 ++++
.../query/calcite/prepare/bounds/MultiBounds.java | 73 ++++
.../query/calcite/prepare/bounds/RangeBounds.java | 113 ++++++
.../query/calcite/prepare/bounds/SearchBounds.java | 56 +++
.../query/calcite/rel/AbstractIndexScan.java | 63 +--
.../query/calcite/rel/IgniteIndexScan.java | 14 +-
.../query/calcite/rel/IgniteSortedIndexSpool.java | 28 +-
.../rel/ProjectableFilterableTableScan.java | 6 +-
.../rel/logical/IgniteLogicalIndexScan.java | 12 +-
.../rule/FilterSpoolMergeToHashIndexSpoolRule.java | 8 +-
.../FilterSpoolMergeToSortedIndexSpoolRule.java | 37 +-
.../calcite/rule/LogicalScanConverterRule.java | 2 +-
.../calcite/rule/logical/FilterScanMergeRule.java | 10 +-
.../query/calcite/schema/CacheIndexImpl.java | 16 +-
.../query/calcite/schema/IgniteIndex.java | 9 +-
.../query/calcite/schema/SystemViewIndexImpl.java | 17 +-
.../processors/query/calcite/trait/TraitUtils.java | 12 +-
.../query/calcite/util/IndexConditions.java | 142 -------
.../processors/query/calcite/util/RexUtils.java | 397 ++++++++++++-------
.../calcite/exec/LogicalRelImplementorTest.java | 4 +-
.../query/calcite/exec/RuntimeSortedIndexTest.java | 2 +-
.../exec/rel/SortedIndexSpoolExecutionTest.java | 52 ++-
.../integration/AbstractBasicIntegrationTest.java | 14 +-
.../CalciteBasicSecondaryIndexIntegrationTest.java | 4 -
.../integration/IndexScanlIntegrationTest.java | 8 +-
.../SearchSargOnIndexIntegrationTest.java | 153 ++++++++
.../query/calcite/planner/AbstractPlannerTest.java | 6 +-
.../CorrelatedNestedLoopJoinPlannerTest.java | 25 +-
.../planner/ProjectFilterScanMergePlannerTest.java | 22 +-
.../planner/SearchSargOnIndexPlannerTest.java | 436 +++++++++++++++++++++
.../planner/SortedIndexSpoolPlannerTest.java | 76 ++--
.../calcite/planner/StatisticsPlannerTest.java | 28 +-
.../query/calcite/rules/OrToUnionRuleTest.java | 13 +-
.../ignite/testsuites/IntegrationTestSuite.java | 2 +
.../apache/ignite/testsuites/PlannerTestSuite.java | 2 +
51 files changed, 1856 insertions(+), 653 deletions(-)
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java
index ca12e493005..228680a9473 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/AbstractIndexScan.java
@@ -20,13 +20,15 @@ import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
-
import org.apache.calcite.rel.type.RelDataType;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeCondition;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
import org.apache.ignite.internal.util.lang.GridCursor;
import org.apache.ignite.internal.util.lang.GridIteratorAdapter;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteClosure;
import org.jetbrains.annotations.NotNull;
/**
@@ -39,11 +41,8 @@ public abstract class AbstractIndexScan<Row, IdxRow> implements Iterable<Row>, A
/** Additional filters. */
private final Predicate<Row> filters;
- /** Lower index scan bound. */
- private final Supplier<Row> lowerBound;
-
- /** Upper index scan bound. */
- private final Supplier<Row> upperBound;
+ /** Index scan bounds. */
+ private final RangeIterable<Row> ranges;
/** */
private final Function<Row, Row> rowTransformer;
@@ -58,33 +57,41 @@ public abstract class AbstractIndexScan<Row, IdxRow> implements Iterable<Row>, A
* @param ectx Execution context.
* @param idx Physical index.
* @param filters Additional filters.
- * @param lowerBound Lower index scan bound.
- * @param upperBound Upper index scan bound.
+ * @param ranges Index scan bounds.
*/
protected AbstractIndexScan(
ExecutionContext<Row> ectx,
RelDataType rowType,
TreeIndex<IdxRow> idx,
Predicate<Row> filters,
- Supplier<Row> lowerBound,
- Supplier<Row> upperBound,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer
) {
this.ectx = ectx;
this.rowType = rowType;
this.idx = idx;
this.filters = filters;
- this.lowerBound = lowerBound;
- this.upperBound = upperBound;
+ this.ranges = ranges;
this.rowTransformer = rowTransformer;
}
/** {@inheritDoc} */
@Override public synchronized Iterator<Row> iterator() {
- IdxRow lower = lowerBound == null ? null : row2indexRow(lowerBound.get());
- IdxRow upper = upperBound == null ? null : row2indexRow(upperBound.get());
+ if (ranges == null)
+ return new IteratorImpl(idx.find(null, null, true, true, indexQueryContext()));
+
+ IgniteClosure<RangeCondition<Row>, IteratorImpl> clo = range -> {
+ IdxRow lower = range.lower() == null ? null : row2indexRow(range.lower());
+ IdxRow upper = range.upper() == null ? null : row2indexRow(range.upper());
+
+ return new IteratorImpl(
+ idx.find(lower, upper, range.lowerInclude(), range.upperInclude(), indexQueryContext()));
+ };
+
+ if (ranges.size() == 1)
+ return clo.apply(ranges.iterator().next());
- return new IteratorImpl(idx.find(lower, upper, indexQueryContext()));
+ return F.flat(F.iterator(ranges, clo, true));
}
/** */
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 8b9b2fcc731..a21308e2a44 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
@@ -23,7 +23,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.ImmutableIntList;
@@ -46,6 +45,7 @@ import org.apache.ignite.internal.processors.cache.distributed.dht.topology.Grid
import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionTopology;
import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+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.type.IgniteTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
@@ -100,8 +100,7 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
* @param idxFieldMapping Mapping from index keys to row fields.
* @param idx Phisycal index.
* @param filters Additional filters.
- * @param lowerBound Lower index scan bound.
- * @param upperBound Upper index scan bound.
+ * @param ranges Index scan bounds.
*/
public IndexScan(
ExecutionContext<Row> ectx,
@@ -110,8 +109,7 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
ImmutableIntList idxFieldMapping,
int[] parts,
Predicate<Row> filters,
- Supplier<Row> lowerBound,
- Supplier<Row> upperBound,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
) {
@@ -120,8 +118,7 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
desc.rowType(ectx.getTypeFactory(), requiredColumns),
new TreeIndexWrapper(idx),
filters,
- lowerBound,
- upperBound,
+ ranges,
rowTransformer
);
@@ -296,9 +293,15 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
}
/** {@inheritDoc} */
- @Override public GridCursor<IndexRow> find(IndexRow lower, IndexRow upper, IndexQueryContext qctx) {
+ @Override public GridCursor<IndexRow> find(
+ IndexRow lower,
+ IndexRow upper,
+ boolean lowerInclude,
+ boolean upperInclude,
+ IndexQueryContext qctx
+ ) {
try {
- return idx.find(lower, upper, true, true, qctx);
+ return idx.find(lower, upper, lowerInclude, upperInclude, qctx);
}
catch (IgniteCheckedException e) {
throw new IgniteException("Failed to find index rows", e);
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 20f9def3eb3..829236d6c8b 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
@@ -40,6 +40,7 @@ import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.AbstractSetOpNode;
@@ -65,6 +66,7 @@ import org.apache.ignite.internal.processors.query.calcite.exec.rel.TableSpoolNo
import org.apache.ignite.internal.processors.query.calcite.exec.rel.UnionAllNode;
import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
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.rel.IgniteCollect;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
@@ -295,22 +297,21 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
IgniteTypeFactory typeFactory = ctx.getTypeFactory();
ImmutableBitSet requiredColumns = rel.requiredColumns();
- List<RexNode> lowerCond = rel.lowerBound();
- List<RexNode> upperCond = rel.upperBound();
+ List<SearchBounds> searchBounds = rel.searchBounds();
RelDataType rowType = tbl.getRowType(typeFactory, requiredColumns);
Predicate<Row> filters = condition == null ? null : expressionFactory.predicate(condition, rowType);
- Supplier<Row> lower = lowerCond == null ? null : expressionFactory.rowSource(lowerCond);
- Supplier<Row> upper = upperCond == null ? null : expressionFactory.rowSource(upperCond);
Function<Row, Row> prj = projects == null ? null : expressionFactory.project(projects, rowType);
+ RangeIterable<Row> ranges = searchBounds == null ? null :
+ expressionFactory.ranges(searchBounds, rel.collation(), tbl.getRowType(typeFactory));
ColocationGroup grp = ctx.group(rel.sourceId());
IgniteIndex idx = tbl.getIndex(rel.indexName());
if (idx != null && !tbl.isIndexRebuildInProgress()) {
- Iterable<Row> rowsIter = idx.scan(ctx, grp, filters, lower, upper, prj, requiredColumns);
+ Iterable<Row> rowsIter = idx.scan(ctx, grp, filters, ranges, prj, requiredColumns);
return new ScanNode<>(ctx, rowType, rowsIter);
}
@@ -361,24 +362,15 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
}
if (spoolNodeRequired) {
- if (lowerCond != null || upperCond != null) {
- if (requiredColumns != null) {
- // Remap index find predicate according to rowType of the spool.
- int cardinality = requiredColumns.cardinality();
- List<RexNode> remappedLowerCond = lowerCond != null ? new ArrayList<>(cardinality) : null;
- List<RexNode> remappedUpperCond = upperCond != null ? new ArrayList<>(cardinality) : null;
-
- for (int i = requiredColumns.nextSetBit(0); i != -1; i = requiredColumns.nextSetBit(i + 1)) {
- if (remappedLowerCond != null)
- remappedLowerCond.add(lowerCond.get(i));
-
- if (remappedUpperCond != null)
- remappedUpperCond.add(upperCond.get(i));
- }
-
- lower = remappedLowerCond == null ? null : expressionFactory.rowSource(remappedLowerCond);
- upper = remappedUpperCond == null ? null : expressionFactory.rowSource(remappedUpperCond);
- }
+ if (searchBounds != null && requiredColumns != null) {
+ // Remap index find predicate according to rowType of the spool.
+ List<SearchBounds> remappedSearchBounds = new ArrayList<>(requiredColumns.cardinality());
+
+ for (int i = requiredColumns.nextSetBit(0); i != -1; i = requiredColumns.nextSetBit(i + 1))
+ remappedSearchBounds.add(searchBounds.get(i));
+
+ // Collation and row type are already remapped taking into account requiredColumns.
+ ranges = expressionFactory.ranges(remappedSearchBounds, collation, rowType);
}
IndexSpoolNode<Row> spoolNode = IndexSpoolNode.createTreeSpool(
@@ -387,8 +379,7 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
collation,
expressionFactory.comparator(collation),
filterHasCorrelation ? filters : null, // Not correlated filter included into table scan.
- lower,
- upper
+ ranges
);
spoolNode.register(node);
@@ -515,14 +506,10 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
@Override public Node<Row> visit(IgniteSortedIndexSpool rel) {
RelCollation collation = rel.collation();
- assert rel.indexCondition() != null : rel;
-
- List<RexNode> lowerBound = rel.indexCondition().lowerBound();
- List<RexNode> upperBound = rel.indexCondition().upperBound();
+ assert rel.searchBounds() != null : rel;
Predicate<Row> filter = expressionFactory.predicate(rel.condition(), rel.getRowType());
- Supplier<Row> lower = lowerBound == null ? null : expressionFactory.rowSource(lowerBound);
- Supplier<Row> upper = upperBound == null ? null : expressionFactory.rowSource(upperBound);
+ RangeIterable<Row> ranges = expressionFactory.ranges(rel.searchBounds(), collation, rel.getRowType());
IndexSpoolNode<Row> node = IndexSpoolNode.createTreeSpool(
ctx,
@@ -530,8 +517,7 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
collation,
expressionFactory.comparator(collation),
filter,
- lower,
- upper
+ ranges
);
Node<Row> input = visit(rel.getInput());
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java
index 88df20afb71..e58d17f2759 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/RuntimeSortedIndex.java
@@ -21,10 +21,10 @@ import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
import org.apache.ignite.internal.util.lang.GridCursor;
import org.apache.ignite.internal.util.typedef.F;
import org.jetbrains.annotations.Nullable;
@@ -74,7 +74,13 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
}
/** {@inheritDoc} */
- @Override public GridCursor<Row> find(Row lower, Row upper, IndexQueryContext qctx) {
+ @Override public GridCursor<Row> find(
+ Row lower,
+ Row upper,
+ boolean lowerInclude,
+ boolean upperInclude,
+ IndexQueryContext qctx
+ ) {
assert qctx == null;
int firstCol = F.first(collation.getKeys());
@@ -85,7 +91,7 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
Row lowerRow = (lowerBound == null) ? null : lower;
Row upperRow = (upperBound == null) ? null : upper;
- return new Cursor(rows, lowerRow, upperRow);
+ return new Cursor(rows, lowerRow, upperRow, lowerInclude, upperInclude);
}
/**
@@ -95,10 +101,9 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
ExecutionContext<Row> ectx,
RelDataType rowType,
Predicate<Row> filter,
- Supplier<Row> lowerBound,
- Supplier<Row> upperBound
+ RangeIterable<Row> ranges
) {
- return new IndexScan(rowType, this, filter, lowerBound, upperBound);
+ return new IndexScan(rowType, this, filter, ranges);
}
/**
@@ -111,6 +116,9 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
/** Upper bound. */
private final Row upper;
+ /** Include upper bound. */
+ private final boolean includeUpper;
+
/** Current row. */
private Row row;
@@ -119,14 +127,17 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
/**
* @param rows List of rows.
- * @param lower Lower bound (inclusive).
- * @param upper Upper bound (inclusive).
+ * @param lower Lower bound.
+ * @param upper Upper bound.
+ * @param lowerInclude {@code True} for inclusive lower bound.
+ * @param upperInclude {@code True} for inclusive upper bound.
*/
- Cursor(List<Row> rows, @Nullable Row lower, @Nullable Row upper) {
+ Cursor(List<Row> rows, @Nullable Row lower, @Nullable Row upper, boolean lowerInclude, boolean upperInclude) {
this.rows = rows;
this.upper = upper;
+ this.includeUpper = upperInclude;
- idx = lower == null ? 0 : lowerBound(rows, lower);
+ idx = lower == null ? 0 : lowerBound(rows, lower, lowerInclude);
}
/**
@@ -136,7 +147,7 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
* @param bound Lower bound.
* @return Lower bound position in the list.
*/
- private int lowerBound(List<Row> rows, Row bound) {
+ private int lowerBound(List<Row> rows, Row bound, boolean includeBound) {
int low = 0, high = rows.size() - 1, idx = -1;
while (low <= high) {
@@ -145,7 +156,7 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
if (compRes > 0)
high = mid - 1;
- else if (compRes == 0) {
+ else if (compRes == 0 && includeBound) {
idx = mid;
high = mid - 1;
}
@@ -158,7 +169,7 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
/** {@inheritDoc} */
@Override public boolean next() {
- if (idx == rows.size() || (upper != null && comp.compare(upper, rows.get(idx)) < 0))
+ if (idx == rows.size() || (upper != null && comp.compare(upper, rows.get(idx)) < (includeUpper ? 0 : 1)))
return false;
row = rows.get(idx++);
@@ -180,16 +191,15 @@ public class RuntimeSortedIndex<Row> implements RuntimeIndex<Row>, TreeIndex<Row
* @param rowType Row type.
* @param idx Physical index.
* @param filter Additional filters.
- * @param lowerBound Lower index scan bound.
- * @param upperBound Upper index scan bound.
+ * @param ranges Index scan bounds.
*/
IndexScan(
RelDataType rowType,
TreeIndex<Row> idx,
Predicate<Row> filter,
- Supplier<Row> lowerBound,
- Supplier<Row> upperBound) {
- super(RuntimeSortedIndex.this.ectx, rowType, idx, filter, lowerBound, upperBound, null);
+ RangeIterable<Row> ranges
+ ) {
+ super(RuntimeSortedIndex.this.ectx, rowType, idx, filter, ranges, null);
}
/** {@inheritDoc} */
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java
index 89392be37e4..2fa944ae678 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/SystemViewScan.java
@@ -22,10 +22,11 @@ import java.util.Iterator;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeCondition;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
import org.apache.ignite.internal.processors.query.calcite.schema.SystemViewColumnDescriptor;
import org.apache.ignite.internal.processors.query.calcite.schema.SystemViewTableDescriptorImpl;
import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
@@ -48,7 +49,7 @@ public class SystemViewScan<Row, ViewRow> implements Iterable<Row> {
private final RowFactory<Row> factory;
/** */
- private final Supplier<Row> searchRow;
+ private final RangeIterable<Row> ranges;
/** */
private final Predicate<Row> filters;
@@ -69,14 +70,14 @@ public class SystemViewScan<Row, ViewRow> implements Iterable<Row> {
public SystemViewScan(
ExecutionContext<Row> ectx,
SystemViewTableDescriptorImpl<ViewRow> desc,
- @Nullable Supplier<Row> searchRow,
+ @Nullable RangeIterable<Row> ranges,
Predicate<Row> filters,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
) {
this.ectx = ectx;
this.desc = desc;
- this.searchRow = searchRow;
+ this.ranges = ranges;
this.filters = filters;
this.rowTransformer = rowTransformer;
this.requiredColumns = requiredColumns;
@@ -104,10 +105,15 @@ public class SystemViewScan<Row, ViewRow> implements Iterable<Row> {
Iterator<ViewRow> viewIter;
- if (searchRow != null) {
+ if (ranges != null) {
assert view instanceof FiltrableSystemView : view;
- Row searchValues = searchRow.get();
+ Iterator<RangeCondition<Row>> rangesIter = ranges.iterator();
+ RangeCondition<Row> range = rangesIter.next();
+
+ assert !rangesIter.hasNext();
+
+ Row searchValues = range.lower(); // Lower bound for the hash index should be the same as upper bound.
RowHandler<Row> rowHnd = ectx.rowHandler();
Map<String, Object> filterMap = null;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java
index 91cb9127249..6b3e2c47d18 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TreeIndex.java
@@ -30,8 +30,10 @@ public interface TreeIndex<R> {
*
* @param lower Lower bound.
* @param upper Upper bound.
+ * @param lowerInclude Inclusive lower bound.
+ * @param upperInclude Inclusive upper bound.
* @param qctx Index query context.
* @return Cursor over the rows within bounds.
*/
- public GridCursor<R> find(R lower, R upper, IndexQueryContext qctx);
+ public GridCursor<R> find(R lower, R upper, boolean lowerInclude, boolean upperInclude, IndexQueryContext qctx);
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
index 0893229c79b..a68078f6c42 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java
@@ -32,6 +32,7 @@ import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
/**
* Expression factory.
@@ -105,6 +106,18 @@ public interface ExpressionFactory<Row> {
*/
Supplier<Row> rowSource(List<RexNode> values);
+ /**
+ * Creates iterable search bounds tuples (lower row/upper row) by search bounds expressions.
+ *
+ * @param searchBounds Search bounds.
+ * @param collation Collation.
+ * @param rowType Row type.
+ */
+ RangeIterable<Row> ranges(
+ List<SearchBounds> searchBounds,
+ RelCollation collation,
+ RelDataType rowType
+ );
/**
* Executes expression.
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 2617ae576ff..f5c3f91705b 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
@@ -20,9 +20,13 @@ package org.apache.ignite.internal.processors.query.calcite.exec.exp;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
@@ -63,6 +67,10 @@ import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexToLixTran
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorsFactory;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.MultiBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.RangeBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
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.processors.query.calcite.util.IgniteMethod;
@@ -270,6 +278,121 @@ public class ExpressionFactoryImpl<Row> implements ExpressionFactory<Row> {
return rows;
}
+ /** {@inheritDoc} */
+ @Override public RangeIterable<Row> ranges(
+ List<SearchBounds> searchBounds,
+ RelCollation collation,
+ RelDataType rowType
+ ) {
+ RowFactory<Row> rowFactory = ctx.rowHandler().factory(typeFactory, rowType);
+
+ List<RangeCondition<Row>> ranges = new ArrayList<>();
+
+ expandBounds(
+ ranges,
+ searchBounds,
+ rowType,
+ rowFactory,
+ collation.getKeys(),
+ 0,
+ Arrays.asList(new RexNode[searchBounds.size()]),
+ Arrays.asList(new RexNode[searchBounds.size()]),
+ true,
+ true
+ );
+
+ return new RangeIterableImpl(ranges, comparator(collation));
+ }
+
+ /**
+ * Expand column-oriented {@link SearchBounds} to a row-oriented list of ranges ({@link RangeCondition}).
+ *
+ * @param ranges List of ranges.
+ * @param searchBounds Search bounds.
+ * @param rowType Row type.
+ * @param rowFactory Row factory.
+ * @param collationKeys Collation keys.
+ * @param collationKeyIdx Current collation key index (field to process).
+ * @param curLower Current lower row.
+ * @param curUpper Current upper row.
+ * @param lowerInclude Include current lower row.
+ * @param upperInclude Include current upper row.
+ */
+ private void expandBounds(
+ List<RangeCondition<Row>> ranges,
+ List<SearchBounds> searchBounds,
+ RelDataType rowType,
+ RowFactory<Row> rowFactory,
+ List<Integer> collationKeys,
+ int collationKeyIdx,
+ List<RexNode> curLower,
+ List<RexNode> curUpper,
+ boolean lowerInclude,
+ boolean upperInclude
+ ) {
+ if ((collationKeyIdx >= collationKeys.size()) || (!lowerInclude && !upperInclude) ||
+ searchBounds.get(collationKeys.get(collationKeyIdx)) == null) {
+ ranges.add(new RangeConditionImpl(
+ scalar(curLower, rowType),
+ scalar(curUpper, rowType),
+ lowerInclude,
+ upperInclude,
+ rowFactory
+ ));
+
+ return;
+ }
+
+ int fieldIdx = collationKeys.get(collationKeyIdx);
+ SearchBounds fieldBounds = searchBounds.get(fieldIdx);
+
+ Collection<SearchBounds> fieldMultiBounds = fieldBounds instanceof MultiBounds ?
+ ((MultiBounds)fieldBounds).bounds() : Collections.singleton(fieldBounds);
+
+ for (SearchBounds fieldSingleBounds : fieldMultiBounds) {
+ RexNode fieldLowerBound;
+ RexNode fieldUpperBound;
+ boolean fieldLowerInclude;
+ boolean fieldUpperInclude;
+
+ if (fieldSingleBounds instanceof ExactBounds) {
+ fieldLowerBound = fieldUpperBound = ((ExactBounds)fieldSingleBounds).bound();
+ fieldLowerInclude = fieldUpperInclude = true;
+ }
+ else if (fieldSingleBounds instanceof RangeBounds) {
+ RangeBounds fieldRangeBounds = (RangeBounds)fieldSingleBounds;
+ fieldLowerBound = fieldRangeBounds.lowerBound();
+ fieldUpperBound = fieldRangeBounds.upperBound();
+ fieldLowerInclude = fieldRangeBounds.lowerInclude();
+ fieldUpperInclude = fieldRangeBounds.upperInclude();
+ }
+ else
+ throw new IllegalStateException("Unexpected bounds: " + fieldSingleBounds);
+
+ if (lowerInclude)
+ curLower.set(fieldIdx, fieldLowerBound);
+
+ if (upperInclude)
+ curUpper.set(fieldIdx, fieldUpperBound);
+
+ expandBounds(
+ ranges,
+ searchBounds,
+ rowType,
+ rowFactory,
+ collationKeys,
+ collationKeyIdx + 1,
+ curLower,
+ curUpper,
+ lowerInclude && fieldLowerInclude,
+ upperInclude && fieldUpperInclude
+ );
+ }
+
+ curLower.set(fieldIdx, null);
+ curLower.set(fieldIdx, null);
+ }
+
/**
* Creates {@link SingleScalar}, a code-generated expressions evaluator.
*
@@ -288,7 +411,7 @@ public class ExpressionFactoryImpl<Row> implements ExpressionFactory<Row> {
* @param type Row type.
* @return SingleScalar.
*/
- public SingleScalar scalar(List<RexNode> nodes, RelDataType type) {
+ private SingleScalar scalar(List<RexNode> nodes, RelDataType type) {
return (SingleScalar)SCALAR_CACHE.computeIfAbsent(digest(nodes, type, false),
k -> compile(nodes, type, false));
}
@@ -300,7 +423,7 @@ public class ExpressionFactoryImpl<Row> implements ExpressionFactory<Row> {
* @param type Row type.
* @return BiScalar.
*/
- public BiScalar biScalar(RexNode node, RelDataType type) {
+ private BiScalar biScalar(RexNode node, RelDataType type) {
ImmutableList<RexNode> nodes = ImmutableList.of(node);
return (BiScalar)SCALAR_CACHE.computeIfAbsent(digest(nodes, type, true),
@@ -552,6 +675,120 @@ public class ExpressionFactoryImpl<Row> implements ExpressionFactory<Row> {
}
}
+ /** */
+ private class RangeConditionImpl implements RangeCondition<Row> {
+ /** */
+ private final SingleScalar lowerBound;
+
+ /** */
+ private final SingleScalar upperBound;
+
+ /** */
+ private final boolean lowerInclude;
+
+ /** */
+ private final boolean upperInclude;
+
+ /** */
+ private Row lowerRow;
+
+ /** */
+ private Row upperRow;
+
+ /** */
+ private final RowFactory<Row> factory;
+
+ /** */
+ private RangeConditionImpl(
+ SingleScalar lowerBound,
+ SingleScalar upperBound,
+ boolean lowerInclude,
+ boolean upperInclude,
+ RowFactory<Row> factory
+ ) {
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ this.lowerInclude = lowerInclude;
+ this.upperInclude = upperInclude;
+ this.factory = factory;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Row lower() {
+ return lowerRow != null ? lowerRow : (lowerRow = getRow(lowerBound));
+ }
+
+ /** {@inheritDoc} */
+ @Override public Row upper() {
+ return upperRow != null ? upperRow : (upperRow = getRow(upperBound));
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean lowerInclude() {
+ return lowerInclude;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean upperInclude() {
+ return upperInclude;
+ }
+
+ /** Compute row. */
+ private Row getRow(SingleScalar scalar) {
+ Row res = factory.create();
+ scalar.execute(ctx, null, res);
+
+ return res;
+ }
+
+ /** Clear cached rows. */
+ public void clearCache() {
+ lowerRow = upperRow = null;
+ }
+ }
+
+ /** */
+ private class RangeIterableImpl implements RangeIterable<Row> {
+ /** */
+ private final List<RangeCondition<Row>> ranges;
+
+ /** */
+ private final Comparator<Row> comparator;
+
+ /** */
+ private boolean sorted;
+
+ /** */
+ public RangeIterableImpl(List<RangeCondition<Row>> ranges, Comparator<Row> comparator) {
+ this.ranges = ranges;
+ this.comparator = comparator;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return ranges.size();
+ }
+
+ /** {@inheritDoc} */
+ @Override public Iterator<RangeCondition<Row>> iterator() {
+ ranges.forEach(b -> ((RangeConditionImpl)b).clearCache());
+
+ if (ranges.size() == 1)
+ return ranges.iterator();
+
+ // Sort ranges using collation comparator to produce sorted output. There should be no ranges
+ // intersection.
+ // Do not sort again if ranges already were sorted before, different values of correlated variables
+ // should not affect ordering.
+ if (!sorted) {
+ ranges.sort((o1, o2) -> comparator.compare(o1.lower(), o2.lower()));
+ sorted = true;
+ }
+
+ return ranges.iterator();
+ }
+ }
+
/** */
private class BiFieldGetter extends CommonFieldGetter {
/** */
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RangeCondition.java
similarity index 56%
copy from modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RangeCondition.java
index 221809cf67e..3b08962ce34 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RangeCondition.java
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -14,16 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.ignite.internal.processors.query.calcite.externalize;
-import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelInput;
+package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-/** */
-public interface RelInputEx extends RelInput {
- /**
- * @param tag Tag.
- * @return A collation value.
- */
- RelCollation getCollation(String tag);
+/**
+ * A range condition is a search condition which represents a comparison predicate or a BETWEEN predicate.
+ *
+ * <p>Used to define bounds of a range scan.
+ */
+public interface RangeCondition<Row> {
+ /** Lower search row. */
+ public Row lower();
+
+ /** Upper search row. */
+ public Row upper();
+
+ /** Inlusive search by lower row. */
+ public boolean lowerInclude();
+
+ /** Inlusive search by upper row. */
+ public boolean upperInclude();
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RangeIterable.java
similarity index 66%
copy from modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
copy to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RangeIterable.java
index 221809cf67e..7e0be826c19 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RangeIterable.java
@@ -6,7 +6,7 @@
* (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
+ * 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,
@@ -14,16 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.ignite.internal.processors.query.calcite.externalize;
-import org.apache.calcite.rel.RelCollation;
-import org.apache.calcite.rel.RelInput;
+package org.apache.ignite.internal.processors.query.calcite.exec.exp;
-/** */
-public interface RelInputEx extends RelInput {
- /**
- * @param tag Tag.
- * @return A collation value.
- */
- RelCollation getCollation(String tag);
+/**
+ * Iterable over range conditions.
+ */
+public interface RangeIterable<Row> extends Iterable<RangeCondition<Row>> {
+ /** Count of ranges in iterable. */
+ public int size();
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/IndexSpoolNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/IndexSpoolNode.java
index 93e8b714ba2..2ea72a5a6b6 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/IndexSpoolNode.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/IndexSpoolNode.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.processors.query.calcite.exec.rel;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.function.Supplier;
-
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.util.ImmutableBitSet;
@@ -28,6 +27,7 @@ import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext
import org.apache.ignite.internal.processors.query.calcite.exec.RuntimeHashIndex;
import org.apache.ignite.internal.processors.query.calcite.exec.RuntimeIndex;
import org.apache.ignite.internal.processors.query.calcite.exec.RuntimeSortedIndex;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
import org.apache.ignite.internal.util.typedef.F;
import org.jetbrains.annotations.Nullable;
@@ -165,8 +165,7 @@ public class IndexSpoolNode<Row> extends AbstractNode<Row> implements SingleNode
RelCollation collation,
Comparator<Row> comp,
Predicate<Row> filter,
- Supplier<Row> lowerIdxBound,
- Supplier<Row> upperIdxBound
+ RangeIterable<Row> ranges
) {
RuntimeSortedIndex<Row> idx = new RuntimeSortedIndex<>(ctx, collation, comp);
@@ -177,8 +176,7 @@ public class IndexSpoolNode<Row> extends AbstractNode<Row> implements SingleNode
ctx,
rowType,
filter,
- lowerIdxBound,
- upperIdxBound
+ ranges
)
);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
index 221809cf67e..cb64b13e634 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelInputEx.java
@@ -16,8 +16,10 @@
*/
package org.apache.ignite.internal.processors.query.calcite.externalize;
+import java.util.List;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelInput;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
/** */
public interface RelInputEx extends RelInput {
@@ -26,4 +28,11 @@ public interface RelInputEx extends RelInput {
* @return A collation value.
*/
RelCollation getCollation(String tag);
+
+ /**
+ *
+ * @param tag Tag.
+ * @return Search bounds.
+ */
+ List<SearchBounds> getSearchBounds(String tag);
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java
index 04ddded380d..57048ad6c7c 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java
@@ -100,6 +100,10 @@ import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Util;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.MultiBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.RangeBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction;
import org.apache.ignite.internal.processors.query.calcite.trait.DistributionTrait;
@@ -304,6 +308,8 @@ class RelJson {
return toJson((RelDataTypeField)value);
else if (value instanceof ByteString)
return toJson((ByteString)value);
+ else if (value instanceof SearchBounds)
+ return toJson((SearchBounds)value);
else
throw new UnsupportedOperationException("type not serializable: "
+ value + " (type " + value.getClass().getCanonicalName() + ")");
@@ -526,7 +532,7 @@ class RelJson {
else if (type.getSqlTypeName().getFamily() == SqlTypeFamily.BINARY)
literal = toByteString(literal);
- return rexBuilder.makeLiteral(literal, type, false);
+ return rexBuilder.makeLiteral(literal, type, true);
}
throw new UnsupportedOperationException("cannot convert to rex " + o);
@@ -677,6 +683,37 @@ class RelJson {
return list;
}
+ /** */
+ private SearchBounds toSearchBound(RelInput input, Map<String, Object> map) {
+ if (map == null)
+ return null;
+
+ String type = (String)map.get("type");
+
+ if (SearchBounds.Type.EXACT.name().equals(type))
+ return new ExactBounds(null, toRex(input, map.get("bound")));
+ else if (SearchBounds.Type.MULTI.name().equals(type))
+ return new MultiBounds(null, toSearchBoundList(input, (List<Map<String, Object>>)map.get("bounds")));
+ else if (SearchBounds.Type.RANGE.name().equals(type)) {
+ return new RangeBounds(null,
+ toRex(input, map.get("lowerBound")),
+ toRex(input, map.get("upperBound")),
+ (Boolean)map.get("lowerInclude"),
+ (Boolean)map.get("upperInclude")
+ );
+ }
+
+ throw new IllegalStateException("Unsupported search bound type: " + type);
+ }
+
+ /** */
+ List<SearchBounds> toSearchBoundList(RelInput input, List<Map<String, Object>> bounds) {
+ if (bounds == null)
+ return null;
+
+ return bounds.stream().map(b -> toSearchBound(input, b)).collect(Collectors.toList());
+ }
+
/** */
private Object toJson(Enum<?> enum0) {
String key = enum0.getDeclaringClass().getSimpleName() + "#" + enum0.name();
@@ -955,4 +992,27 @@ class RelJson {
private Object toJson(ByteString val) {
return val.toString();
}
+
+ /** */
+ private Object toJson(SearchBounds val) {
+ Map map = map();
+ map.put("type", val.type().name());
+
+ if (val instanceof ExactBounds)
+ map.put("bound", toJson(((ExactBounds)val).bound()));
+ else if (val instanceof MultiBounds)
+ map.put("bounds", toJson(((MultiBounds)val).bounds()));
+ else {
+ assert val instanceof RangeBounds : val;
+
+ RangeBounds val0 = (RangeBounds)val;
+
+ map.put("lowerBound", val0.lowerBound() == null ? null : toJson(val0.lowerBound()));
+ map.put("upperBound", val0.upperBound() == null ? null : toJson(val0.upperBound()));
+ map.put("lowerInclude", val0.lowerInclude());
+ map.put("upperInclude", val0.upperInclude());
+ }
+
+ return map;
+ }
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java
index 7847ef20fbc..522ec373125 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJsonReader.java
@@ -46,6 +46,7 @@ import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
/** */
@@ -282,6 +283,11 @@ public class RelJsonReader {
return relJson.toCollation((List)get(tag));
}
+ /** {@inheritDoc} */
+ @Override public List<SearchBounds> getSearchBounds(String tag) {
+ return relJson.toSearchBoundList(this, (List<Map<String, Object>>)get(tag));
+ }
+
/** {@inheritDoc} */
@Override public RelDistribution getDistribution() {
return relJson.toDistribution(get("distribution"));
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java
index 85ceba2c16a..6377af09f09 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdSelectivity.java
@@ -38,6 +38,7 @@ import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexLocalRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexSlot;
+import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.schema.Statistic;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
@@ -207,6 +208,12 @@ public class IgniteMdSelectivity extends RelMdSelectivity {
continue;
}
+ else if (predKind == SqlKind.SEARCH) {
+ sel *= getTablePredicateBasedSelectivity(rel, mq,
+ RexUtil.expandSearch(rel.getCluster().getRexBuilder(), null, pred));
+
+ continue;
+ }
RexSlot op = null;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePrograms.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePrograms.java
index b4ba54ceee3..838fea05657 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePrograms.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePrograms.java
@@ -53,6 +53,8 @@ public class IgnitePrograms {
final HepPlanner hepPlanner = new HepPlanner(builder.build(), Commons.context(rel), true,
null, Commons.context(rel).config().getCostFactory());
+ hepPlanner.setExecutor(planner.getExecutor());
+
for (RelOptMaterialization materialization : materializations)
hepPlanner.addMaterialization(materialization);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/ExactBounds.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/ExactBounds.java
new file mode 100644
index 00000000000..2e67a1d22a3
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/ExactBounds.java
@@ -0,0 +1,70 @@
+/*
+ * 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.prepare.bounds;
+
+import java.util.Objects;
+import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Exact (equals) bounds holder for search row.
+ */
+public class ExactBounds extends SearchBounds {
+ /** */
+ private final RexNode bound;
+
+ /**
+ */
+ public ExactBounds(RexNode condition, RexNode bound) {
+ super(condition);
+ this.bound = bound;
+ }
+
+ /**
+ * @return Search bound.
+ */
+ public RexNode bound() {
+ return bound;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Type type() {
+ return Type.EXACT;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ return bound.equals(((ExactBounds)o).bound);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(bound);
+ }
+
+ /** */
+ @Override public String toString() {
+ return S.toString(ExactBounds.class, this);
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/MultiBounds.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/MultiBounds.java
new file mode 100644
index 00000000000..5101fe85b69
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/MultiBounds.java
@@ -0,0 +1,73 @@
+/*
+ * 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.prepare.bounds;
+
+import java.util.List;
+import java.util.Objects;
+import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.util.tostring.GridToStringInclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Multiple bounds holder for search row.
+ */
+public class MultiBounds extends SearchBounds {
+ /** */
+ @GridToStringInclude
+ private final List<SearchBounds> bounds;
+
+ /**
+ */
+ public MultiBounds(RexNode condition, List<SearchBounds> bounds) {
+ super(condition);
+ this.bounds = bounds;
+ }
+
+ /**
+ * @return Search bounds.
+ */
+ public List<SearchBounds> bounds() {
+ return bounds;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Type type() {
+ return Type.MULTI;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ return bounds.equals(((MultiBounds)o).bounds);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(bounds);
+ }
+
+ /** */
+ @Override public String toString() {
+ return S.toString(MultiBounds.class, this);
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/RangeBounds.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/RangeBounds.java
new file mode 100644
index 00000000000..0d61987adf5
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/RangeBounds.java
@@ -0,0 +1,113 @@
+/*
+ * 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.prepare.bounds;
+
+import java.util.Objects;
+import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Range bounds holder for search row.
+ */
+public class RangeBounds extends SearchBounds {
+ /** */
+ private final RexNode lowerBound;
+
+ /** */
+ private final RexNode upperBound;
+
+ /** */
+ private final boolean lowerInclude;
+
+ /** */
+ private final boolean upperInclude;
+
+ /**
+ */
+ public RangeBounds(
+ RexNode condition,
+ @Nullable RexNode lowerBound,
+ @Nullable RexNode upperBound,
+ boolean lowerInclude,
+ boolean upperInclude
+ ) {
+ super(condition);
+ this.lowerBound = lowerBound;
+ this.upperBound = upperBound;
+ this.lowerInclude = lowerInclude;
+ this.upperInclude = upperInclude;
+ }
+
+ /**
+ * @return Lower search bound.
+ */
+ public RexNode lowerBound() {
+ return lowerBound;
+ }
+
+ /**
+ * @return Upper search bound.
+ */
+ public RexNode upperBound() {
+ return upperBound;
+ }
+
+ /**
+ * @return {@code True} if the lower bound is inclusive.
+ */
+ public boolean lowerInclude() {
+ return lowerInclude;
+ }
+
+ /**
+ * @return {@code True} if the upper bound is inclusive.
+ */
+ public boolean upperInclude() {
+ return upperInclude;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Type type() {
+ return Type.RANGE;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ return lowerInclude == ((RangeBounds)o).lowerInclude &&
+ upperInclude == ((RangeBounds)o).upperInclude &&
+ Objects.equals(lowerBound, ((RangeBounds)o).lowerBound) &&
+ Objects.equals(upperBound, ((RangeBounds)o).upperBound);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(lowerBound, upperBound, lowerInclude, upperInclude);
+ }
+
+ /** */
+ @Override public String toString() {
+ return S.toString(RangeBounds.class, this);
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/SearchBounds.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/SearchBounds.java
new file mode 100644
index 00000000000..42caf9e4488
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/bounds/SearchBounds.java
@@ -0,0 +1,56 @@
+/*
+ * 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.prepare.bounds;
+
+import org.apache.calcite.rex.RexNode;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Bounds holder for search row.
+ *
+ * This class is describing bounds on per-column basis, and such choice was made to optimize serialization of a plan.
+ */
+public abstract class SearchBounds {
+ /** Condition required only for cost calculation, no serialization needed. */
+ private final RexNode condition;
+
+ /** */
+ protected SearchBounds(@Nullable RexNode condition) {
+ this.condition = condition;
+ }
+
+ /** */
+ public RexNode condition() {
+ return condition;
+ }
+
+ /** */
+ public abstract Type type();
+
+ /** */
+ public enum Type {
+ /** Exact search value. */
+ EXACT,
+
+ /** Range of values. */
+ RANGE,
+
+ /** Multiple values or multiple ranges. */
+ MULTI
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIndexScan.java
index 41916dcef60..c63a59c28f7 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIndexScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/AbstractIndexScan.java
@@ -18,7 +18,6 @@
package org.apache.ignite.internal.processors.query.calcite.rel;
import java.util.List;
-
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
@@ -32,8 +31,10 @@ import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelInputEx;
import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.jetbrains.annotations.Nullable;
/**
@@ -44,7 +45,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
protected final String idxName;
/** */
- protected final IndexConditions idxCond;
+ protected final List<SearchBounds> searchBounds;
/**
* Constructor used for deserialization.
@@ -54,7 +55,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
protected AbstractIndexScan(RelInput input) {
super(input);
idxName = input.getString("index");
- idxCond = new IndexConditions(input);
+ searchBounds = ((RelInputEx)input).getSearchBounds("searchBounds");
}
/** */
@@ -66,21 +67,22 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
String idxName,
@Nullable List<RexNode> proj,
@Nullable RexNode cond,
- @Nullable IndexConditions idxCond,
+ @Nullable List<SearchBounds> searchBounds,
@Nullable ImmutableBitSet reqColumns
) {
super(cluster, traitSet, hints, table, proj, cond, reqColumns);
this.idxName = idxName;
- this.idxCond = idxCond;
+ this.searchBounds = searchBounds;
}
/** {@inheritDoc} */
@Override protected RelWriter explainTerms0(RelWriter pw) {
pw = pw.item("index", idxName);
pw = super.explainTerms0(pw);
+ pw = pw.itemIf("searchBounds", searchBounds, searchBounds != null);
- return idxCond.explainTerms(pw);
+ return pw;
}
/**
@@ -90,34 +92,6 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
return idxName;
}
- /**
- * @return Lower index condition.
- */
- public List<RexNode> lowerCondition() {
- return idxCond == null ? null : idxCond.lowerCondition();
- }
-
- /**
- * @return Lower index condition.
- */
- public List<RexNode> lowerBound() {
- return idxCond == null ? null : idxCond.lowerBound();
- }
-
- /**
- * @return Upper index condition.
- */
- public List<RexNode> upperCondition() {
- return idxCond == null ? null : idxCond.upperCondition();
- }
-
- /**
- * @return Upper index condition.
- */
- public List<RexNode> upperBound() {
- return idxCond == null ? null : idxCond.upperBound();
- }
-
/** {@inheritDoc} */
@Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
double rows = table.getRowCount();
@@ -133,18 +107,11 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
cost = 0;
- if (lowerCondition() != null) {
- double selectivity0 = mq.getSelectivity(this, RexUtil.composeConjunction(builder, lowerCondition()));
-
- selectivity -= 1 - selectivity0;
-
- cost += Math.log(rows) * IgniteCost.ROW_COMPARISON_COST;
- }
-
- if (upperCondition() != null && (lowerCondition() == null || !lowerCondition().equals(upperCondition()))) {
- double selectivity0 = mq.getSelectivity(this, RexUtil.composeConjunction(builder, upperCondition()));
+ if (searchBounds != null) {
+ selectivity = mq.getSelectivity(this, RexUtil.composeConjunction(builder,
+ Commons.transform(searchBounds, b -> b == null ? null : b.condition())));
- selectivity -= 1 - selectivity0;
+ cost = Math.log(rows) * IgniteCost.ROW_COMPARISON_COST;
}
rows *= selectivity;
@@ -160,7 +127,7 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
}
/** */
- public IndexConditions indexConditions() {
- return idxCond;
+ public List<SearchBounds> searchBounds() {
+ return searchBounds;
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
index 981744017ca..a5a3c82c9b3 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
@@ -27,7 +27,7 @@ import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils.changeTraits;
@@ -77,11 +77,11 @@ public class IgniteIndexScan extends AbstractIndexScan implements SourceAwareIgn
String idxName,
@Nullable List<RexNode> proj,
@Nullable RexNode cond,
- @Nullable IndexConditions idxCond,
+ @Nullable List<SearchBounds> searchBounds,
@Nullable ImmutableBitSet requiredCols,
RelCollation collation
) {
- this(-1L, cluster, traits, tbl, idxName, proj, cond, idxCond, requiredCols, collation);
+ this(-1L, cluster, traits, tbl, idxName, proj, cond, searchBounds, requiredCols, collation);
}
/**
@@ -103,11 +103,11 @@ public class IgniteIndexScan extends AbstractIndexScan implements SourceAwareIgn
String idxName,
@Nullable List<RexNode> proj,
@Nullable RexNode cond,
- @Nullable IndexConditions idxCond,
+ @Nullable List<SearchBounds> searchBounds,
@Nullable ImmutableBitSet requiredCols,
RelCollation collation
) {
- super(cluster, traits, ImmutableList.of(), tbl, idxName, proj, cond, idxCond, requiredCols);
+ super(cluster, traits, ImmutableList.of(), tbl, idxName, proj, cond, searchBounds, requiredCols);
this.sourceId = sourceId;
this.collation = collation;
@@ -133,13 +133,13 @@ public class IgniteIndexScan extends AbstractIndexScan implements SourceAwareIgn
/** {@inheritDoc} */
@Override public IgniteRel clone(long sourceId) {
return new IgniteIndexScan(sourceId, getCluster(), getTraitSet(), getTable(),
- idxName, projects, condition, idxCond, requiredColumns, collation);
+ idxName, projects, condition, searchBounds, requiredColumns, collation);
}
/** {@inheritDoc} */
@Override public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs) {
return new IgniteIndexScan(sourceId, cluster, getTraitSet(), getTable(),
- idxName, projects, condition, idxCond, requiredColumns, collation);
+ idxName, projects, condition, searchBounds, requiredColumns, collation);
}
/** {@inheritDoc} */
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSortedIndexSpool.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSortedIndexSpool.java
index 8dfea7495b1..33d45ea51a7 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSortedIndexSpool.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteSortedIndexSpool.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.processors.query.calcite.rel;
import java.util.List;
import java.util.Objects;
-
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
@@ -31,9 +30,10 @@ import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.core.Spool;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelInputEx;
import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost;
import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
/**
* Relational operator that returns the sorted contents of a table
@@ -43,8 +43,8 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
/** */
private final RelCollation collation;
- /** Index condition. */
- private final IndexConditions idxCond;
+ /** */
+ private final List<SearchBounds> searchBounds;
/** Filters. */
protected final RexNode condition;
@@ -56,14 +56,13 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
RelNode input,
RelCollation collation,
RexNode condition,
- IndexConditions idxCond
+ List<SearchBounds> searchBounds
) {
super(cluster, traits, input, Type.LAZY, Type.EAGER);
- assert Objects.nonNull(idxCond);
assert Objects.nonNull(condition);
- this.idxCond = idxCond;
+ this.searchBounds = searchBounds;
this.condition = condition;
this.collation = collation;
}
@@ -79,7 +78,7 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
input.getInputs().get(0),
input.getCollation(),
input.getExpression("condition"),
- new IndexConditions(input)
+ ((RelInputEx)input).getSearchBounds("searchBounds")
);
}
@@ -90,12 +89,12 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
/** */
@Override public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs) {
- return new IgniteSortedIndexSpool(cluster, getTraitSet(), inputs.get(0), collation, condition, idxCond);
+ return new IgniteSortedIndexSpool(cluster, getTraitSet(), inputs.get(0), collation, condition, searchBounds);
}
/** {@inheritDoc} */
@Override protected Spool copy(RelTraitSet traitSet, RelNode input, Type readType, Type writeType) {
- return new IgniteSortedIndexSpool(getCluster(), traitSet, input, collation, condition, idxCond);
+ return new IgniteSortedIndexSpool(getCluster(), traitSet, input, collation, condition, searchBounds);
}
/** {@inheritDoc} */
@@ -109,8 +108,9 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
writer.item("condition", condition);
writer.item("collation", collation);
+ writer.itemIf("searchBounds", searchBounds, searchBounds != null);
- return idxCond.explainTerms(writer);
+ return writer;
}
/** {@inheritDoc} */
@@ -119,8 +119,8 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
}
/** */
- public IndexConditions indexCondition() {
- return idxCond;
+ public List<SearchBounds> searchBounds() {
+ return searchBounds;
}
/** */
@@ -140,7 +140,7 @@ public class IgniteSortedIndexSpool extends Spool implements IgniteRel {
double totalBytes = rowCnt * bytesPerRow;
double cpuCost;
- if (idxCond.lowerCondition() != null)
+ if (searchBounds != null)
cpuCost = Math.log(rowCnt) * IgniteCost.ROW_COMPARISON_COST;
else
cpuCost = rowCnt * IgniteCost.ROW_PASS_THROUGH_COST;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java
index 09673940770..5aa5c67e4e7 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java
@@ -117,8 +117,12 @@ public abstract class ProjectableFilterableTableScan extends TableScan {
/** */
protected RelWriter explainTerms0(RelWriter pw) {
+ if (condition != null) {
+ pw.item("filters", pw.nest() ? condition :
+ RexUtil.expandSearch(getCluster().getRexBuilder(), null, condition));
+ }
+
return pw
- .itemIf("filters", condition, condition != null)
.itemIf("projects", projects, projects != null)
.itemIf("requiredColumns", requiredColumns, requiredColumns != null);
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java
index af11ef2d694..1cb7dd0cd80 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java
@@ -24,10 +24,10 @@ import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.AbstractIndexScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
import org.jetbrains.annotations.Nullable;
/** */
@@ -45,7 +45,7 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
IgniteTable tbl = table.unwrap(IgniteTable.class);
IgniteIndex idx = tbl.getIndex(idxName);
- IndexConditions idxCond = idx.toIndexCondition(cluster, cond, requiredColumns);
+ List<SearchBounds> searchBounds = idx.toSearchBounds(cluster, cond, requiredColumns);
return new IgniteLogicalIndexScan(
cluster,
@@ -54,7 +54,7 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
idxName,
proj,
cond,
- idxCond,
+ searchBounds,
requiredColumns);
}
@@ -66,7 +66,7 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
* @param idxName Index name.
* @param proj Projects.
* @param cond Filters.
- * @param idxCond Index conditions.
+ * @param searchBounds Index search bounds.
* @param requiredCols Participating columns.
*/
private IgniteLogicalIndexScan(
@@ -76,9 +76,9 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
String idxName,
@Nullable List<RexNode> proj,
@Nullable RexNode cond,
- @Nullable IndexConditions idxCond,
+ @Nullable List<SearchBounds> searchBounds,
@Nullable ImmutableBitSet requiredCols
) {
- super(cluster, traits, ImmutableList.of(), tbl, idxName, proj, cond, idxCond, requiredCols);
+ super(cluster, traits, ImmutableList.of(), tbl, idxName, proj, cond, searchBounds, requiredCols);
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToHashIndexSpoolRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToHashIndexSpoolRule.java
index dae8f276840..f3bec5479e4 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToHashIndexSpoolRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToHashIndexSpoolRule.java
@@ -64,13 +64,7 @@ public class FilterSpoolMergeToHashIndexSpoolRule extends RelRule<FilterSpoolMer
RelNode input = spool.getInput();
- List<RexNode> searchRow = RexUtils.buildHashSearchRow(
- cluster,
- filter.getCondition(),
- spool.getRowType(),
- null,
- false
- );
+ List<RexNode> searchRow = RexUtils.buildHashSearchRow(cluster, filter.getCondition(), spool.getRowType());
if (F.isEmpty(searchRow))
return;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
index 024be7e6457..aa55c2e2081 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterSpoolMergeToSortedIndexSpoolRule.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
+import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
@@ -31,13 +32,12 @@ import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Spool;
-import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableSpool;
import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTrait;
import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.apache.ignite.internal.util.typedef.F;
import org.immutables.value.Value;
@@ -72,7 +72,7 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends RelRule<FilterSpoolM
RelCollation inCollation = TraitUtils.collation(input);
- IndexConditions idxCond = RexUtils.buildSortedIndexConditions(
+ List<SearchBounds> bounds = RexUtils.buildSortedSearchBounds(
cluster,
inCollation,
filter.getCondition(),
@@ -80,31 +80,21 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends RelRule<FilterSpoolM
null
);
- if (F.isEmpty(idxCond.lowerCondition()) && F.isEmpty(idxCond.upperCondition()))
+ if (F.isEmpty(bounds))
return;
RelCollation traitCollation;
RelCollation searchCollation;
if (inCollation == null || inCollation.isDefault()) {
- // Create collation by index condition.
- List<RexNode> lowerBound = idxCond.lowerBound();
- List<RexNode> upperBound = idxCond.upperBound();
-
- assert lowerBound == null || upperBound == null || lowerBound.size() == upperBound.size();
-
- int cardinality = lowerBound != null ? lowerBound.size() : upperBound.size();
-
- List<Integer> equalsFields = new ArrayList<>(cardinality);
- List<Integer> otherFields = new ArrayList<>(cardinality);
+ // Create collation by index bounds.
+ List<Integer> equalsFields = new ArrayList<>(bounds.size());
+ List<Integer> otherFields = new ArrayList<>(bounds.size());
// First, add all equality filters to collation, then add other fields.
- for (int i = 0; i < cardinality; i++) {
- RexNode lowerNode = lowerBound != null ? lowerBound.get(i) : null;
- RexNode upperNode = upperBound != null ? upperBound.get(i) : null;
-
- if (RexUtils.isNotNull(lowerNode) || RexUtils.isNotNull(upperNode))
- (F.eq(lowerNode, upperNode) ? equalsFields : otherFields).add(i);
+ for (int i = 0; i < bounds.size(); i++) {
+ if (bounds.get(i) != null)
+ (bounds.get(i).type() == SearchBounds.Type.EXACT ? equalsFields : otherFields).add(i);
}
searchCollation = traitCollation = TraitUtils.createCollation(F.concat(true, equalsFields, otherFields));
@@ -113,7 +103,10 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends RelRule<FilterSpoolM
// Create search collation as a prefix of input collation.
traitCollation = inCollation;
- Set<Integer> searchKeys = idxCond.keys();
+ Set<Integer> searchKeys = Ord.zip(bounds).stream()
+ .filter(v -> v.e != null)
+ .map(v -> v.i)
+ .collect(Collectors.toSet());
List<RelFieldCollation> collationFields = inCollation.getFieldCollations().subList(0, searchKeys.size());
@@ -130,7 +123,7 @@ public class FilterSpoolMergeToSortedIndexSpoolRule extends RelRule<FilterSpoolM
convert(input, input.getTraitSet().replace(traitCollation)),
searchCollation,
filter.getCondition(),
- idxCond
+ bounds
);
call.transformTo(res);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/LogicalScanConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/LogicalScanConverterRule.java
index 348f20fd18a..28053b9c701 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/LogicalScanConverterRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/LogicalScanConverterRule.java
@@ -106,7 +106,7 @@ public abstract class LogicalScanConverterRule<T extends ProjectableFilterableTa
rel.indexName(),
rel.projects(),
rel.condition(),
- rel.indexConditions(),
+ rel.searchBounds(),
rel.requiredColumns(),
idx.collation()
);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java
index b8a8e62b7f5..76459398d86 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.query.calcite.rule.logical;
import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptPredicateList;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelRule;
@@ -27,6 +28,7 @@ import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.rex.RexSimplify;
import org.apache.calcite.rex.RexUtil;
import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalIndexScan;
@@ -84,9 +86,13 @@ public abstract class FilterScanMergeRule<T extends ProjectableFilterableTableSc
if (scan.condition() != null)
condition = RexUtil.composeConjunction(builder, F.asList(scan.condition(), condition));
+ // We need to replace RexLocalRef with RexInputRef because "simplify" doesn't understand local refs.
+ condition = RexUtils.replaceLocalRefs(condition);
+ condition = new RexSimplify(builder, RelOptPredicateList.EMPTY, call.getPlanner().getExecutor())
+ .simplifyUnknownAsFalse(condition);
+
// We need to replace RexInputRef with RexLocalRef because TableScan doesn't have inputs.
- // TODO SEARCH support
- condition = RexUtils.replaceInputRefs(RexUtil.expandSearch(builder, null, condition));
+ condition = RexUtils.replaceInputRefs(condition);
// Set default traits, real traits will be calculated for physical node.
RelTraitSet trait = cluster.traitSet();
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 5b969b03baa..705c5288afb 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
@@ -21,7 +21,6 @@ import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelCollation;
@@ -35,10 +34,11 @@ import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryCont
import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
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.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.rel.logical.IgniteLogicalIndexScan;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.apache.ignite.spi.indexing.IndexingQueryFilter;
import org.apache.ignite.spi.indexing.IndexingQueryFilterImpl;
@@ -104,16 +104,14 @@ public class CacheIndexImpl implements IgniteIndex {
ExecutionContext<Row> execCtx,
ColocationGroup group,
Predicate<Row> filters,
- Supplier<Row> lowerIdxConditions,
- Supplier<Row> upperIdxConditions,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
) {
UUID localNodeId = execCtx.localNodeId();
if (group.nodeIds().contains(localNodeId) && idx != null) {
return new IndexScan<>(execCtx, tbl.descriptor(), idx.unwrap(InlineIndex.class), collation.getKeys(),
- group.partitions(localNodeId), filters, lowerIdxConditions, upperIdxConditions, rowTransformer,
- requiredColumns);
+ group.partitions(localNodeId), filters, ranges, rowTransformer, requiredColumns);
}
return Collections.emptyList();
@@ -142,7 +140,7 @@ public class CacheIndexImpl implements IgniteIndex {
}
/** {@inheritDoc} */
- @Override public IndexConditions toIndexCondition(
+ @Override public List<SearchBounds> toSearchBounds(
RelOptCluster cluster,
@Nullable RexNode cond,
@Nullable ImmutableBitSet requiredColumns
@@ -154,7 +152,7 @@ public class CacheIndexImpl implements IgniteIndex {
collation = collation.apply(Commons.mapping(requiredColumns, rowType.getFieldCount()));
if (!collation.getFieldCollations().isEmpty()) {
- return RexUtils.buildSortedIndexConditions(
+ return RexUtils.buildSortedSearchBounds(
cluster,
collation,
cond,
@@ -164,6 +162,6 @@ public class CacheIndexImpl implements IgniteIndex {
}
// Empty index find predicate.
- return new IndexConditions();
+ return null;
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
index 5753302dbf0..5df2b9d60d3 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
@@ -19,16 +19,16 @@ package org.apache.ignite.internal.processors.query.calcite.schema;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+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.rel.logical.IgniteLogicalIndexScan;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
import org.jetbrains.annotations.Nullable;
/**
@@ -70,7 +70,7 @@ public interface IgniteIndex {
* @param requiredColumns Set of columns to extract from original row.
* @return Index condition.
*/
- public IndexConditions toIndexCondition(
+ public List<SearchBounds> toSearchBounds(
RelOptCluster cluster,
@Nullable RexNode cond,
@Nullable ImmutableBitSet requiredColumns
@@ -81,8 +81,7 @@ public interface IgniteIndex {
ExecutionContext<Row> execCtx,
ColocationGroup grp,
Predicate<Row> filters,
- Supplier<Row> lowerIdxConditions,
- Supplier<Row> upperIdxConditions,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewIndexImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewIndexImpl.java
index 050678107cc..eb203b1c0cb 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewIndexImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewIndexImpl.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.processors.query.calcite.schema;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelCollation;
@@ -29,9 +28,10 @@ import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
import org.apache.ignite.internal.processors.query.calcite.exec.SystemViewScan;
+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.rel.logical.IgniteLogicalIndexScan;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.jetbrains.annotations.Nullable;
@@ -82,15 +82,14 @@ public class SystemViewIndexImpl implements IgniteIndex {
ExecutionContext<Row> execCtx,
ColocationGroup grp,
Predicate<Row> filters,
- Supplier<Row> lowerIdxConditions,
- Supplier<Row> upperIdxConditions,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
) {
return new SystemViewScan<>(
execCtx,
tbl.descriptor(),
- lowerIdxConditions, // Should have the same values as upperIdxConditions.
+ ranges,
filters,
rowTransformer,
requiredColumns
@@ -103,18 +102,16 @@ public class SystemViewIndexImpl implements IgniteIndex {
}
/** {@inheritDoc} */
- @Override public IndexConditions toIndexCondition(
+ @Override public List<SearchBounds> toSearchBounds(
RelOptCluster cluster,
@Nullable RexNode cond,
@Nullable ImmutableBitSet requiredColumns
) {
if (cond == null)
- return new IndexConditions();
+ return null;
RelDataType rowType = tbl.getRowType(cluster.getTypeFactory());
- List<RexNode> searchRow = RexUtils.buildHashSearchRow(cluster, cond, rowType, requiredColumns, true);
-
- return new IndexConditions(searchRow, searchRow, searchRow, searchRow);
+ return RexUtils.buildHashSearchBounds(cluster, cond, rowType, requiredColumns, true);
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
index 29744c06070..bc9a0a81005 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
@@ -52,6 +52,8 @@ import org.apache.calcite.util.ControlFlowException;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.processors.query.calcite.externalize.RelInputEx;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
@@ -266,7 +268,7 @@ public class TraitUtils {
RelTraitSet traitSet0 = traitSet;
- return new RelInput() {
+ return new RelInputEx() {
@Override public RelOptCluster getCluster() {
return input.getCluster();
}
@@ -359,6 +361,14 @@ public class TraitUtils {
@Override public boolean getBoolean(String tag, boolean default_) {
return input.getBoolean(tag, default_);
}
+
+ @Override public RelCollation getCollation(String tag) {
+ return ((RelInputEx)input).getCollation(tag);
+ }
+
+ @Override public List<SearchBounds> getSearchBounds(String tag) {
+ return ((RelInputEx)input).getSearchBounds(tag);
+ }
};
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IndexConditions.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IndexConditions.java
deleted file mode 100644
index b0a0212febb..00000000000
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IndexConditions.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.util;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.calcite.rel.RelInput;
-import org.apache.calcite.rel.RelWriter;
-import org.apache.calcite.rex.RexNode;
-import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.internal.util.typedef.internal.S;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Index conditions and bounds holder.
- * Conditions are not printed to terms (serialized). They are used only to calculate selectivity.
- */
-public class IndexConditions {
- /** */
- private final List<RexNode> lowerCond;
-
- /** */
- private final List<RexNode> upperCond;
-
- /** */
- private final List<RexNode> lowerBound;
-
- /** */
- private final List<RexNode> upperBound;
-
- /** */
- public IndexConditions() {
- this(null, null, null, null);
- }
-
- /**
- */
- public IndexConditions(
- @Nullable List<RexNode> lowerCond,
- @Nullable List<RexNode> upperCond,
- @Nullable List<RexNode> lowerBound,
- @Nullable List<RexNode> upperBound
- ) {
- this.lowerCond = lowerCond;
- this.upperCond = upperCond;
- this.lowerBound = lowerBound;
- this.upperBound = upperBound;
- }
-
- /** */
- public IndexConditions(RelInput input) {
- lowerCond = null;
- upperCond = null;
- lowerBound = input.get("lower") == null ? null : input.getExpressionList("lower");
- upperBound = input.get("upper") == null ? null : input.getExpressionList("upper");
- }
-
- /**
- * @return Lower index condition.
- */
- public List<RexNode> lowerCondition() {
- return lowerCond;
- }
-
- /**
- * @return Upper index condition.
- */
- public List<RexNode> upperCondition() {
- return upperCond;
- }
-
- /**
- * @return Lower index bounds (a row with values at the index columns).
- */
- public List<RexNode> lowerBound() {
- return lowerBound;
- }
-
- /**
- * @return Upper index bounds (a row with values at the index columns).
- */
- public List<RexNode> upperBound() {
- return upperBound;
- }
-
- /** */
- public Set<Integer> keys() {
- if (upperBound == null && lowerBound == null)
- return Collections.emptySet();
-
- Set<Integer> keys = new HashSet<>();
-
- int cols = lowerBound != null ? lowerBound.size() : upperBound.size();
-
- for (int i = 0; i < cols; ++i ) {
- if (upperBound != null && RexUtils.isNotNull(upperBound.get(i))
- || lowerBound != null && RexUtils.isNotNull(lowerBound.get(i)))
- keys.add(i);
- }
-
- return Collections.unmodifiableSet(keys);
- }
-
- /**
- * Describes index bounds.
- *
- * @param pw Plan writer
- * @return Plan writer for fluent-explain pattern
- */
- public RelWriter explainTerms(RelWriter pw) {
- return pw
- .itemIf("lower", lowerBound, !F.isEmpty(lowerBound))
- .itemIf("upper", upperBound, !F.isEmpty(upperBound));
- }
-
- /** */
- @Override public String toString() {
- return S.toString(IndexConditions.class, this,
- "lower", lowerCond,
- "upper", upperCond,
- "lowerBound", lowerBound,
- "upperBound", upperBound);
- }
-}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
index b00b1dcbf5e..e8e120edd73 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
@@ -49,6 +49,7 @@ import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexSimplify;
import org.apache.calcite.rex.RexSlot;
+import org.apache.calcite.rex.RexUnknownAs;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.rex.RexVisitor;
import org.apache.calcite.rex.RexVisitorImpl;
@@ -59,22 +60,35 @@ import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.ControlFlowException;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.Sarg;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.mapping.MappingType;
import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.MultiBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.RangeBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.Nullable;
+import static org.apache.calcite.rex.RexUtil.removeCast;
+import static org.apache.calcite.rex.RexUtil.sargRef;
import static org.apache.calcite.sql.SqlKind.EQUALS;
import static org.apache.calcite.sql.SqlKind.GREATER_THAN;
import static org.apache.calcite.sql.SqlKind.GREATER_THAN_OR_EQUAL;
+import static org.apache.calcite.sql.SqlKind.IS_NOT_NULL;
+import static org.apache.calcite.sql.SqlKind.IS_NULL;
import static org.apache.calcite.sql.SqlKind.LESS_THAN;
import static org.apache.calcite.sql.SqlKind.LESS_THAN_OR_EQUAL;
+import static org.apache.calcite.sql.SqlKind.SEARCH;
/** */
public class RexUtils {
+ /** Maximum amount of search bounds tuples per scan. */
+ public static final int MAX_SEARCH_BOUNDS_COMPLEXITY = 100;
+
/** */
public static RexNode makeCast(RexBuilder builder, RexNode node, RelDataType type) {
return TypeUtils.needCast(builder.getTypeFactory(), node.getType(), type) ? builder.makeCast(type, node) : node;
@@ -149,17 +163,24 @@ public class RexUtils {
return true;
}
+ /** Binary comparison operations. */
+ private static final Set<SqlKind> BINARY_COMPARISON =
+ EnumSet.of(EQUALS, LESS_THAN, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL);
+
/** Supported index operations. */
private static final Set<SqlKind> TREE_INDEX_COMPARISON =
EnumSet.of(
+ SEARCH,
+ IS_NULL,
+ IS_NOT_NULL,
EQUALS,
LESS_THAN, GREATER_THAN,
GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL);
/**
- * Builds index conditions.
+ * Builds sorted search bounds.
*/
- public static IndexConditions buildSortedIndexConditions(
+ public static List<SearchBounds> buildSortedSearchBounds(
RelOptCluster cluster,
RelCollation collation,
RexNode condition,
@@ -167,17 +188,14 @@ public class RexUtils {
ImmutableBitSet requiredColumns
) {
if (condition == null)
- return new IndexConditions();
+ return null;
condition = RexUtil.toCnf(builder(cluster), condition);
Map<Integer, List<RexCall>> fieldsToPredicates = mapPredicatesToFields(condition, cluster);
if (F.isEmpty(fieldsToPredicates))
- return new IndexConditions();
-
- List<RexNode> lower = new ArrayList<>();
- List<RexNode> upper = new ArrayList<>();
+ return null;
// Force collation for all fields of the condition.
if (collation == null || collation.isDefault()) {
@@ -191,6 +209,17 @@ public class RexUtils {
collation = TraitUtils.createCollation(F.concat(true, equalsFields, otherFields));
}
+ List<RelDataType> types = RelOptUtil.getFieldTypeList(rowType);
+
+ Mappings.TargetMapping mapping = null;
+
+ if (requiredColumns != null)
+ mapping = Commons.inverseMapping(requiredColumns, types.size());
+
+ List<SearchBounds> bounds = Arrays.asList(new SearchBounds[types.size()]);
+ boolean boundsEmpty = true;
+ int prevComplexity = 1;
+
for (int i = 0; i < collation.getFieldCollations().size(); i++) {
RelFieldCollation fc = collation.getFieldCollations().get(i);
@@ -201,92 +230,37 @@ public class RexUtils {
if (F.isEmpty(collFldPreds))
break;
- RexNode bestUpper = null;
- RexNode bestLower = null;
-
- for (RexCall pred : collFldPreds) {
- if (U.assertionsEnabled()) {
- RexNode cond = RexUtil.removeCast(pred.operands.get(1));
-
- assert idxOpSupports(cond) : cond;
- }
+ if (mapping != null)
+ collFldIdx = mapping.getSourceOpt(collFldIdx);
- boolean lowerBoundBelow = !fc.getDirection().isDescending();
- SqlOperator op = pred.getOperator();
- switch (op.kind) {
- case EQUALS:
- bestUpper = pred;
- bestLower = pred;
- break;
-
- case LESS_THAN:
- case LESS_THAN_OR_EQUAL:
- lowerBoundBelow = !lowerBoundBelow;
- // Fall through.
-
- case GREATER_THAN:
- case GREATER_THAN_OR_EQUAL:
- if (lowerBoundBelow)
- bestLower = pred;
- else
- bestUpper = pred;
- break;
+ SearchBounds fldBounds = createBounds(fc, collFldPreds, cluster, types.get(collFldIdx), prevComplexity);
- default:
- throw new AssertionError("Unknown condition: " + op.kind);
- }
+ if (fldBounds == null)
+ break;
- if (bestUpper != null && bestLower != null)
- break; // We've found either "=" condition or both lower and upper.
- }
+ boundsEmpty = false;
- if (bestLower == null && bestUpper == null)
- break; // No bounds, so break the loop.
+ bounds.set(collFldIdx, fldBounds);
- if (bestLower != null && bestUpper != null) { // "x>5 AND x<10"
- upper.add(bestUpper);
- lower.add(bestLower);
+ if (fldBounds instanceof MultiBounds) {
+ prevComplexity *= ((MultiBounds)fldBounds).bounds().size();
- if (bestLower != bestUpper)
+ // Any bounds after multi range bounds are not allowed, since it can cause intervals intersection.
+ if (((MultiBounds)fldBounds).bounds().stream().anyMatch(b -> b.type() != SearchBounds.Type.EXACT))
break;
}
- else if (bestLower != null) { // "x>5"
- lower.add(bestLower);
-
- break; // TODO https://issues.apache.org/jira/browse/IGNITE-13568
- }
- else { // "x<10"
- upper.add(bestUpper);
+ if (fldBounds.type() == SearchBounds.Type.RANGE)
break; // TODO https://issues.apache.org/jira/browse/IGNITE-13568
- }
}
- Mappings.TargetMapping mapping = null;
-
- if (requiredColumns != null)
- mapping = Commons.inverseMapping(requiredColumns, rowType.getFieldCount());
-
- List<RexNode> lowerBound = null;
- List<RexNode> upperBound = null;
-
- if (!F.isEmpty(lower))
- lowerBound = asBound(cluster, lower, rowType, mapping);
- else
- lower = null;
-
- if (!F.isEmpty(upper))
- upperBound = asBound(cluster, upper, rowType, mapping);
- else
- upper = null;
-
- return new IndexConditions(lower, upper, lowerBound, upperBound);
+ return boundsEmpty ? null : bounds;
}
/**
- * Builds index conditions.
+ * Builds hash index search bounds.
*/
- public static List<RexNode> buildHashSearchRow(
+ public static List<SearchBounds> buildHashSearchBounds(
RelOptCluster cluster,
RexNode condition,
RelDataType rowType,
@@ -300,7 +274,14 @@ public class RexUtils {
if (F.isEmpty(fieldsToPredicates))
return null;
- List<RexNode> searchPreds = null;
+ List<SearchBounds> bounds = null;
+
+ List<RelDataType> types = RelOptUtil.getFieldTypeList(rowType);
+
+ Mappings.TargetMapping mapping = null;
+
+ if (requiredColumns != null)
+ mapping = Commons.inverseMapping(requiredColumns, types.size());
for (int fldIdx : fieldsToPredicates.keySet()) {
List<RexCall> collFldPreds = fieldsToPredicates.get(fldIdx);
@@ -309,12 +290,6 @@ public class RexUtils {
break;
for (RexCall pred : collFldPreds) {
- if (U.assertionsEnabled()) {
- RexNode cond = RexUtil.removeCast(pred.operands.get(1));
-
- assert idxOpSupports(cond) : cond;
- }
-
if (pred.getOperator().kind != SqlKind.EQUALS) {
if (ignoreNotEqualPreds)
continue;
@@ -322,22 +297,160 @@ public class RexUtils {
return null;
}
- if (searchPreds == null)
- searchPreds = new ArrayList<>();
+ if (bounds == null)
+ bounds = Arrays.asList(new SearchBounds[types.size()]);
+
+ if (mapping != null)
+ fldIdx = mapping.getSourceOpt(fldIdx);
- searchPreds.add(pred);
+ bounds.set(fldIdx, createBounds(null, Collections.singletonList(pred), cluster, types.get(fldIdx), 1));
}
}
- if (searchPreds == null)
- return null;
+ return bounds;
+ }
- Mappings.TargetMapping mapping = null;
+ /** Create index search bound by conditions of the field. */
+ private static SearchBounds createBounds(
+ @Nullable RelFieldCollation fc, // Can be null for EQUALS condition.
+ List<RexCall> collFldPreds,
+ RelOptCluster cluster,
+ RelDataType fldType,
+ int prevComplexity
+ ) {
+ RexBuilder builder = builder(cluster);
- if (requiredColumns != null)
- mapping = Commons.inverseMapping(requiredColumns, rowType.getFieldCount());
+ RexNode nullVal = builder.makeNullLiteral(fldType);
+
+ RexNode upperCond = null;
+ RexNode lowerCond = null;
+ RexNode upperBound = null;
+ RexNode lowerBound = null;
+ boolean upperInclude = true;
+ boolean lowerInclude = true;
+
+ for (RexCall pred : collFldPreds) {
+ RexNode val = null;
+
+ if (isBinaryComparison(pred)) {
+ val = removeCast(pred.operands.get(1));
+
+ assert idxOpSupports(val) : val;
+
+ val = makeCast(builder, val, fldType);
+ }
+
+ SqlOperator op = pred.getOperator();
+
+ if (op.kind == EQUALS)
+ return new ExactBounds(pred, val);
+ else if (op.kind == IS_NULL)
+ return new ExactBounds(pred, nullVal);
+ else if (op.kind == SEARCH) {
+ Sarg<?> sarg = ((RexLiteral)pred.operands.get(1)).getValueAs(Sarg.class);
+
+ int complexity = prevComplexity * sarg.complexity();
- return asBound(cluster, searchPreds, rowType, mapping);
+ // Limit amount of search bounds tuples.
+ if (complexity > MAX_SEARCH_BOUNDS_COMPLEXITY)
+ return null;
+
+ RexNode sargCond = sargRef(builder, pred.operands.get(0), sarg, fldType, RexUnknownAs.UNKNOWN);
+ List<RexNode> disjunctions = RelOptUtil.disjunctions(RexUtil.toDnf(builder, sargCond));
+ List<SearchBounds> bounds = new ArrayList<>(disjunctions.size());
+
+ for (RexNode bound : disjunctions) {
+ List<RexNode> conjunctions = RelOptUtil.conjunctions(bound);
+ List<RexCall> calls = new ArrayList<>(conjunctions.size());
+
+ for (RexNode rexNode : conjunctions) {
+ if (isSupportedTreeComparison(rexNode))
+ calls.add((RexCall)rexNode);
+ else
+ return null; // Cannot filter using this predicate (NOT_EQUALS for example).
+ }
+
+ bounds.add(createBounds(fc, calls, cluster, fldType, complexity));
+ }
+
+ if (bounds.size() == 1)
+ return bounds.get(0);
+
+ return new MultiBounds(pred, bounds);
+ }
+
+ // Range bounds.
+ boolean lowerBoundBelow = !fc.getDirection().isDescending();
+ switch (op.kind) {
+ case LESS_THAN:
+ case LESS_THAN_OR_EQUAL:
+ lowerBoundBelow = !lowerBoundBelow;
+ // Fall through.
+
+ case GREATER_THAN:
+ case GREATER_THAN_OR_EQUAL:
+ if (lowerBoundBelow) {
+ lowerCond = pred;
+ lowerBound = val;
+
+ if (op.kind == GREATER_THAN || op.kind == LESS_THAN)
+ lowerInclude = false;
+ }
+ else {
+ upperCond = pred;
+ upperBound = val;
+
+ if (op.kind == GREATER_THAN || op.kind == LESS_THAN)
+ upperInclude = false;
+ }
+
+ case IS_NOT_NULL:
+ if (fc.nullDirection == RelFieldCollation.NullDirection.FIRST && lowerBound == null) {
+ lowerCond = pred;
+ lowerBound = nullVal;
+ lowerInclude = false;
+ }
+ else if (fc.nullDirection == RelFieldCollation.NullDirection.LAST && upperBound == null) {
+ upperCond = pred;
+ upperBound = nullVal;
+ upperInclude = false;
+ }
+ break;
+
+ default:
+ throw new AssertionError("Unknown condition: " + op.kind);
+ }
+ }
+
+ if (lowerBound == null && upperBound == null)
+ return null; // No bounds.
+
+ // Found upper bound, lower bound or both.
+ RexNode cond = lowerCond == null ? upperCond :
+ upperCond == null ? lowerCond :
+ upperCond == lowerCond ? lowerCond : builder.makeCall(SqlStdOperatorTable.AND, lowerCond, upperCond);
+
+ return new RangeBounds(cond, lowerBound, upperBound, lowerInclude, upperInclude);
+ }
+
+ /**
+ * Builds index conditions.
+ */
+ public static List<RexNode> buildHashSearchRow(
+ RelOptCluster cluster,
+ RexNode condition,
+ RelDataType rowType
+ ) {
+ List<SearchBounds> searchBounds = buildHashSearchBounds(cluster, condition, rowType, null, false);
+
+ if (searchBounds == null)
+ return null;
+
+ return Commons.transform(searchBounds, b -> {
+ assert b == null || b instanceof ExactBounds : b;
+
+ return b == null ? null : ((ExactBounds)b).bound();
+ });
}
/** */
@@ -347,18 +460,28 @@ public class RexUtils {
Map<Integer, List<RexCall>> res = new HashMap<>(conjunctions.size());
for (RexNode rexNode : conjunctions) {
- if (!isBinaryComparison(rexNode))
+ if (!isSupportedTreeComparison(rexNode))
continue;
RexCall predCall = (RexCall)rexNode;
- RexSlot ref = (RexSlot)extractRef(predCall);
+ RexSlot ref;
- if (ref == null)
- continue;
+ if (isBinaryComparison(rexNode)) {
+ ref = (RexSlot)extractRefFromBinary(predCall, cluster);
+
+ if (ref == null)
+ continue;
- // Let RexLocalRef be on the left side.
- if (refOnTheRight(predCall))
- predCall = (RexCall)RexUtil.invert(builder(cluster), predCall);
+ // Let RexLocalRef be on the left side.
+ if (refOnTheRight(predCall))
+ predCall = (RexCall)RexUtil.invert(builder(cluster), predCall);
+ }
+ else {
+ ref = (RexSlot)extractRefFromOperand(predCall, cluster, 0);
+
+ if (ref == null)
+ continue;
+ }
List<RexCall> fldPreds = res.computeIfAbsent(ref.getIndex(), k -> new ArrayList<>(conjunctions.size()));
@@ -368,19 +491,36 @@ public class RexUtils {
}
/** */
- private static RexNode extractRef(RexCall call) {
+ private static RexNode extractRefFromBinary(RexCall call, RelOptCluster cluster) {
assert isBinaryComparison(call);
- RexNode leftOp = call.getOperands().get(0);
+ RexNode leftRef = extractRefFromOperand(call, cluster, 0);
RexNode rightOp = call.getOperands().get(1);
- leftOp = RexUtil.removeCast(leftOp);
- rightOp = RexUtil.removeCast(rightOp);
+ if (leftRef != null)
+ return idxOpSupports(removeCast(rightOp)) ? leftRef : null;
- if ((leftOp instanceof RexLocalRef || leftOp instanceof RexInputRef) && idxOpSupports(rightOp))
- return leftOp;
- else if ((rightOp instanceof RexLocalRef || rightOp instanceof RexInputRef) && idxOpSupports(leftOp))
- return rightOp;
+ RexNode rightRef = extractRefFromOperand(call, cluster, 1);
+ RexNode leftOp = call.getOperands().get(0);
+
+ if (rightRef != null)
+ return idxOpSupports(removeCast(leftOp)) ? rightRef : null;
+
+ return null;
+ }
+
+ /** */
+ private static RexNode extractRefFromOperand(RexCall call, RelOptCluster cluster, int operandNum) {
+ assert isSupportedTreeComparison(call);
+
+ RexNode op = call.getOperands().get(operandNum);
+
+ op = removeCast(op);
+
+ // Can proceed without ref cast only if cast was redundant in terms of values comparison.
+ if ((op instanceof RexSlot) &&
+ !TypeUtils.needCast(cluster.getTypeFactory(), op.getType(), call.getOperands().get(operandNum).getType()))
+ return op;
return null;
}
@@ -389,18 +529,24 @@ public class RexUtils {
private static boolean refOnTheRight(RexCall predCall) {
RexNode rightOp = predCall.getOperands().get(1);
- rightOp = RexUtil.removeCast(rightOp);
+ rightOp = removeCast(rightOp);
return rightOp.isA(SqlKind.LOCAL_REF) || rightOp.isA(SqlKind.INPUT_REF);
}
/** */
- public static boolean isBinaryComparison(RexNode exp) {
- return TREE_INDEX_COMPARISON.contains(exp.getKind()) &&
+ private static boolean isBinaryComparison(RexNode exp) {
+ return BINARY_COMPARISON.contains(exp.getKind()) &&
(exp instanceof RexCall) &&
((RexCall)exp).getOperands().size() == 2;
}
+ /** */
+ private static boolean isSupportedTreeComparison(RexNode exp) {
+ return TREE_INDEX_COMPARISON.contains(exp.getKind()) &&
+ (exp instanceof RexCall);
+ }
+
/** */
private static boolean idxOpSupports(RexNode op) {
return op instanceof RexLiteral
@@ -416,35 +562,6 @@ public class RexUtils {
return !(op instanceof RexLiteral) || !((RexLiteral)op).isNull();
}
- /** */
- public static List<RexNode> asBound(RelOptCluster cluster, Iterable<RexNode> idxCond, RelDataType rowType,
- @Nullable Mappings.TargetMapping mapping) {
- if (F.isEmpty(idxCond))
- return null;
-
- RexBuilder builder = builder(cluster);
- List<RelDataType> types = RelOptUtil.getFieldTypeList(rowType);
- List<RexNode> res = Arrays.asList(new RexNode[types.size()]);
-
- for (RexNode pred : idxCond) {
- assert pred instanceof RexCall;
-
- RexCall call = (RexCall)pred;
- RexSlot ref = (RexSlot)RexUtil.removeCast(call.operands.get(0));
- RexNode cond = RexUtil.removeCast(call.operands.get(1));
-
- assert idxOpSupports(cond) : cond;
-
- int index = mapping == null ? ref.getIndex() : mapping.getSourceOpt(ref.getIndex());
-
- assert index != -1;
-
- res.set(index, makeCast(builder, cond, types.get(index)));
- }
-
- return res;
- }
-
/** */
public static Mappings.TargetMapping inversePermutation(List<RexNode> nodes, RelDataType inputRowType, boolean local) {
final Mappings.TargetMapping mapping =
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 ee6f7fd5765..b3ec5455981 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
@@ -248,7 +248,7 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
"IDX",
project,
filter,
- RexUtils.buildSortedIndexConditions(cluster, idxCollation, filter, rowType, requiredColumns),
+ RexUtils.buildSortedSearchBounds(cluster, idxCollation, filter, rowType, requiredColumns),
requiredColumns,
idxCollation
);
@@ -333,7 +333,7 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
templateScan.indexName(),
projects,
filters,
- templateScan.indexConditions(),
+ templateScan.searchBounds(),
requiredColumns,
templateScan.collation()
);
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 0ffaae6fed9..60d75349544 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
@@ -82,7 +82,7 @@ public class RuntimeSortedIndexTest extends GridCommonAbstractTest {
Object[] lower = generateFindRow(rowIdLow, testIdx.getKey(), notUnique, testIdx.getValue());
Object[] upper = generateFindRow(rowIdUp, testIdx.getKey(), notUnique, testIdx.getValue());
- GridCursor<Object[]> cur = idx0.find(lower, upper, null);
+ GridCursor<Object[]> cur = idx0.find(lower, upper, true, true, null);
int rows = 0;
while (cur.next()) {
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortedIndexSpoolExecutionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortedIndexSpoolExecutionTest.java
index 866af24c7c2..f28e08bf682 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortedIndexSpoolExecutionTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortedIndexSpoolExecutionTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.query.calcite.exec.rel;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -27,6 +28,8 @@ import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeCondition;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RangeIterable;
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
import org.apache.ignite.internal.util.lang.GridTuple4;
@@ -140,8 +143,7 @@ public class SortedIndexSpoolExecutionTest extends AbstractExecutionTest {
RelCollations.of(ImmutableIntList.of(0)),
(o1, o2) -> o1[0] != null ? ((Comparable)o1[0]).compareTo(o2[0]) : 0,
testFilter,
- () -> lower,
- () -> upper
+ new StaticRangeIterable(lower, upper)
);
spool.register(Arrays.asList(scan));
@@ -188,8 +190,7 @@ public class SortedIndexSpoolExecutionTest extends AbstractExecutionTest {
collation,
ctx.expressionFactory().comparator(collation),
v -> true,
- () -> lower,
- () -> upper
+ new StaticRangeIterable(lower, upper)
);
spool.register(scan);
@@ -239,4 +240,47 @@ public class SortedIndexSpoolExecutionTest extends AbstractExecutionTest {
return delegate.test(objects);
}
}
+
+ /** */
+ private static class StaticRangeIterable implements RangeIterable<Object[]> {
+ /** */
+ private final Object[] lower;
+
+ /** */
+ private final Object[] upper;
+
+ /** */
+ private StaticRangeIterable(Object[] lower, Object[] upper) {
+ this.lower = lower;
+ this.upper = upper;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return 1;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Iterator<RangeCondition<Object[]>> iterator() {
+ RangeCondition<Object[]> range = new RangeCondition<Object[]>() {
+ @Override public Object[] lower() {
+ return lower;
+ }
+
+ @Override public Object[] upper() {
+ return upper;
+ }
+
+ @Override public boolean lowerInclude() {
+ return true;
+ }
+
+ @Override public boolean upperInclude() {
+ return true;
+ }
+ };
+
+ return Collections.singleton(range).iterator();
+ }
+ }
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractBasicIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractBasicIntegrationTest.java
index 6e8dd456fb4..3add7c03e23 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractBasicIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AbstractBasicIntegrationTest.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.processors.query.calcite.integration;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelCollation;
@@ -39,12 +38,13 @@ import org.apache.ignite.internal.processors.query.QueryEngine;
import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+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.rel.logical.IgniteLogicalIndexScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.testframework.junits.WithSystemProperty;
@@ -226,12 +226,12 @@ public class AbstractBasicIntegrationTest extends GridCommonAbstractTest {
}
/** {@inheritDoc} */
- @Override public IndexConditions toIndexCondition(
+ @Override public List<SearchBounds> toSearchBounds(
RelOptCluster cluster,
@Nullable RexNode cond,
@Nullable ImmutableBitSet requiredColumns
) {
- return delegate.toIndexCondition(cluster, cond, requiredColumns);
+ return delegate.toSearchBounds(cluster, cond, requiredColumns);
}
/** {@inheritDoc} */
@@ -239,13 +239,11 @@ public class AbstractBasicIntegrationTest extends GridCommonAbstractTest {
ExecutionContext<Row> execCtx,
ColocationGroup grp,
Predicate<Row> filters,
- Supplier<Row> lowerIdxConditions,
- Supplier<Row> upperIdxConditions,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
) {
- return delegate.scan(execCtx, grp, filters, lowerIdxConditions, upperIdxConditions, rowTransformer,
- requiredColumns);
+ return delegate.scan(execCtx, grp, filters, ranges, rowTransformer, requiredColumns);
}
/** {@inheritDoc} */
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteBasicSecondaryIndexIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteBasicSecondaryIndexIntegrationTest.java
index 9987405a5c7..6415d3bf50a 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteBasicSecondaryIndexIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CalciteBasicSecondaryIndexIntegrationTest.java
@@ -881,10 +881,8 @@ public class CalciteBasicSecondaryIndexIntegrationTest extends AbstractBasicInte
/** */
@Test
- @Ignore("https://issues.apache.org/jira/browse/IGNITE-13710")
public void testOrCondition2() {
assertQuery("SELECT * FROM Developer WHERE name='Mozart' AND (depId=1 OR depId=3)")
- .matches(containsUnion(true))
.matches(containsIndexScan("PUBLIC", "DEVELOPER", NAME_DEPID_CITY_IDX))
.returns(1, "Mozart", 3, "Vienna", 33)
.check();
@@ -892,10 +890,8 @@ public class CalciteBasicSecondaryIndexIntegrationTest extends AbstractBasicInte
/** */
@Test
- @Ignore("https://issues.apache.org/jira/browse/IGNITE-13710")
public void testOrCondition3() {
assertQuery("SELECT * FROM Developer WHERE name='Mozart' AND (age > 22 AND (depId=1 OR depId=3))")
- .matches(containsUnion(true))
.matches(containsIndexScan("PUBLIC", "DEVELOPER", NAME_DEPID_CITY_IDX))
.returns(1, "Mozart", 3, "Vienna", 33)
.check();
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexScanlIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexScanlIntegrationTest.java
index 29f6b5fc36d..53457074fd5 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexScanlIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexScanlIntegrationTest.java
@@ -20,13 +20,13 @@ package org.apache.ignite.internal.processors.query.calcite.integration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+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.schema.IgniteTable;
import org.apache.ignite.internal.util.typedef.F;
@@ -57,8 +57,7 @@ public class IndexScanlIntegrationTest extends AbstractBasicIntegrationTest {
ExecutionContext<Row> execCtx,
ColocationGroup grp,
Predicate<Row> filters,
- Supplier<Row> lowerIdxConditions,
- Supplier<Row> upperIdxConditions,
+ RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
) {
@@ -70,8 +69,7 @@ public class IndexScanlIntegrationTest extends AbstractBasicIntegrationTest {
filters = filter.and(filters);
- return delegate.scan(execCtx, grp, filters, lowerIdxConditions, upperIdxConditions, rowTransformer,
- requiredColumns);
+ return delegate.scan(execCtx, grp, filters, ranges, rowTransformer, requiredColumns);
}
});
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SearchSargOnIndexIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SearchSargOnIndexIntegrationTest.java
new file mode 100644
index 00000000000..396c42e7784
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SearchSargOnIndexIntegrationTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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 org.apache.ignite.internal.processors.query.calcite.QueryChecker;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+/**
+ * Tests of SEARCH/SARG operator.
+ */
+public class SearchSargOnIndexIntegrationTest extends AbstractBasicIntegrationTest {
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ sql("CREATE TABLE test (c1 INTEGER, c2 VARCHAR, c3 INTEGER)");
+ sql("CREATE INDEX c1c2c3 ON test(c1, c2, c3)");
+
+ sql("INSERT INTO test VALUES (0, null, 0)");
+
+ for (int i = 0; i <= 5; i++) {
+ for (int j = 1; j <= 5; j++)
+ sql("INSERT INTO test VALUES (?, ?, ?)", i == 0 ? null : i, Integer.toString(j), i * j);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() {
+ // Skip super method to keep caches after each test.
+ }
+
+ /** */
+ @Test
+ public void testIn() {
+ assertQuery("SELECT * FROM test WHERE c1 IN (2, 3) AND c2 IN ('2', '3') AND c3 IN (6, 9)")
+ .returns(2, "3", 6)
+ .returns(3, "2", 6)
+ .returns(3, "3", 9)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE (c1 = 2 OR c1 IS NULL) AND c2 IN ('2', '3') AND c3 IN (0, 6)")
+ .returns(null, "2", 0)
+ .returns(null, "3", 0)
+ .returns(2, "3", 6)
+ .check();
+ }
+
+ /** */
+ @Test
+ public void testRange() {
+ assertQuery("SELECT * FROM test WHERE ((c1 > 1 AND c1 < 3) OR (c1 > 3 AND c1 < 5)) AND c2 > '2' AND c2 < '5'")
+ .returns(2, "3", 6)
+ .returns(2, "4", 8)
+ .returns(4, "3", 12)
+ .returns(4, "4", 16)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 IN (1, 2) AND ((c2 >= '2' AND c2 < '3') OR (c2 > '4' AND c1 <= '5'))")
+ .returns(1, "2", 2)
+ .returns(1, "5", 5)
+ .returns(2, "2", 4)
+ .returns(2, "5", 10)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 IN (1, 2) AND " +
+ "(c2 < '1' OR (c2 >= '2' AND c2 < '3') OR (c2 > '2' AND c2 < '4') OR c2 > '5')")
+ .returns(1, "2", 2)
+ .returns(1, "3", 3)
+ .returns(2, "2", 4)
+ .returns(2, "3", 6)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 = 4 AND c2 > '3' AND c3 in (16, 20)")
+ .returns(4, "4", 16)
+ .returns(4, "5", 20)
+ .check();
+ }
+
+ /** */
+ @Test
+ public void testNulls() {
+ assertQuery("SELECT * FROM test WHERE c1 IS NULL AND c2 <= '1'")
+ .returns(null, "1", 0)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 IS NOT NULL AND c2 IS NULL")
+ .returns(0, null, 0)
+ .check();
+ }
+
+ /** Tests not supported SEARCH/SARG conditions. */
+ @Test
+ public void testNot() {
+ assertQuery("SELECT * FROM test WHERE c1 <> 1 AND c3 = 6")
+ .matches(QueryChecker.containsTableScan("PUBLIC", "TEST")) // Can't use index scan.
+ .returns(2, "3", 6)
+ .returns(3, "2", 6)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 NOT IN (1, 2, 5) AND c2 NOT IN (1, 2, 5)")
+ .matches(QueryChecker.containsTableScan("PUBLIC", "TEST")) // Can't use index scan.
+ .returns(3, "3", 9)
+ .returns(3, "4", 12)
+ .returns(4, "3", 12)
+ .returns(4, "4", 16)
+ .check();
+ }
+
+ /** Test correct index ordering without additional sorting. */
+ @Test
+ public void testOrdering() {
+ assertQuery("SELECT * FROM test WHERE c1 IN (3, 2) AND c2 IN ('3', '2') ORDER BY c1, c2, c3")
+ .matches(CoreMatchers.not(QueryChecker.containsSubPlan("IgniteSort"))) // Don't require additional sorting.
+ .ordered()
+ .returns(2, "2", 4)
+ .returns(2, "3", 6)
+ .returns(3, "2", 6)
+ .returns(3, "3", 9)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 IN (2, 3) AND c2 < '3' ORDER BY c1, c2, c3")
+ .matches(CoreMatchers.not(QueryChecker.containsSubPlan("IgniteSort"))) // Don't require additional sorting.
+ .ordered()
+ .returns(2, "1", 2)
+ .returns(2, "2", 4)
+ .returns(3, "1", 3)
+ .returns(3, "2", 6)
+ .check();
+
+ assertQuery("SELECT * FROM test WHERE c1 IN (2, 3) AND c2 IN ('2', '3') AND c3 BETWEEN 5 AND 7 " +
+ "ORDER BY c1, c2, c3")
+ .matches(CoreMatchers.not(QueryChecker.containsSubPlan("IgniteSort"))) // Don't require additional sorting.
+ .ordered()
+ .returns(2, "3", 6)
+ .returns(3, "2", 6)
+ .check();
+ }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
index 9a62151ed58..a1ecdecb5c5 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
@@ -125,7 +125,7 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
protected volatile Throwable lastE;
/** Last error message. */
- private String lastErrorMsg;
+ protected String lastErrorMsg;
/** */
@Before
@@ -399,7 +399,7 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
}
/** */
- protected static void createTable(IgniteSchema schema, String name, RelDataType type, IgniteDistribution distr,
+ protected static TestTable createTable(IgniteSchema schema, String name, RelDataType type, IgniteDistribution distr,
List<List<UUID>> assignment) {
TestTable table = new TestTable(type) {
@Override public ColocationGroup colocationGroup(MappingQueryContext ctx) {
@@ -419,6 +419,8 @@ public abstract class AbstractPlannerTest extends GridCommonAbstractTest {
};
schema.addTable(name, table);
+
+ return table;
}
/** */
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
index 812ec10c297..06051ccb0ff 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
@@ -21,7 +21,8 @@ import java.util.List;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexFieldAccess;
-import org.apache.calcite.rex.RexNode;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
@@ -93,23 +94,15 @@ public class CorrelatedNestedLoopJoinPlannerTest extends AbstractPlannerTest {
IgniteIndexScan idxScan = findFirstNode(phys, byClass(IgniteIndexScan.class));
- List<RexNode> lBound = idxScan.lowerBound();
+ List<SearchBounds> searchBounds = idxScan.searchBounds();
- assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), lBound);
- assertEquals(3, lBound.size());
+ assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), searchBounds);
+ assertEquals(3, searchBounds.size());
- assertNull(lBound.get(0));
- assertTrue(lBound.get(1) instanceof RexFieldAccess);
- assertNull(lBound.get(2));
-
- List<RexNode> uBound = idxScan.upperBound();
-
- assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), uBound);
- assertEquals(3, uBound.size());
-
- assertNull(uBound.get(0));
- assertTrue(uBound.get(1) instanceof RexFieldAccess);
- assertNull(uBound.get(2));
+ assertNull(searchBounds.get(0));
+ assertTrue(searchBounds.get(1) instanceof ExactBounds);
+ assertTrue(((ExactBounds)searchBounds.get(1)).bound() instanceof RexFieldAccess);
+ assertNull(searchBounds.get(2));
}
/**
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
index 225bbfc0232..7eb17b7eaef 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
@@ -17,10 +17,15 @@
package org.apache.ignite.internal.processors.query.calcite.planner;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
@@ -95,8 +100,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
.and(scan -> scan.condition() != null)
.and(scan -> "=($t2, 0)".equals(scan.condition().toString()))
.and(scan -> ImmutableBitSet.of(0, 1, 2).equals(scan.requiredColumns()))
- .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
- .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ .and(scan -> "[=($t2, 0)]".equals(searchBoundsCondition(scan.searchBounds()).toString()))
);
// Index condition shifted according to requiredColumns.
@@ -106,8 +110,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
.and(scan -> scan.condition() != null)
.and(scan -> "=($t1, 0)".equals(scan.condition().toString()))
.and(scan -> ImmutableBitSet.of(1, 2).equals(scan.requiredColumns()))
- .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
- .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ .and(scan -> "[=($t1, 0)]".equals(searchBoundsCondition(scan.searchBounds()).toString()))
);
}
@@ -124,8 +127,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
.and(scan -> scan.condition() != null)
.and(scan -> "=($t2, 0)".equals(scan.condition().toString()))
.and(scan -> ImmutableBitSet.of(0, 1, 2).equals(scan.requiredColumns()))
- .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
- .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ .and(scan -> "[=($t2, 0)]".equals(searchBoundsCondition(scan.searchBounds()).toString()))
);
// Index condition shift and identity.
@@ -134,8 +136,7 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
.and(scan -> scan.condition() != null)
.and(scan -> "=($t1, 0)".equals(scan.condition().toString()))
.and(scan -> ImmutableBitSet.of(1, 2).equals(scan.requiredColumns()))
- .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
- .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ .and(scan -> "[=($t1, 0)]".equals(searchBoundsCondition(scan.searchBounds()).toString()))
);
}
@@ -244,4 +245,9 @@ public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
.and(scan -> ImmutableBitSet.of(0, 2).equals(scan.requiredColumns())),
"ProjectFilterTransposeRule", "FilterProjectTransposeRule");
}
+
+ /** */
+ private static List<RexNode> searchBoundsCondition(List<SearchBounds> searchBounds) {
+ return searchBounds.stream().filter(Objects::nonNull).map(SearchBounds::condition).collect(Collectors.toList());
+ }
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SearchSargOnIndexPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SearchSargOnIndexPlannerTest.java
new file mode 100644
index 00000000000..a77a0fcd582
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SearchSargOnIndexPlannerTest.java
@@ -0,0 +1,436 @@
+/*
+ * 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.planner;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.IntStream;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.MultiBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.RangeBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteUnionAll;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
+import org.junit.Test;
+
+/**
+ * Planner test for SEARCH/SARG condition on indexed fields.
+ */
+public class SearchSargOnIndexPlannerTest extends AbstractPlannerTest {
+ /** */
+ private IgniteSchema publicSchema;
+
+ /** */
+ private TestTable tbl;
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+ RelDataType type = new RelDataTypeFactory.Builder(f)
+ .add("C1", f.createTypeWithNullability(f.createSqlType(SqlTypeName.INTEGER), true))
+ .add("C2", f.createTypeWithNullability(f.createSqlType(SqlTypeName.VARCHAR), true))
+ .add("C3", f.createTypeWithNullability(f.createSqlType(SqlTypeName.INTEGER), true))
+ .add("C4", f.createTypeWithNullability(f.createSqlType(SqlTypeName.INTEGER), true))
+ .build();
+
+ publicSchema = new IgniteSchema("PUBLIC");
+
+ tbl = createTable(publicSchema, "TEST", type, IgniteDistributions.single(), null);
+
+ tbl.addIndex("C1C2C3", 0, 1, 2);
+ }
+
+ /** Simple case on one field, without multi tuple SEARCH/SARG. */
+ @Test
+ public void testBoundsOneFieldSingleTuple() throws Exception {
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1", exact(1));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C3 = 1", exact(1), empty(), empty(), empty());
+
+ assertBounds("SELECT * FROM TEST WHERE C1 > 1 AND C1 <= 3",
+ range(1, 3, false, true));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 < 3 AND C1 IS NOT NULL",
+ range("null", 3, false, false));
+
+ // Redundant "IS NOT NULL condition".
+ assertBounds("SELECT * FROM TEST WHERE C1 > 3 AND C1 IS NOT NULL",
+ range(3, null, false, true));
+
+ // C4 field not in collation.
+ assertBounds("SELECT * FROM TEST WHERE C1 > 1 AND C1 <= 3 AND C4 = 1",
+ range(1, 3, false, true),
+ empty(),
+ empty(),
+ empty()
+ );
+
+ // Cannot proceed to C3 without C2.
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C3 = 1",
+ exact(1),
+ empty(),
+ empty(),
+ empty()
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 > 1 AND C1 <= 3 AND C3 = 1",
+ range(1, 3, false, true),
+ empty(),
+ empty(),
+ empty()
+ );
+ }
+
+ /** Simple SEARCH/SARG. */
+ @Test
+ public void testBoundsOneFieldSearch() throws Exception {
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3)",
+ multi(exact(1), exact(2), exact(3)));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3) AND C1 IS NOT NULL",
+ multi(exact(1), exact(2), exact(3)));
+
+ assertBounds("SELECT * FROM TEST WHERE (C1 > 1 AND C1 < 3) OR C1 IN (4, 5) OR C1 = 6 OR C1 > 7",
+ multi(
+ range(1, 3, false, false),
+ exact(4),
+ exact(5),
+ exact(6),
+ range(7, null, false, true)));
+ }
+
+ /** Simple SEARCH/SARG, values deduplication. */
+ @Test
+ public void testBoundsOneFieldSearchDeduplication() throws Exception {
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3, 2, 1)",
+ multi(exact(1), exact(2), exact(3)));
+ }
+
+ /** Simple SEARCH/SARG, range optimization. */
+ @Test
+ public void testBoundsOneFieldSearchRangeOptimization() throws Exception {
+ assertBounds("SELECT * FROM TEST WHERE (C1 > 1 AND C1 < 4) OR (C1 > 3 AND C1 < 5) OR (C1 > 7) OR (C1 > 6)",
+ multi(
+ range(1, 5, false, false),
+ range(6, null, false, true)));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 > 1 AND C1 < 3 AND C1 <> 2",
+ multi(
+ range(1, 2, false, false),
+ range(2, 3, false, false)));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND " +
+ "((C2 > '1' AND C2 < '3') OR (C2 > '11' AND C2 < '33') OR C2 > '4')",
+ exact(1),
+ multi(
+ range("1", "33", false, false),
+ range("4", null, false, true)));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND (C2 > '1' OR C2 < '3')",
+ exact(1),
+ range("null", null, false, true));
+ }
+
+ /** Simple SEARCH/SARG with "IS NULL" condition. */
+ @Test
+ public void testBoundsOneFieldSearchWithNull() throws Exception {
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3) OR C1 IS NULL",
+ multi(exact("null"), exact(1), exact(2), exact(3)),
+ empty(),
+ empty(),
+ empty()
+ );
+ }
+
+ /** Tests bounds with DESC ordering. */
+ @Test
+ public void testBoundsDescOrdering() throws Exception {
+ tbl.addIndex(RelCollations.of(TraitUtils.createFieldCollation(3, false),
+ TraitUtils.createFieldCollation(2, true)), "C4");
+
+ assertBounds("SELECT * FROM TEST WHERE C4 > 1",
+ empty(),
+ empty(),
+ empty(),
+ range(null, 1, true, false));
+
+ assertBounds("SELECT * FROM TEST WHERE C4 < 1",
+ empty(),
+ empty(),
+ empty(),
+ range(1, "null", false, false));
+
+ assertBounds("SELECT * FROM TEST WHERE C4 IS NULL", empty(), empty(), empty(), exact("null"));
+
+ assertBounds("SELECT * FROM TEST WHERE C4 IS NOT NULL",
+ empty(),
+ empty(),
+ empty(),
+ range(null, "null", true, false));
+
+ assertBounds("SELECT * FROM TEST WHERE C4 IN (1, 2, 3) AND C3 > 1",
+ empty(),
+ empty(),
+ range(1, null, false, true),
+ multi(exact(1), exact(2), exact(3))
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE ((C4 > 1 AND C4 < 5) OR (C4 > 7 AND C4 < 9)) AND C3 = 1",
+ empty(),
+ empty(),
+ empty(),
+ multi(
+ range(5, 1, false, false),
+ range(9, 7, false, false))
+ );
+ }
+
+ /** Tests bounds with conditions on several fields. */
+ @Test
+ public void testBoundsSeveralFieldsSearch() throws Exception {
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C2 IN ('a', 'b')",
+ exact(1),
+ multi(exact("a"), exact("b"))
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C2 > 'a'",
+ exact(1),
+ range("a", null, false, true)
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3) AND C2 = 'a'",
+ multi(exact(1), exact(2), exact(3)),
+ exact("a")
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3) AND C2 IN ('a', 'b')",
+ multi(exact(1), exact(2), exact(3)),
+ multi(exact("a"), exact("b"))
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3) AND C2 IN ('a', 'b') AND C3 IN (4, 5) AND C4 = 1",
+ multi(exact(1), exact(2), exact(3)),
+ multi(exact("a"), exact("b")),
+ multi(exact(4), exact(5)),
+ empty()
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3) AND C2 IN ('a', 'b') AND C3 > 4",
+ multi(exact(1), exact(2), exact(3)),
+ multi(exact("a"), exact("b")),
+ range(4, null, false, true)
+ );
+
+ // Cannot proceed to the next field after the range condition.
+ assertBounds("SELECT * FROM TEST WHERE C1 > 1 AND C2 = 'a'",
+ range(1, null, false, true),
+ empty()
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 > 1 AND C2 > 'a'",
+ range(1, null, false, true),
+ empty()
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 >= 1 AND C2 = 'a'",
+ range(1, null, true, true),
+ empty() // TODO https://issues.apache.org/jira/browse/IGNITE-13568 Fix to exact("a")
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 >= 1 AND C2 > 'a'",
+ range(1, null, true, true),
+ empty() // TODO https://issues.apache.org/jira/browse/IGNITE-13568 Fix to range("a", null, false, true)
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 >= 1 AND C2 < 'a'",
+ range(1, null, true, true),
+ empty()
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 >= 1 AND C2 IN ('a', 'b')",
+ range(1, null, true, true),
+ empty()
+ );
+
+ // Cannot proceed to the next field after SEARCH/SARG with range condition.
+ assertBounds("SELECT * FROM TEST WHERE ((C1 > 1 AND C1 < 3) OR C1 > 5) AND C2 = 'a'",
+ multi(
+ range(1, 3, false, false),
+ range(5, null, false, true)),
+ empty()
+ );
+ }
+
+ /** Tests max complexity of SEARCH/SARG to include into index scan. */
+ @Test
+ public void testBoundsMaxComplexity() throws Exception {
+ int limit = RexUtils.MAX_SEARCH_BOUNDS_COMPLEXITY;
+
+ String inVals = String.join(", ", IntStream.range(0, limit + 1).mapToObj(Integer::toString)
+ .toArray(String[]::new));
+
+ assertPlan("SELECT * FROM TEST WHERE C1 IN (" + inVals + ")", publicSchema, isTableScan("TEST"));
+
+ inVals = String.join(", ", IntStream.range(0, limit / 10 + 1).mapToObj(Integer::toString)
+ .toArray(String[]::new));
+
+ assertBounds("SELECT * FROM TEST WHERE C1 IN (1, 2, 3, 4, 5) AND C2 IN ('a', 'b') AND C3 IN (" + inVals + ")",
+ multi(exact(1), exact(2), exact(3), exact(4), exact(5)),
+ multi(exact("a"), exact("b")),
+ empty()
+ );
+ }
+
+ /** Tests bounds with wrong literal types. */
+ @Test
+ public void testBoundsTypeConversion() throws Exception {
+ // Implicit cast of all filter values to INTEGER.
+ assertBounds("SELECT * FROM TEST WHERE C1 IN ('1', '2', '3')",
+ multi(exact(1), exact(2), exact(3))
+ );
+
+ // Implicit cast of '1' to INTEGER.
+ assertBounds("SELECT * FROM TEST WHERE C1 IN ('1', 2, 3)",
+ multi(exact(1), exact(2), exact(3))
+ );
+
+ // Casted to INTEGER type C2 column cannot be used as index bound.
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C2 > 1",
+ exact(1),
+ empty()
+ );
+
+ // Casted to INTEGER type C2 column cannot be used as index bound.
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C2 IN (2, 3)",
+ exact(1),
+ empty()
+ );
+
+ // Implicit cast of 2 to VARCHAR.
+ assertBounds("SELECT * FROM TEST WHERE C1 = 1 AND C2 IN (2, '3')",
+ exact(1),
+ multi(exact("2"), exact("3"))
+ );
+ }
+
+ /** Tests bounds with dynamic parameters. */
+ @Test
+ public void testBoundsDynamicParams() throws Exception {
+ // Cannot optimize dynamic parameters to SEARCH/SARG, query is splitted by or-to-union rule.
+ assertPlan("SELECT * FROM TEST WHERE C1 IN (?, ?)", publicSchema, isInstanceOf(IgniteUnionAll.class)
+ .and(input(0, isIndexScan("TEST", "C1C2C3")))
+ .and(input(1, isIndexScan("TEST", "C1C2C3")))
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 = ? AND C2 IN ('a', 'b')",
+ exact("?0"),
+ multi(exact("a"), exact("b"))
+ );
+
+ assertBounds("SELECT * FROM TEST WHERE C1 = ? AND C2 > ? AND C2 < ?",
+ exact("?0"),
+ range("?1", "?2", false, false)
+ );
+ }
+
+ /** Tests bounds with correlated value. */
+ @Test
+ public void testBoundsWithCorrelate() throws Exception {
+ assertBounds("SELECT (SELECT C1 FROM TEST t2 WHERE t2.C1 = t1.C1) FROM TEST t1",
+ exact("$cor0.C1")
+ );
+
+ assertBounds(
+ "SELECT (SELECT C1 FROM TEST t2 WHERE C1 = 1 AND C2 = 'a' AND C3 IN (t1.C3, 0, 1, 2)) FROM TEST t1",
+ exact(1),
+ exact("a"),
+ empty()
+ );
+ }
+
+ /** */
+ private void assertBounds(String sql, Predicate<SearchBounds>... predicates) throws Exception {
+ assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(IgniteIndexScan.class)
+ .and(scan -> matchBounds(scan.searchBounds(), predicates))));
+ }
+
+ /** */
+ private boolean matchBounds(List<SearchBounds> searchBounds, Predicate<SearchBounds>... predicates) {
+ for (int i = 0; i < predicates.length; i++) {
+ if (!predicates[i].test(searchBounds.get(i))) {
+ lastErrorMsg = "Not expected bounds: " + searchBounds.get(i);
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /** */
+ private static Predicate<SearchBounds> empty() {
+ return Objects::isNull;
+ }
+
+ /** */
+ private static Predicate<SearchBounds> exact(Object val) {
+ return b -> b instanceof ExactBounds && matchValue(val, ((ExactBounds)b).bound());
+ }
+
+ /** */
+ private static Predicate<SearchBounds> range(Object lower, Object upper, boolean lowerInclude, boolean upperInclude) {
+ return b -> b instanceof RangeBounds &&
+ matchValue(lower, ((RangeBounds)b).lowerBound()) &&
+ matchValue(upper, ((RangeBounds)b).upperBound()) &&
+ lowerInclude == ((RangeBounds)b).lowerInclude() &&
+ upperInclude == ((RangeBounds)b).upperInclude();
+ }
+
+ /** */
+ private Predicate<SearchBounds> multi(Predicate<SearchBounds>... predicates) {
+ return b -> b instanceof MultiBounds &&
+ ((MultiBounds)b).bounds().size() == predicates.length &&
+ matchBounds(((MultiBounds)b).bounds(), predicates);
+ }
+
+ /** */
+ private static boolean matchValue(Object val, RexNode bound) {
+ if (val == null || bound == null)
+ return val == bound;
+
+ bound = RexUtil.removeCast(bound);
+
+ return Objects.toString(val).equals(Objects.toString(
+ bound instanceof RexLiteral ? ((RexLiteral)bound).getValueAs(val.getClass()) : bound));
+ }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java
index 096adfedf96..54b8f4893ea 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/SortedIndexSpoolPlannerTest.java
@@ -23,7 +23,10 @@ import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexFieldAccess;
-import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.RangeBounds;
+import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSortedIndexSpool;
@@ -95,23 +98,15 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
IgniteSortedIndexSpool idxSpool = findFirstNode(phys, byClass(IgniteSortedIndexSpool.class));
- List<RexNode> lBound = idxSpool.indexCondition().lowerBound();
+ List<SearchBounds> searchBounds = idxSpool.searchBounds();
- assertNotNull(lBound);
- assertEquals(3, lBound.size());
+ assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), searchBounds);
+ assertEquals(3, searchBounds.size());
- assertNull(lBound.get(0));
- assertTrue(lBound.get(1) instanceof RexFieldAccess);
- assertNull(lBound.get(2));
-
- List<RexNode> uBound = idxSpool.indexCondition().upperBound();
-
- assertNotNull(uBound);
- assertEquals(3, uBound.size());
-
- assertNull(uBound.get(0));
- assertTrue(uBound.get(1) instanceof RexFieldAccess);
- assertNull(uBound.get(2));
+ assertNull(searchBounds.get(0));
+ assertTrue(searchBounds.get(1) instanceof ExactBounds);
+ assertTrue(((ExactBounds)searchBounds.get(1)).bound() instanceof RexFieldAccess);
+ assertNull(searchBounds.get(2));
}
/**
@@ -172,25 +167,16 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
IgniteSortedIndexSpool idxSpool = findFirstNode(phys, byClass(IgniteSortedIndexSpool.class));
- List<RexNode> lBound = idxSpool.indexCondition().lowerBound();
-
- assertNotNull(lBound);
- assertEquals(4, lBound.size());
-
- assertNull(lBound.get(0));
- assertTrue(lBound.get(1) instanceof RexFieldAccess);
- assertNull(lBound.get(2));
- assertNull(lBound.get(3));
+ List<SearchBounds> searchBounds = idxSpool.searchBounds();
- List<RexNode> uBound = idxSpool.indexCondition().upperBound();
+ assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), searchBounds);
+ assertEquals(4, searchBounds.size());
- assertNotNull(uBound);
- assertEquals(4, uBound.size());
-
- assertNull(uBound.get(0));
- assertTrue(uBound.get(1) instanceof RexFieldAccess);
- assertNull(uBound.get(2));
- assertNull(uBound.get(3));
+ assertNull(searchBounds.get(0));
+ assertTrue(searchBounds.get(1) instanceof ExactBounds);
+ assertTrue(((ExactBounds)searchBounds.get(1)).bound() instanceof RexFieldAccess);
+ assertNull(searchBounds.get(2));
+ assertNull(searchBounds.get(3));
}
/**
@@ -215,20 +201,22 @@ public class SortedIndexSpoolPlannerTest extends AbstractPlannerTest {
isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
.and(input(1, isInstanceOf(IgniteSortedIndexSpool.class)
.and(spool -> {
- List<RexNode> lBound = spool.indexCondition().lowerBound();
+ List<SearchBounds> searchBounds = spool.searchBounds();
// Condition is LESS_THEN, but we have DESC field and condition should be in lower bound
// instead of upper bound.
- assertNotNull(lBound);
- assertEquals(3, lBound.size());
-
- assertNull(lBound.get(0));
- assertTrue(lBound.get(1) instanceof RexFieldAccess);
- assertNull(lBound.get(2));
-
- List<RexNode> uBound = spool.indexCondition().upperBound();
-
- assertNull(uBound);
+ assertNotNull(searchBounds);
+ assertEquals(3, searchBounds.size());
+
+ assertNull(searchBounds.get(0));
+ assertTrue(searchBounds.get(1) instanceof RangeBounds);
+ RangeBounds fld1Bounds = (RangeBounds)searchBounds.get(1);
+ assertTrue(fld1Bounds.lowerBound() instanceof RexFieldAccess);
+ assertFalse(fld1Bounds.lowerInclude());
+ // NULLS LAST in collation, so nulls can be skipped by upper bound.
+ assertTrue(((RexLiteral)fld1Bounds.upperBound()).isNull());
+ assertFalse(fld1Bounds.upperInclude());
+ assertNull(searchBounds.get(2));
return true;
})
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java
index 5c5de98f8bf..59409e1be0e 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatisticsPlannerTest.java
@@ -212,7 +212,7 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
}
/**
- * Check index choosing with is null condition. Due to AbstractIndexScan logic - no index should be choosen.
+ * Check index choosing with is null condition.
*/
@Test
public void testIsNull() throws Exception {
@@ -231,14 +231,15 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
String idxName = getIdxName(1, field.getName().toUpperCase());
- checkIdxNotUsed(isNullSql, idxName);
- checkIdxNotUsed(isNullPKSql, idxName);
+ assertPlan(isNullSql, publicSchema, hasChildThat(isIndexScan("TBL1", idxName)));
+ assertPlan(isNullPKSql, publicSchema, hasChildThat(isIndexScan("TBL1", idxName)
+ .or(isIndexScan("TBL1", "PK"))));
}
}
}
/**
- * Check index choosing with not null condition. Due to AbstractIndexScan logic - no index should be choosen.
+ * Check index choosing with not null condition.
*/
@Test
public void testNotNull() throws Exception {
@@ -257,8 +258,9 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
String idxName = getIdxName(1, field.getName().toUpperCase());
- checkIdxNotUsed(isNullSql, idxName);
- checkIdxNotUsed(isNullPKSql, idxName);
+ assertPlan(isNullSql, publicSchema, hasChildThat(isIndexScan("TBL1", idxName)));
+ assertPlan(isNullPKSql, publicSchema, hasChildThat(isIndexScan("TBL1", idxName)
+ .or(isIndexScan("TBL1", "PK"))));
}
}
}
@@ -327,20 +329,6 @@ public class StatisticsPlannerTest extends AbstractPlannerTest {
assertEquals(idxName, idxScan.indexName());
}
- /**
- * Check index is not used.
- *
- * @param sql Query.
- * @param idxName Not expected index name.
- * @throws Exception In case of errors.
- */
- private void checkIdxNotUsed(String sql, String idxName) throws Exception {
- IgniteRel phys = physicalPlan(sql, publicSchema);
- IgniteIndexScan idxScan = findFirstNode(phys, byClass(IgniteIndexScan.class));
-
- assertTrue(idxScan == null || !idxName.equals(idxScan.indexName()));
- }
-
/**
* Get index name by column.
*
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
index 3ea94ac21e5..926d288a384 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
@@ -129,8 +129,6 @@ public class OrToUnionRuleTest extends GridCommonAbstractTest {
/**
* Check 'OR -> UNION' rule is applied for equality conditions on indexed columns.
- *
- * @throws Exception If failed.
*/
@Test
public void testEqualityOrToUnionAllRewrite() {
@@ -150,9 +148,7 @@ public class OrToUnionRuleTest extends GridCommonAbstractTest {
}
/**
- * Check 'OR -> UNION' rule is applied for equality conditions on indexed columns.
- *
- * @throws Exception If failed.
+ * Check 'OR -> UNION' rule is NOT applied for conditions on the same indexed column.
*/
@Test
public void testNonDistinctOrToUnionAllRewrite() {
@@ -160,8 +156,7 @@ public class OrToUnionRuleTest extends GridCommonAbstractTest {
"FROM products " +
"WHERE subcategory = 'Camera Lens' " +
"OR subcategory = 'Other'")
- .matches(containsUnion(true))
- .matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_SUBCATEGORY"))
+ .matches(CoreMatchers.not(containsUnion(true)))
.matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_SUBCATEGORY"))
.returns(3, "Photo", 1, "Camera Lens", 12, "Lens 1")
.returns(4, "Photo", 1, "Other", 12, "Charger 1")
@@ -172,8 +167,6 @@ public class OrToUnionRuleTest extends GridCommonAbstractTest {
/**
* Check 'OR -> UNION' rule is applied for mixed conditions on indexed columns.
- *
- * @throws Exception If failed.
*/
@Test
public void testMixedOrToUnionAllRewrite() {
@@ -254,8 +247,6 @@ public class OrToUnionRuleTest extends GridCommonAbstractTest {
/**
* Check 'OR -> UNION' rule is not applied if all columns are not indexed.
- *
- * @throws Exception If failed.
*/
@Test
public void testAllNonIndexedOrToUnionAllRewrite() {
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 2c14e4a2d5f..712589eb52a 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
@@ -41,6 +41,7 @@ import org.apache.ignite.internal.processors.query.calcite.integration.KillQuery
import org.apache.ignite.internal.processors.query.calcite.integration.MetadataIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.QueryEngineConfigurationIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.RunningQueriesIntegrationTest;
+import org.apache.ignite.internal.processors.query.calcite.integration.SearchSargOnIndexIntegrationTest;
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;
@@ -103,6 +104,7 @@ import org.junit.runners.Suite;
SystemViewsIntegrationTest.class,
IndexRebuildIntegrationTest.class,
QueryEngineConfigurationIntegrationTest.class,
+ SearchSargOnIndexIntegrationTest.class,
})
public class IntegrationTestSuite {
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
index d4f75e09668..e967eb26b64 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
@@ -32,6 +32,7 @@ import org.apache.ignite.internal.processors.query.calcite.planner.MergeJoinPlan
import org.apache.ignite.internal.processors.query.calcite.planner.PlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.PlannerTimeoutTest;
import org.apache.ignite.internal.processors.query.calcite.planner.ProjectFilterScanMergePlannerTest;
+import org.apache.ignite.internal.processors.query.calcite.planner.SearchSargOnIndexPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.SetOpPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.SortAggregatePlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.SortedIndexSpoolPlannerTest;
@@ -71,6 +72,7 @@ import org.junit.runners.Suite;
ProjectFilterScanMergePlannerTest.class,
IndexRebuildPlannerTest.class,
PlannerTimeoutTest.class,
+ SearchSargOnIndexPlannerTest.class,
})
public class PlannerTestSuite {
}