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/11/11 07:13:47 UTC
[ignite] branch master updated: IGNITE-17250 SQL Calcite: Make 'min()/max()' use first/last index value - Fixes #10153.
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 ad0c63cd1f2 IGNITE-17250 SQL Calcite: Make 'min()/max()' use first/last index value - Fixes #10153.
ad0c63cd1f2 is described below
commit ad0c63cd1f2bb8219a34d7a3ac2b6607fb019e69
Author: vladsz83 <vl...@gmail.com>
AuthorDate: Fri Nov 11 10:08:36 2022 +0300
IGNITE-17250 SQL Calcite: Make 'min()/max()' use first/last index value - Fixes #10153.
Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
.../query/calcite/exec/IndexFirstLastScan.java | 135 ++++++++++++++
.../processors/query/calcite/exec/IndexScan.java | 37 +++-
.../query/calcite/exec/LogicalRelImplementor.java | 49 ++++++
.../query/calcite/exec/exp/agg/Accumulators.java | 2 +-
.../calcite/metadata/IgniteMdFragmentMapping.java | 9 +
.../processors/query/calcite/prepare/Cloner.java | 6 +
.../query/calcite/prepare/IgniteRelShuttle.java | 6 +
.../query/calcite/prepare/PlannerPhase.java | 2 +
.../query/calcite/rel/IgniteIndexBound.java | 194 +++++++++++++++++++++
.../query/calcite/rel/IgniteRelVisitor.java | 6 +
.../query/calcite/rule/IndexMinMaxRule.java | 95 ++++++++++
.../query/calcite/schema/CacheIndexImpl.java | 26 +++
.../query/calcite/schema/IgniteIndex.java | 16 ++
.../query/calcite/schema/SystemViewIndexImpl.java | 11 ++
.../calcite/exec/LogicalRelImplementorTest.java | 50 ++++++
.../integration/AbstractBasicIntegrationTest.java | 10 ++
.../integration/AggregatesIntegrationTest.java | 114 +++++++++++-
.../integration/IndexRebuildIntegrationTest.java | 60 +++++++
.../calcite/planner/HashAggregatePlannerTest.java | 89 +++++++++-
.../calcite/planner/IndexRebuildPlannerTest.java | 76 +++++++-
.../java/org/apache/ignite/cache/QueryEntity.java | 2 +-
.../query/index/sorted/SortedSegmentedIndex.java | 8 +
.../index/sorted/client/ClientInlineIndex.java | 5 +
.../query/index/sorted/inline/InlineIndexImpl.java | 76 +++++++-
.../sorted/inline/InlineTreeFilterClosure.java | 2 +-
25 files changed, 1055 insertions(+), 31 deletions(-)
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexFirstLastScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexFirstLastScan.java
new file mode 100644
index 00000000000..aacada64133
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexFirstLastScan.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.internal.processors.query.calcite.exec;
+
+import java.util.List;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.cache.query.index.sorted.IndexKeyType;
+import org.apache.ignite.internal.cache.query.index.sorted.IndexRow;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexImpl;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexKeyType;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.io.InlineIO;
+import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKey;
+import org.apache.ignite.internal.cache.query.index.sorted.keys.NullIndexKey;
+import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
+import org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
+import org.apache.ignite.internal.util.lang.GridCursor;
+import org.apache.ignite.internal.util.typedef.F;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Takes only first or last index value excluding nulls.
+ */
+public class IndexFirstLastScan<Row> extends IndexScan<Row> {
+ /**
+ * @param first {@code True} to take first index value. {@code False} to take last value.
+ * @param ectx Execution context.
+ * @param desc Table descriptor.
+ * @param idx Physical index.
+ * @param idxFieldMapping Mapping from index keys to row fields.
+ * @param parts Mapping from index keys to row fields.
+ * @param requiredColumns Required columns.
+ */
+ public IndexFirstLastScan(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ CacheTableDescriptor desc,
+ InlineIndexImpl idx,
+ ImmutableIntList idxFieldMapping,
+ int[] parts,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ super(ectx, desc, new FirstLastIndexWrapper(idx, first), idxFieldMapping, parts, null, null, null,
+ requiredColumns);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IndexQueryContext indexQueryContext() {
+ IndexQueryContext res = super.indexQueryContext();
+
+ BPlusTree.TreeRowClosure<IndexRow, IndexRow> f = res.rowFilter();
+
+ List<InlineIndexKeyType> inlineKeyTypes = idx.segment(0).rowHandler().inlineIndexKeyTypes();
+
+ InlineIndexKeyType keyType = F.isEmpty(inlineKeyTypes) ? null : inlineKeyTypes.get(0);
+
+ return new IndexQueryContext(
+ res.cacheFilter(),
+ new BPlusTree.TreeRowClosure<IndexRow, IndexRow>() {
+ /** {@inheritDoc} */
+ @Override public boolean apply(
+ BPlusTree<IndexRow, IndexRow> tree,
+ BPlusIO<IndexRow> io,
+ long pageAddr,
+ int idx
+ ) throws IgniteCheckedException {
+ if (f != null && !f.apply(tree, io, pageAddr, idx))
+ return false;
+
+ if (keyType != null && io instanceof InlineIO) {
+ IndexKey key = keyType.get(pageAddr, io.offset(idx), ((InlineIO)io).inlineSize());
+
+ if (key != null)
+ return key != NullIndexKey.INSTANCE;
+ }
+
+ return io.getLookupRow(tree, pageAddr, idx).key(0).type() != IndexKeyType.NULL;
+ }
+ },
+ res.mvccSnapshot()
+ );
+ }
+
+ /** */
+ private static class FirstLastIndexWrapper extends IndexScan.TreeIndexWrapper {
+ /** */
+ private final boolean first;
+
+ /**
+ * @param idx Index
+ * @param first {@code True} to take first index value. {@code False} to take last value.
+ */
+ protected FirstLastIndexWrapper(InlineIndexImpl idx, boolean first) {
+ super(idx);
+ this.first = first;
+ }
+
+ /** {@inheritDoc} */
+ @Override public GridCursor<IndexRow> find(
+ IndexRow lower,
+ IndexRow upper,
+ boolean lowerInclude,
+ boolean upperInclude,
+ IndexQueryContext qctx
+ ) {
+ assert lower == null && upper == null;
+ assert lowerInclude && upperInclude;
+
+ try {
+ return idx.findFirstOrLast(qctx, first);
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteException("Failed to take " + (first ? "first" : "last") + " not-null index value.", e);
+ }
+ }
+ }
+}
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 a21308e2a44..7a6ca9ea761 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
@@ -86,7 +86,7 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
private final ImmutableBitSet requiredColumns;
/** */
- private final InlineIndex idx;
+ protected final InlineIndex idx;
/** Mapping from index keys to row fields. */
private final ImmutableIntList idxFieldMapping;
@@ -98,7 +98,7 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
* @param ectx Execution context.
* @param desc Table descriptor.
* @param idxFieldMapping Mapping from index keys to row fields.
- * @param idx Phisycal index.
+ * @param idx Physical index.
* @param filters Additional filters.
* @param ranges Index scan bounds.
*/
@@ -112,18 +112,41 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
RangeIterable<Row> ranges,
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
+ ) {
+ this(ectx, desc, new TreeIndexWrapper(idx), idxFieldMapping, parts, filters, ranges, rowTransformer,
+ requiredColumns);
+ }
+
+ /**
+ * @param ectx Execution context.
+ * @param desc Table descriptor.
+ * @param idxFieldMapping Mapping from index keys to row fields.
+ * @param treeIdx Physical index wrapper.
+ * @param filters Additional filters.
+ * @param ranges Index scan bounds.
+ */
+ protected IndexScan(
+ ExecutionContext<Row> ectx,
+ CacheTableDescriptor desc,
+ TreeIndexWrapper treeIdx,
+ ImmutableIntList idxFieldMapping,
+ int[] parts,
+ Predicate<Row> filters,
+ RangeIterable<Row> ranges,
+ Function<Row, Row> rowTransformer,
+ @Nullable ImmutableBitSet requiredColumns
) {
super(
ectx,
desc.rowType(ectx.getTypeFactory(), requiredColumns),
- new TreeIndexWrapper(idx),
+ treeIdx,
filters,
ranges,
rowTransformer
);
this.desc = desc;
- this.idx = idx;
+ this.idx = treeIdx.idx;
cctx = desc.cacheContext();
kctx = cctx.kernalContext();
@@ -283,12 +306,12 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
}
/** */
- private static class TreeIndexWrapper implements TreeIndex<IndexRow> {
+ protected static class TreeIndexWrapper implements TreeIndex<IndexRow> {
/** Underlying index. */
- private final InlineIndex idx;
+ protected final InlineIndex idx;
/** */
- private TreeIndexWrapper(InlineIndex idx) {
+ protected TreeIndexWrapper(InlineIndex idx) {
this.idx = idx;
}
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 fb0a5845e1a..98bc94a8285 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
@@ -72,6 +72,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedN
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
@@ -419,6 +420,54 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
}
}
+ /** {@inheritDoc} */
+ @Override public Node<Row> visit(IgniteIndexBound idxBndRel) {
+ IgniteTable tbl = idxBndRel.getTable().unwrap(IgniteTable.class);
+ IgniteIndex idx = tbl.getIndex(idxBndRel.indexName());
+ IgniteTypeFactory typeFactory = ctx.getTypeFactory();
+ ColocationGroup grp = ctx.group(idxBndRel.sourceId());
+ ImmutableBitSet requiredColumns = idxBndRel.requiredColumns();
+ RelDataType rowType = tbl.getRowType(typeFactory, requiredColumns);
+
+ if (idx != null && !tbl.isIndexRebuildInProgress())
+ return new ScanNode<>(ctx, rowType, idx.firstOrLast(idxBndRel.first(), ctx, grp, requiredColumns));
+ else {
+ assert requiredColumns.cardinality() == 1;
+
+ Iterable<Row> rowsIter = tbl.scan(
+ ctx,
+ grp,
+ r -> ctx.rowHandler().get(0, r) != null,
+ null,
+ idxBndRel.requiredColumns()
+ );
+
+ Node<Row> scanNode = new ScanNode<>(ctx, rowType, rowsIter);
+
+ RelCollation collation = idx.collation().apply(LogicalScanConverterRule.createMapping(
+ null,
+ requiredColumns,
+ tbl.getRowType(typeFactory).getFieldCount()
+ ));
+
+ Comparator<Row> cmp = expressionFactory.comparator(collation);
+
+ assert cmp != null;
+
+ SortNode<Row> sortNode = new SortNode<>(
+ ctx,
+ rowType,
+ idxBndRel.first() ? cmp : cmp.reversed(),
+ null,
+ () -> 1
+ );
+
+ sortNode.register(scanNode);
+
+ return sortNode;
+ }
+ }
+
/** {@inheritDoc} */
@Override public Node<Row> visit(IgniteTableScan rel) {
RexNode condition = rel.condition();
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
index 05fd17e1ea8..a98fc2bdb8f 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
@@ -973,7 +973,7 @@ public class Accumulators {
private final boolean min;
/** */
- private final Function<IgniteTypeFactory, RelDataType> typeSupplier;
+ private final transient Function<IgniteTypeFactory, RelDataType> typeSupplier;
/** */
private T val;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java
index 7f38201e501..b4efd07ffcd 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdFragmentMapping.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetada
import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
@@ -199,6 +200,14 @@ public class IgniteMdFragmentMapping implements MetadataHandler<FragmentMappingM
rel.getTable().unwrap(IgniteTable.class).colocationGroup(ctx));
}
+ /**
+ * See {@link IgniteMdFragmentMapping#fragmentMapping(RelNode, RelMetadataQuery, MappingQueryContext)}
+ */
+ public FragmentMapping fragmentMapping(IgniteIndexBound rel, RelMetadataQuery mq, MappingQueryContext ctx) {
+ return FragmentMapping.create(rel.sourceId(),
+ rel.getTable().unwrap(IgniteTable.class).colocationGroup(ctx));
+ }
+
/**
* See {@link IgniteMdFragmentMapping#fragmentMapping(RelNode, RelMetadataQuery, MappingQueryContext)}
*/
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
index 606766fa7ee..149645d0c4b 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedN
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
@@ -154,6 +155,11 @@ public class Cloner implements IgniteRelVisitor<IgniteRel> {
return rel.clone(cluster, F.asList());
}
+ /** {@inheritDoc} */
+ @Override public IgniteRel visit(IgniteIndexBound rel) {
+ return rel.clone(cluster, F.asList());
+ }
+
/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteIndexCount rel) {
return rel.clone(cluster, F.asList());
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java
index 65abac5aae7..a01198dcd93 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelShuttle.java
@@ -23,6 +23,7 @@ import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedN
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
@@ -153,6 +154,11 @@ public class IgniteRelShuttle implements IgniteRelVisitor<IgniteRel> {
return processNode(rel);
}
+ /** {@inheritDoc} */
+ @Override public IgniteRel visit(IgniteIndexBound rel) {
+ return processNode(rel);
+ }
+
/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteTableScan rel) {
return processNode(rel);
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
index bb4de36b4e5..8de3f3018b6 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
@@ -50,6 +50,7 @@ import org.apache.ignite.internal.processors.query.calcite.rule.FilterSpoolMerge
import org.apache.ignite.internal.processors.query.calcite.rule.FilterSpoolMergeToSortedIndexSpoolRule;
import org.apache.ignite.internal.processors.query.calcite.rule.HashAggregateConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.IndexCountRule;
+import org.apache.ignite.internal.processors.query.calcite.rule.IndexMinMaxRule;
import org.apache.ignite.internal.processors.query.calcite.rule.LogicalScanConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.MergeJoinConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.NestedLoopJoinConverterRule;
@@ -238,6 +239,7 @@ public enum PlannerPhase {
LogicalScanConverterRule.INDEX_SCAN,
LogicalScanConverterRule.TABLE_SCAN,
IndexCountRule.INSTANCE,
+ IndexMinMaxRule.INSTANCE,
CollectRule.INSTANCE,
HashAggregateConverterRule.COLOCATED,
HashAggregateConverterRule.MAP_REDUCE,
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexBound.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexBound.java
new file mode 100644
index 00000000000..772599d2eaf
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexBound.java
@@ -0,0 +1,194 @@
+/*
+ * 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.rel;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelInput;
+import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+/**
+ * Extracts first or last not null index value.
+ */
+public class IgniteIndexBound extends AbstractRelNode implements SourceAwareIgniteRel {
+ /** */
+ private static final double INDEX_NULLS_EXPECTED_RATIO = 0.05f;
+
+ /** */
+ private final String idxName;
+
+ /** */
+ private final long sourceId;
+
+ /** */
+ private final boolean first;
+
+ /** */
+ private final RelOptTable tbl;
+
+ /** */
+ private final ImmutableBitSet requiredCols;
+
+ /**
+ * Ctor.
+ *
+ * @param tbl Table definition.
+ * @param cluster Cluster that this relational expression belongs to.
+ * @param traits Traits of this relational expression.
+ * @param idxName Index name.
+ * @param first {@code True} if first not null index value required. {@code False} if last.
+ * @param requiredCols Required columns.
+ */
+ public IgniteIndexBound(
+ RelOptTable tbl,
+ RelOptCluster cluster,
+ RelTraitSet traits,
+ String idxName,
+ boolean first,
+ ImmutableBitSet requiredCols
+ ) {
+ this(-1, tbl, cluster, traits, idxName, first, requiredCols);
+ }
+
+ /**
+ * Ctor.
+ *
+ * @param sourceId Source id.
+ * @param tbl Table definition.
+ * @param cluster Cluster that this relational expression belongs to.
+ * @param traits Traits of this relational expression.
+ * @param idxName Index name.
+ * @param first {@code True} if first not null index value required. {@code False} if last.
+ * @param requiredCols Required columns.
+ */
+ private IgniteIndexBound(
+ long sourceId,
+ RelOptTable tbl,
+ RelOptCluster cluster,
+ RelTraitSet traits,
+ String idxName,
+ boolean first,
+ ImmutableBitSet requiredCols
+ ) {
+ super(cluster, traits);
+
+ this.sourceId = sourceId;
+ this.tbl = tbl;
+ this.idxName = idxName;
+ this.first = first;
+ this.requiredCols = requiredCols;
+ }
+
+ /**
+ * Constructor for deserialization.
+ *
+ * @param input Serialized representation.
+ */
+ public IgniteIndexBound(RelInput input) {
+ super(input.getCluster(), input.getTraitSet());
+
+ Object srcIdObj = input.get("sourceId");
+ if (srcIdObj != null)
+ sourceId = ((Number)srcIdObj).longValue();
+ else
+ sourceId = -1L;
+
+ tbl = input.getTable("table");
+
+ idxName = input.getString("index");
+
+ first = input.getBoolean("first", true);
+
+ requiredCols = input.getBitSet("requiredCols");
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+ //Taking first or last value supposes scan until not null.
+ return planner.getCostFactory().makeCost(1.0, tbl.getRowCount() * INDEX_NULLS_EXPECTED_RATIO
+ * IgniteCost.ROW_PASS_THROUGH_COST, 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelDataType deriveRowType() {
+ return tbl.unwrap(IgniteTable.class).getRowType(Commons.typeFactory(getCluster()), requiredCols);
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelWriter explainTerms(RelWriter pw) {
+ return super.explainTerms(pw)
+ .itemIf("sourceId", sourceId, sourceId != -1L)
+ .item("table", tbl.getQualifiedName())
+ .item("index", idxName)
+ .item("first", first)
+ .item("requiredCols", requiredCols);
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T accept(IgniteRelVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs) {
+ return new IgniteIndexBound(sourceId, tbl, cluster, traitSet, idxName, first, requiredCols);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long sourceId() {
+ return sourceId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteRel clone(long sourceId) {
+ return new IgniteIndexBound(sourceId, tbl, getCluster(), traitSet, idxName, first, requiredCols);
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelOptTable getTable() {
+ return tbl;
+ }
+
+ /** */
+ public String indexName() {
+ return idxName;
+ }
+
+ /**
+ * @return {@code True}, if first index record is required. {@code False}, if last index record is required.
+ */
+ public boolean first() {
+ return first;
+ }
+
+ /** */
+ public ImmutableBitSet requiredColumns() {
+ return requiredCols;
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
index fc5084507de..ca9456e0b0b 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteRelVisitor.java
@@ -74,6 +74,12 @@ public interface IgniteRelVisitor<T> {
*/
T visit(IgniteIndexCount rel);
+
+ /**
+ * See {@link IgniteRelVisitor#visit(IgniteRel)}
+ */
+ T visit(IgniteIndexBound rel);
+
/**
* See {@link IgniteRelVisitor#visit(IgniteRel)}
*/
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexMinMaxRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexMinMaxRule.java
new file mode 100644
index 00000000000..e74ab9989e7
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexMinMaxRule.java
@@ -0,0 +1,95 @@
+/*
+ * 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.rule;
+
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
+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.trait.RewindabilityTrait;
+import org.apache.ignite.internal.util.typedef.F;
+import org.immutables.value.Value;
+
+/**
+ * Tries to optimize MIN() and MAX() so that it takes only first or last not-null index record.
+ */
+@Value.Enclosing
+public class IndexMinMaxRule extends RelRule<IndexMinMaxRule.Config> {
+ /** */
+ public static final IndexMinMaxRule INSTANCE = Config.DEFAULT.toRule();
+
+ /** Ctor. */
+ private IndexMinMaxRule(IndexMinMaxRule.Config cfg) {
+ super(cfg);
+ }
+
+ /** */
+ @Override public void onMatch(RelOptRuleCall call) {
+ IgniteAggregate aggr = call.rel(0);
+ IgniteIndexScan idxScan = call.rel(1);
+ IgniteTable table = idxScan.getTable().unwrap(IgniteTable.class);
+ IgniteIndex idx = table.getIndex(idxScan.indexName());
+
+ if (
+ table.isIndexRebuildInProgress() ||
+ idxScan.condition() != null ||
+ idxScan.projects() != null ||
+ aggr.getGroupCount() > 0 ||
+ aggr.getAggCallList().stream().filter(a -> a.getAggregation().getKind() == SqlKind.MIN
+ || a.getAggregation().getKind() == SqlKind.MAX).count() != 1 ||
+ idx.collation().getFieldCollations().isEmpty() ||
+ idx.collation().getFieldCollations().get(0).getFieldIndex() != idxScan.requiredColumns().nextSetBit(0)
+ )
+ return;
+
+ SqlAggFunction aggFun = aggr.getAggCallList().get(0).getAggregation();
+ boolean firstIdxVal = (aggFun.getKind() == SqlKind.MIN) !=
+ idx.collation().getFieldCollations().get(0).getDirection().isDescending();
+
+ IgniteIndexBound idxMinMaxRel = new IgniteIndexBound(
+ idxScan.getTable(),
+ idxScan.getCluster(),
+ idxScan.getTraitSet().replace(RewindabilityTrait.REWINDABLE),
+ idxScan.indexName(),
+ firstIdxVal,
+ idxScan.requiredColumns()
+ );
+
+ call.transformTo(aggr.clone(aggr.getCluster(), F.asList(idxMinMaxRel)));
+ }
+
+ /** The rule config. */
+ @Value.Immutable
+ public interface Config extends RelRule.Config {
+ /** */
+ IndexMinMaxRule.Config DEFAULT = ImmutableIndexMinMaxRule.Config.of()
+ .withDescription("IndexMinMaxRule")
+ .withOperandSupplier(r -> r.operand(IgniteAggregate.class)
+ .oneInput(i -> i.operand(IgniteIndexScan.class).anyInputs()));
+
+ /** {@inheritDoc} */
+ @Override default IndexMinMaxRule toRule() {
+ return new IndexMinMaxRule(this);
+ }
+ }
+}
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 705c5288afb..368e4cdfd6b 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
@@ -32,7 +32,9 @@ import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.cache.query.index.Index;
import org.apache.ignite.internal.cache.query.index.sorted.inline.IndexQueryContext;
import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
+import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexImpl;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.exec.IndexFirstLastScan;
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;
@@ -117,6 +119,30 @@ public class CacheIndexImpl implements IgniteIndex {
return Collections.emptyList();
}
+ /** {@inheritDoc} */
+ @Override public <Row> Iterable<Row> firstOrLast(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ UUID localNodeId = ectx.localNodeId();
+
+ if (grp.nodeIds().contains(localNodeId) && idx != null) {
+ return new IndexFirstLastScan<>(
+ first,
+ ectx,
+ tbl.descriptor(),
+ idx.unwrap(InlineIndexImpl.class),
+ collation.getKeys(),
+ grp.partitions(localNodeId),
+ requiredColumns
+ );
+ }
+
+ return Collections.emptyList();
+ }
+
/** {@inheritDoc} */
@Override public long count(ExecutionContext<?> ectx, ColocationGroup grp) {
long cnt = 0;
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 5df2b9d60d3..83e76f2d346 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
@@ -94,4 +94,20 @@ public interface IgniteIndex {
* @return Index records number for {@code group}.
*/
public long count(ExecutionContext<?> ectx, ColocationGroup grp);
+
+ /**
+ * Takes only first or last not-null index value.
+ *
+ * @param first {@code True} to take first index not-null value. {@code False} for last.
+ * @param ectx Execution context.
+ * @param grp Colocation group.
+ * @param requiredColumns Required columns.
+ * @return Index records for {@code grp}.
+ */
+ public <Row> Iterable<Row> firstOrLast(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ @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 eb203b1c0cb..d511aa47e16 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
@@ -26,6 +26,7 @@ import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.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;
@@ -101,6 +102,16 @@ public class SystemViewIndexImpl implements IgniteIndex {
return tbl.descriptor().systemView().size();
}
+ /** {@inheritDoc} */
+ @Override public <Row> Iterable<Row> firstOrLast(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ throw new IgniteException("Taking first or last value is not implemented for system view index.");
+ }
+
/** {@inheritDoc} */
@Override public List<SearchBounds> toSearchBounds(
RelOptCluster cluster,
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 b3ec5455981..a3daeb2f2f0 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
@@ -47,6 +47,7 @@ import org.apache.ignite.internal.processors.query.calcite.exec.rel.SortNode;
import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
import org.apache.ignite.internal.processors.query.calcite.planner.TestTable;
import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
@@ -55,6 +56,7 @@ import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactor
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
@@ -146,6 +148,54 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
rexBuilder = cluster.getRexBuilder();
}
+ /**
+ * Tests Index take-first execution plan is changed to Sort-Limit/Scan when index is unavailable.
+ */
+ @Test
+ public void testIndexFirstRewriter() {
+ checkIndexFirstOrLastRewriter(true);
+ }
+
+ /**
+ * Tests Index take-last execution plan is changed to Sort-Limit/Scan when index is unavailable.
+ */
+ @Test
+ public void testIndexLastRewriter() {
+ checkIndexFirstOrLastRewriter(false);
+ }
+
+ /**
+ * Tests Index take-last or take-first execution plan is changed to Sort-limit/Scan when index is unavailable.
+ */
+ private void checkIndexFirstOrLastRewriter(boolean first) {
+ int idxColumn = 2;
+
+ tbl.addIndex(QueryUtils.PRIMARY_KEY_INDEX, idxColumn);
+
+ IgniteIndexBound idxScan = new IgniteIndexBound(
+ qctx.catalogReader().getTable(F.asList("PUBLIC", "TBL")),
+ cluster,
+ cluster.traitSet(),
+ QueryUtils.PRIMARY_KEY_INDEX,
+ first,
+ ImmutableBitSet.of(idxColumn)
+ );
+
+ Node<?> node = relImplementor.visit(idxScan);
+
+ assertTrue(node instanceof ScanNode);
+ assertNull(node.sources());
+
+ tbl.markIndexRebuildInProgress(true);
+
+ node = relImplementor.visit(idxScan);
+
+ assertTrue(node instanceof SortNode);
+ assertEquals(1, (int)U.field(node, "limit"));
+ assertTrue(node.sources() != null && node.sources().size() == 1);
+ assertTrue(node.sources().get(0) instanceof ScanNode);
+ }
+
/**
* Tests IndexCount execution plan is changed to Collect/Scan when index is unavailable.
*/
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 3add7c03e23..781f423493d 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
@@ -250,6 +250,16 @@ public class AbstractBasicIntegrationTest extends GridCommonAbstractTest {
@Override public long count(ExecutionContext<?> ectx, ColocationGroup grp) {
return delegate.count(ectx, grp);
}
+
+ /** {@inheritDoc} */
+ @Override public <Row> Iterable<Row> firstOrLast(
+ boolean first,
+ ExecutionContext<Row> ectx,
+ ColocationGroup grp,
+ @Nullable ImmutableBitSet requiredColumns
+ ) {
+ return delegate.firstOrLast(first, ectx, grp, requiredColumns);
+ }
}
/** */
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
index 105ea61324c..ca6c15f7f75 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
@@ -21,7 +21,11 @@ import java.util.List;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
+import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;
@@ -29,20 +33,79 @@ import org.junit.Test;
*
*/
public class AggregatesIntegrationTest extends AbstractBasicIntegrationTest {
+ /** */
+ @Test
+ public void testMinMaxWithTable() {
+ String[] indexes = new String[] {
+ "val0",
+ "val0 desc",
+ "val0, val1",
+ "val0 desc, val1 desc",
+ "val0 asc, val1 desc",
+ "val0 desc, val1 asc"
+ };
+
+ for (String idx : indexes) {
+ for (int backups = -1; backups < 3; ++backups) {
+ executeSql("create table tbl(id integer primary key, val0 integer, val1 float, val2 varchar) " +
+ "with template=" + (backups < 0 ? "replicated" : "partitioned,backups=" + backups));
+
+ executeSql("create index test_idx on tbl(" + idx + ")");
+
+ fillTestTbl();
+
+ assertQuery("select min(val0) from tbl").returns(1).check();
+ assertQuery("select min(val1) from tbl").returns(10.0f).check();
+ assertQuery("select max(val0) from tbl").returns(5).check();
+ assertQuery("select max(val1) from tbl").returns(50.0f).check();
+
+ executeSql("drop table tbl");
+ }
+ }
+ }
+
+ /** */
+ private void fillTestTbl() {
+ executeSql("insert into tbl values(-1, null, null, 'value_-1')");
+ executeSql("insert into tbl values(1, 2, 20.0, 'value_1')");
+ executeSql("insert into tbl values(2, 3, 10.0, 'value_2')");
+ executeSql("insert into tbl values(3, null, 30.0, null)");
+ executeSql("insert into tbl values(4, 4, 30.0, 'value_4')");
+ executeSql("insert into tbl values(5, 5, 50.0, 'value_5')");
+ executeSql("insert into tbl values(6, 1, null, 'value_6')");
+ executeSql("insert into tbl values(7, null, 20.0, 'value_7')");
+ executeSql("insert into tbl values(8, null, null, null)");
+ }
+
+ /** */
+ @Test
+ public void testMinMaxWithEntity() {
+ for (int b = -1; b < 3; ++b) {
+ createAndPopulateIndexedTable(b, b < 0 ? CacheMode.REPLICATED : CacheMode.PARTITIONED);
+
+ assertQuery("select min(salary) from person").returns(1.0).check();
+ assertQuery("select min(descVal) from person").returns(1.0).check();
+ assertQuery("select max(salary) from person").returns(15.0).check();
+ assertQuery("select max(descVal) from person").returns(15.0).check();
+
+ client.destroyCache(TABLE_NAME);
+ }
+ }
+
/** */
@Test
public void testCountWithBackupsAndCacheModes() {
for (int b = 0; b < 2; ++b) {
- createAndPopulateTable(b, CacheMode.PARTITIONED);
+ createAndPopulateIndexedTable(b, CacheMode.PARTITIONED);
- assertQuery("select count(*) from person").returns(5L).check();
+ assertQuery("select count(*) from person").returns(7L).check();
client.destroyCache(TABLE_NAME);
}
- createAndPopulateTable(0, CacheMode.REPLICATED);
+ createAndPopulateIndexedTable(0, CacheMode.REPLICATED);
- assertQuery("select count(*) from person").returns(5L).check();
+ assertQuery("select count(*) from person").returns(7L).check();
}
/** */
@@ -214,4 +277,47 @@ public class AggregatesIntegrationTest extends AbstractBasicIntegrationTest {
.returns("val1", 50L)
.check();
}
+
+ /** */
+ protected void createAndPopulateIndexedTable(int backups, CacheMode cacheMode) {
+ IgniteCache<Integer, IndexedEmployer> person = client.getOrCreateCache(new CacheConfiguration<Integer, IndexedEmployer>()
+ .setName(TABLE_NAME)
+ .setSqlSchema("PUBLIC")
+ .setQueryEntities(F.asList(new QueryEntity(Integer.class, IndexedEmployer.class).setTableName(TABLE_NAME)))
+ .setCacheMode(cacheMode)
+ .setBackups(backups)
+ );
+
+ int idx = 0;
+
+ person.put(idx++, new IndexedEmployer("Igor", 5d, 7d));
+ person.put(idx++, new IndexedEmployer(null, 3d, null));
+ person.put(idx++, new IndexedEmployer("Ilya", 1d, 1d));
+ person.put(idx++, new IndexedEmployer("Roma", null, 9d));
+ person.put(idx++, new IndexedEmployer(null, null, null));
+ person.put(idx++, new IndexedEmployer("Oleg", 15d, 15d));
+ person.put(idx++, new IndexedEmployer("Maya", null, null));
+ }
+
+ /** */
+ public static class IndexedEmployer {
+ /** */
+ @QuerySqlField
+ public String name;
+
+ /** */
+ @QuerySqlField(index = true)
+ public Double salary;
+
+ /** */
+ @QuerySqlField(index = true, descending = true)
+ public Double descVal;
+
+ /** */
+ public IndexedEmployer(String name, Double salary, Double descVal) {
+ this.name = name;
+ this.salary = salary;
+ this.descVal = salary;
+ }
+ }
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexRebuildIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexRebuildIntegrationTest.java
index 152f91e1156..032b7f90f35 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexRebuildIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/IndexRebuildIntegrationTest.java
@@ -208,6 +208,66 @@ public class IndexRebuildIntegrationTest extends AbstractBasicIntegrationTest {
}
}
+ /**
+ * Test min-max optimization with index rebuilding.
+ */
+ @Test
+ public void testIndexFirstLastAtUnavailableIndex() throws IgniteCheckedException {
+ int records = 50;
+ int iterations = 500;
+
+ CalciteQueryProcessor srvEngine = Commons.lookupComponent(grid(0).context(), CalciteQueryProcessor.class);
+
+ for (boolean asc : new boolean[] {true, false}) {
+ for (int backups = -1; backups < 3; ++backups) {
+ String ddl = "CREATE TABLE tbl3 (id INT PRIMARY KEY, val BIGINT, val2 VARCHAR) WITH ";
+
+ ddl += backups < 0 ? "TEMPLATE=REPLICATED" : "TEMPLATE=PARTITIONED,backups=" + backups;
+
+ executeSql(ddl);
+
+ executeSql("CREATE INDEX TEST_IDX ON tbl3(val " + (asc ? "asc)" : "desc)"));
+
+ executeSql("INSERT INTO tbl3 VALUES (-1, null, null)");
+
+ for (int i = 0; i < records; i++)
+ executeSql("INSERT INTO tbl3 VALUES (?, ?, ?)", i, i, "val" + i);
+
+ executeSql("INSERT INTO tbl3 VALUES (" + records + ", null, null)");
+
+ IgniteCacheTable tbl3 = (IgniteCacheTable)srvEngine.schemaHolder().schema("PUBLIC").getTable("TBL3");
+
+ AtomicBoolean stop = new AtomicBoolean();
+
+ tbl3.markIndexRebuildInProgress(false);
+
+ IgniteInternalFuture<?> fut = GridTestUtils.runAsync(() -> {
+ boolean lever = true;
+
+ while (!stop.get())
+ tbl3.markIndexRebuildInProgress(lever = !lever);
+ });
+
+ try {
+ for (int i = 0; i < iterations; ++i) {
+ assertQuery("select MIN(val) from tbl3").returns((long)0).check();
+
+ assertQuery("select MAX(val) from tbl3").returns((long)(records - 1)).check();
+ }
+ }
+ finally {
+ stop.set(true);
+
+ tbl3.markIndexRebuildInProgress(false);
+
+ executeSql("DROP TABLE tbl3");
+ }
+
+ fut.get();
+ }
+ }
+ }
+
/**
* Test IndexCount is disabled at index rebuilding.
*/
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java
index 403d7465876..01122091fe0 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java
@@ -19,9 +19,10 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
import java.util.Arrays;
import java.util.List;
-
import com.google.common.collect.ImmutableList;
import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.fun.SqlAvgAggFunction;
import org.apache.calcite.sql.fun.SqlCountAggFunction;
@@ -29,6 +30,7 @@ import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteMapHashAggregate;
@@ -43,11 +45,79 @@ import org.hamcrest.core.IsInstanceOf;
import org.junit.Assert;
import org.junit.Test;
+import static org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils.createFieldCollation;
+
/**
*
*/
@SuppressWarnings({"TooBroadScope", "FieldCanBeLocal", "TypeMayBeWeakened"})
public class HashAggregatePlannerTest extends AbstractAggregatePlannerTest {
+ /** */
+ @Test
+ public void indexMinMax() throws Exception {
+ IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
+
+ RelCollation[] collations = new RelCollation[] {
+ RelCollations.of(createFieldCollation(1, true)),
+ RelCollations.of(createFieldCollation(1, false)),
+ RelCollations.of(createFieldCollation(1, true), createFieldCollation(2, true)),
+ RelCollations.of(createFieldCollation(1, false), createFieldCollation(2, false)),
+ RelCollations.of(createFieldCollation(1, true), createFieldCollation(2, false)),
+ RelCollations.of(createFieldCollation(1, false), createFieldCollation(2, true))
+ };
+
+ for (RelCollation cll : collations) {
+ for (IgniteDistribution distr : distributions()) {
+ TestTable tbl = createTable(distr);
+ publicSchema.addTable("TEST", tbl);
+
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL0) FROM TEST", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL0) FROM TEST", publicSchema);
+
+ tbl.addIndex(cll, "TEST_IDX");
+
+ boolean targetFldIdxAcc = !cll.getFieldCollations().get(0).direction.isDescending();
+
+ assertIndexFirstOrLastRecord("SELECT MIN(VAL0) FROM TEST", targetFldIdxAcc, publicSchema);
+ assertIndexFirstOrLastRecord("SELECT MAX(VAL0) FROM TEST", !targetFldIdxAcc, publicSchema);
+ assertIndexFirstOrLastRecord("SELECT MIN(V) FROM (SELECT MIN(VAL0) AS V FROM TEST)",
+ targetFldIdxAcc, publicSchema);
+ assertIndexFirstOrLastRecord("SELECT MAX(V) FROM (SELECT MAX(VAL0) AS V FROM TEST)",
+ !targetFldIdxAcc, publicSchema);
+ assertIndexFirstOrLastRecord("SELECT MAX(VAL0) FROM TEST", !targetFldIdxAcc, publicSchema);
+
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL0) FROM TEST GROUP BY GRP0", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL0) FROM TEST GROUP BY GRP0, GRP1 ORDER BY GRP1 DESC",
+ publicSchema);
+
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL1) FROM TEST", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT MAX(VAL1) FROM TEST", publicSchema);
+
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL0) FROM TEST WHERE VAL1 > 1", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT MAX(VAL0) FROM TEST WHERE VAL1 > 1", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT VAL1, MIN(VAL0) FROM TEST GROUP BY VAL1", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT VAL1, MAX(VAL0) FROM TEST GROUP BY VAL1", publicSchema);
+
+ assertNoIndexFirstOrLastRecord("SELECT MIN(VAL0 + 1) FROM TEST", publicSchema);
+ assertNoIndexFirstOrLastRecord("SELECT MAX(VAL0 + 1) FROM TEST", publicSchema);
+
+ publicSchema.removeTable("TEST");
+ }
+ }
+ }
+
+ /** */
+ private void assertIndexFirstOrLastRecord(String sql, boolean first, IgniteSchema schema) throws Exception {
+ assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
+ .and(nodeOrAnyChild(isInstanceOf(IgniteIndexBound.class)
+ .and(s -> first && s.first() || !first && !s.first())))));
+ }
+
+ /** */
+ private void assertNoIndexFirstOrLastRecord(String sql, IgniteSchema publicSchema) throws Exception {
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteIndexBound.class)).negate());
+ }
+
/**
* Tests COUNT(...) plan with and without IndexCount optimization.
*/
@@ -55,13 +125,7 @@ public class HashAggregatePlannerTest extends AbstractAggregatePlannerTest {
public void indexCount() throws Exception {
IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
- List<IgniteDistribution> lst = ImmutableList.of(
- IgniteDistributions.single(),
- IgniteDistributions.random(),
- IgniteDistributions.broadcast(),
- IgniteDistributions.hash(ImmutableList.of(0, 1, 2, 3)));
-
- for (IgniteDistribution distr : lst) {
+ for (IgniteDistribution distr : distributions()) {
TestTable tbl = createTable(distr);
publicSchema.addTable("TEST", tbl);
@@ -108,6 +172,15 @@ public class HashAggregatePlannerTest extends AbstractAggregatePlannerTest {
}
}
+ /** */
+ private static List<IgniteDistribution> distributions() {
+ return ImmutableList.of(
+ IgniteDistributions.single(),
+ IgniteDistributions.random(),
+ IgniteDistributions.broadcast(),
+ IgniteDistributions.hash(ImmutableList.of(0, 1, 2, 3)));
+ }
+
/** */
private void assertIndexCount(String sql, IgniteSchema schema) throws Exception {
assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/IndexRebuildPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/IndexRebuildPlannerTest.java
index 114322ac88f..14c100a8871 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/IndexRebuildPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/IndexRebuildPlannerTest.java
@@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
@@ -32,6 +33,8 @@ import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;
import static org.apache.calcite.sql.SqlKind.COUNT;
+import static org.apache.calcite.sql.SqlKind.MAX;
+import static org.apache.calcite.sql.SqlKind.MIN;
import static org.apache.calcite.sql.SqlKind.SUM0;
/**
@@ -49,7 +52,7 @@ public class IndexRebuildPlannerTest extends AbstractPlannerTest {
super.setup();
tbl = createTable("TBL", 100, IgniteDistributions.single(), "ID", Integer.class, "VAL", String.class)
- .addIndex(QueryUtils.PRIMARY_KEY_INDEX, 0);
+ .addIndex(QueryUtils.PRIMARY_KEY_INDEX, 0).addIndex("TBL_VAL_IDX", 1);
publicSchema = createSchema(tbl);
}
@@ -92,6 +95,39 @@ public class IndexRebuildPlannerTest extends AbstractPlannerTest {
.and(nodeOrAnyChild(isInstanceOf(IgniteIndexCount.class)))));
}
+ /** Test Index min/max (first/last) is disabled when index is unavailable. */
+ @Test
+ public void testIndexMinMaxAtIndexRebuild() throws Exception {
+ checkMinMaxOptimized();
+
+ tbl.markIndexRebuildInProgress(true);
+
+ assertPlan("SELECT MIN(VAL) FROM TBL", publicSchema, isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == MIN).count() == 1)
+ .and(hasChildThat(isTableScan("TBL"))));
+
+ assertPlan("SELECT MAX(VAL) FROM TBL", publicSchema, isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == MAX).count() == 1)
+ .and(hasChildThat(isTableScan("TBL"))));
+
+ tbl.markIndexRebuildInProgress(false);
+
+ checkMinMaxOptimized();
+ }
+
+ /** */
+ private void checkMinMaxOptimized() throws Exception {
+ assertPlan("SELECT MIN(VAL) FROM TBL", publicSchema, isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == MIN).count() == 1)
+ .and(hasChildThat(isInstanceOf(IgniteIndexBound.class)
+ .and(is -> "TBL_VAL_IDX".equals(is.indexName())))));
+
+ assertPlan("SELECT MAX(VAL) FROM TBL", publicSchema, isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == MAX).count() == 1)
+ .and(hasChildThat(isInstanceOf(IgniteIndexBound.class)
+ .and(is -> "TBL_VAL_IDX".equals(is.indexName())))));
+ }
+
/** */
@Test
public void testConcurrentIndexRebuildStateChange() throws Exception {
@@ -147,4 +183,42 @@ public class IndexRebuildPlannerTest extends AbstractPlannerTest {
fut.get();
}
+
+ /**
+ * Test Index min/max is disabled when index becomes unavailable.
+ */
+ @Test
+ public void testIndexMinMaxAtConcurrentIndexRebuild() throws Exception {
+ AtomicBoolean stop = new AtomicBoolean();
+
+ IgniteInternalFuture<?> fut = GridTestUtils.runAsync(() -> {
+ boolean lever = true;
+
+ while (!stop.get())
+ tbl.markIndexRebuildInProgress(lever = !lever);
+ });
+
+ tbl.markIndexRebuildInProgress(true);
+
+ try {
+ for (int i = 0; i < 500; i++) {
+ assertPlan("SELECT MIN(VAL) FROM TBL", publicSchema,
+ nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteIndexBound.class)
+ .and(is -> "TBL_VAL_IDX".equals(is.indexName())))
+ .or(input(isInstanceOf(IgniteTableScan.class))))));
+
+ assertPlan("SELECT MAX(VAL) FROM TBL", publicSchema,
+ nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteIndexBound.class)
+ .and(is -> "TBL_VAL_IDX".equals(is.indexName())))
+ .or(input(isInstanceOf(IgniteTableScan.class))))));
+ }
+ }
+ finally {
+ stop.set(true);
+ }
+
+ fut.get();
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java b/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
index cf4f13ea61d..41a4b4c37f3 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/QueryEntity.java
@@ -102,7 +102,7 @@ public class QueryEntity implements Serializable {
/** Scale for fields. */
private Map<String, Integer> fieldsScale = new HashMap<>();
-
+
/**
* Creates an empty query entity.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/SortedSegmentedIndex.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/SortedSegmentedIndex.java
index 03a6099f6cc..1db578257d3 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/SortedSegmentedIndex.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/SortedSegmentedIndex.java
@@ -85,6 +85,14 @@ public interface SortedSegmentedIndex extends Index {
public GridCursor<IndexRow> findLast(int segment, IndexQueryContext qryCtx)
throws IgniteCheckedException;
+ /**
+ * Takes only one first or last index record.
+ *
+ * @param qryCtx External index qyery context.
+ * @param first {@code True} to take first index value. {@code False} to take last index value.
+ */
+ public GridCursor<IndexRow> findFirstOrLast(IndexQueryContext qryCtx, boolean first) throws IgniteCheckedException;
+
/**
* Counts index rows in specified tree segment.
*
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/client/ClientInlineIndex.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/client/ClientInlineIndex.java
index 529b0e8f2f7..9ae649ec00c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/client/ClientInlineIndex.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/client/ClientInlineIndex.java
@@ -91,6 +91,11 @@ public class ClientInlineIndex extends AbstractClientIndex implements InlineInde
throw unsupported();
}
+ /** {@inheritDoc} */
+ @Override public GridCursor<IndexRow> findFirstOrLast(IndexQueryContext qryCtx, boolean first) {
+ throw unsupported();
+ }
+
/** {@inheritDoc} */
@Override public long count(int segment) {
throw unsupported();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java
index bbabaf91d6e..2d65ac03bca 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineIndexImpl.java
@@ -17,8 +17,11 @@
package org.apache.ignite.internal.cache.query.index.sorted.inline;
+import java.util.ArrayDeque;
+import java.util.Collections;
import java.util.Comparator;
import java.util.PriorityQueue;
+import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
@@ -121,6 +124,26 @@ public class InlineIndexImpl extends AbstractIndex implements InlineIndex {
}
}
+ /** {@inheritDoc} */
+ @Override public GridCursor<IndexRow> findFirstOrLast(IndexQueryContext qryCtx, boolean first)
+ throws IgniteCheckedException {
+ lock.readLock().lock();
+
+ try {
+ int segmentsCnt = segmentsCount();
+
+ final GridCursor<IndexRow>[] segmentCursors = new GridCursor[segmentsCnt];
+
+ for (int i = 0; i < segmentsCnt; i++)
+ segmentCursors[i] = first ? findFirst(i, qryCtx) : findLast(i, qryCtx);
+
+ return new SingleValueSegmentedIndexCursor(segmentCursors, def);
+ }
+ finally {
+ lock.readLock().unlock();
+ }
+ }
+
/** {@inheritDoc} */
@Override public GridCursor<IndexRow> find(
IndexRow lower,
@@ -452,7 +475,7 @@ public class InlineIndexImpl extends AbstractIndex implements InlineIndex {
assert !cctx.mvccEnabled() || v != null;
- if (cacheFilter == null && v == null)
+ if (cacheFilter == null && v == null && qryCtx.rowFilter() == null)
return null;
return new InlineTreeFilterClosure(
@@ -574,7 +597,7 @@ public class InlineIndexImpl extends AbstractIndex implements InlineIndex {
/** Single cursor over multiple segments. The next value is chosen with the index row comparator. */
private static class SegmentedIndexCursor implements GridCursor<IndexRow> {
/** Cursors over segments. */
- private final PriorityQueue<GridCursor<IndexRow>> cursors;
+ private final Queue<GridCursor<IndexRow>> cursors;
/** Comparator to compare index rows. */
private final Comparator<GridCursor<IndexRow>> cursorComp;
@@ -612,12 +635,7 @@ public class InlineIndexImpl extends AbstractIndex implements InlineIndex {
}
};
- this.cursors = new PriorityQueue<>(cursors.length, cursorComp);
-
- for (GridCursor<IndexRow> c: cursors) {
- if (c.next())
- this.cursors.add(c);
- }
+ this.cursors = cursorsQueue(cursors);
}
/** {@inheritDoc} */
@@ -639,5 +657,47 @@ public class InlineIndexImpl extends AbstractIndex implements InlineIndex {
@Override public IndexRow get() throws IgniteCheckedException {
return head;
}
+
+ /** */
+ protected Queue<GridCursor<IndexRow>> cursorsQueue(GridCursor<IndexRow>[] cursors)
+ throws IgniteCheckedException {
+ PriorityQueue<GridCursor<IndexRow>> q = new PriorityQueue<>(cursors.length, cursorComp);
+
+ for (GridCursor<IndexRow> c: cursors) {
+ if (c.next())
+ q.add(c);
+ }
+
+ return q;
+ }
+ }
+
+ /** First-only, single-value-only segmented cursor. */
+ private static class SingleValueSegmentedIndexCursor extends SegmentedIndexCursor {
+ /** Ctor. */
+ SingleValueSegmentedIndexCursor(
+ GridCursor<IndexRow>[] cursors,
+ SortedIndexDefinition idxDef
+ ) throws IgniteCheckedException {
+ super(cursors, idxDef);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected Queue<GridCursor<IndexRow>> cursorsQueue(GridCursor<IndexRow>[] cursors)
+ throws IgniteCheckedException {
+ Queue<GridCursor<IndexRow>> srcQueue = super.cursorsQueue(cursors);
+
+ if (!srcQueue.isEmpty()) {
+ GridCursor<IndexRow> cur = srcQueue.poll();
+
+ IndexRow row = cur.get();
+
+ assert !cur.next();
+
+ return new ArrayDeque<>(Collections.singleton(new SingleCursor<>(row)));
+ }
+
+ return srcQueue;
+ }
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineTreeFilterClosure.java b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineTreeFilterClosure.java
index 3ff58127dd0..70ceb250d60 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineTreeFilterClosure.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/cache/query/index/sorted/inline/InlineTreeFilterClosure.java
@@ -64,7 +64,7 @@ public class InlineTreeFilterClosure implements BPlusTree.TreeRowClosure<IndexRo
GridCacheContext<?, ?> cctx,
IgniteLogger log
) {
- assert (cacheFilter != null || mvccSnapshot != null) && cctx != null;
+ assert (cacheFilter != null || mvccSnapshot != null || rowFilter != null) && cctx != null;
this.cacheFilter = cacheFilter;
this.rowFilter = rowFilter;