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/06/24 06:35:11 UTC
[ignite] branch master updated: IGNITE-16920 SQL Calcite: Use index count scan for COUNT(*) - Fixes #10050.
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 486fe7f934a IGNITE-16920 SQL Calcite: Use index count scan for COUNT(*) - Fixes #10050.
486fe7f934a is described below
commit 486fe7f934acfe64cab41759fe32f65747cac0e5
Author: Vladimir Steshin <vl...@gmail.com>
AuthorDate: Fri Jun 24 11:31:53 2022 +0500
IGNITE-16920 SQL Calcite: Use index count scan for COUNT(*) - Fixes #10050.
Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
.../processors/query/calcite/exec/IndexScan.java | 5 -
.../query/calcite/exec/LogicalRelImplementor.java | 22 +++
.../query/calcite/exec/rel/CollectNode.java | 62 +++++++-
.../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/IgniteIndexCount.java | 165 +++++++++++++++++++++
.../query/calcite/rel/IgniteRelVisitor.java | 5 +
.../query/calcite/rel/IgniteTableScan.java | 2 +-
.../query/calcite/rule/IndexCountRule.java | 108 ++++++++++++++
.../query/calcite/schema/CacheIndexImpl.java | 31 +++-
.../query/calcite/schema/IgniteIndex.java | 9 ++
.../query/calcite/schema/SystemViewIndexImpl.java | 5 +
.../calcite/sql/fun/IgniteStdSqlOperatorTable.java | 1 +
.../calcite/exec/LogicalRelImplementorTest.java | 86 +++++++++--
.../integration/AbstractBasicIntegrationTest.java | 21 ++-
.../integration/AggregatesIntegrationTest.java | 22 ++-
.../integration/IndexRebuildIntegrationTest.java | 53 +++++++
.../planner/AbstractAggregatePlannerTest.java | 54 +++----
.../calcite/planner/HashAggregatePlannerTest.java | 77 ++++++++++
.../calcite/planner/IndexRebuildPlannerTest.java | 59 +++++++-
.../calcite/planner/LimitOffsetPlannerTest.java | 2 +-
.../query/calcite/planner/TestTable.java | 2 +-
24 files changed, 760 insertions(+), 54 deletions(-)
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 40874bdafd8..805cfa01206 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
@@ -39,7 +39,6 @@ import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndex;
import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKey;
import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKeyFactory;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
-import org.apache.ignite.internal.processors.cache.CacheObjectContext;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
@@ -65,9 +64,6 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
/** */
private final GridCacheContext<?, ?> cctx;
- /** */
- private final CacheObjectContext coCtx;
-
/** */
private final CacheTableDescriptor desc;
@@ -133,7 +129,6 @@ public class IndexScan<Row> extends AbstractIndexScan<Row, IndexRow> {
this.idx = idx;
cctx = desc.cacheContext();
kctx = cctx.kernalContext();
- coCtx = cctx.cacheObjectContext();
factory = ectx.rowHandler().factory(ectx.getTypeFactory(), rowType);
topVer = ectx.topologyVersion();
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 c44e6195eca..20f9def3eb3 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
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.query.calcite.exec;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@@ -69,6 +70,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.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
@@ -406,6 +408,26 @@ public class LogicalRelImplementor<Row> implements IgniteRelVisitor<Node<Row>> {
}
}
+ /** {@inheritDoc} */
+ @Override public Node<Row> visit(IgniteIndexCount rel) {
+ IgniteTable tbl = rel.getTable().unwrap(IgniteTable.class);
+ IgniteIndex idx = tbl.getIndex(rel.indexName());
+
+ if (idx != null && !tbl.isIndexRebuildInProgress()) {
+ return new ScanNode<>(ctx, rel.getRowType(), () -> Collections.singletonList(ctx.rowHandler()
+ .factory(ctx.getTypeFactory(), rel.getRowType())
+ .create(idx.count(ctx, ctx.group(rel.sourceId())))).iterator());
+ }
+ else {
+ CollectNode<Row> replacement = CollectNode.createCountCollector(ctx);
+
+ replacement.register(new ScanNode<>(ctx, rel.getTable().getRowType(), tbl.scan(ctx,
+ ctx.group(rel.sourceId()), null, null, ImmutableBitSet.of(0))));
+
+ return replacement;
+ }
+ }
+
/** {@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/rel/CollectNode.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CollectNode.java
index 717a02d58fe..2f63d863776 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CollectNode.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/CollectNode.java
@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.function.Supplier;
import com.google.common.collect.Iterables;
import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
@@ -41,16 +42,49 @@ public class CollectNode<Row> extends AbstractNode<Row> implements SingleNode<Ro
private int waiting;
/**
+ * Creates Collect node with the collector depending on {@code rowType}.
+ *
* @param ctx Execution context.
* @param rowType Output row type.
*/
public CollectNode(
ExecutionContext<Row> ctx,
RelDataType rowType
+ ) {
+ this(ctx, rowType, createCollector(ctx, rowType));
+ }
+
+ /**
+ * Creates Collect node with the collector depending on {@code rowType}.
+ *
+ * @param ctx Execution context.
+ * @param rowType Output row type.
+ * @param collector Collector.
+ */
+ private CollectNode(
+ ExecutionContext<Row> ctx,
+ RelDataType rowType,
+ Collector<Row> collector
) {
super(ctx, rowType);
- collector = createCollector(ctx, rowType);
+ this.collector = collector;
+ }
+
+ /**
+ * Creates row counting Collect node.
+ *
+ * @param ctx Execution context.
+ */
+ public static <Row> CollectNode<Row> createCountCollector(ExecutionContext<Row> ctx) {
+ RelDataType rowType = ctx.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
+
+ Collector<Row> collector = new Counter<>(
+ ctx.rowHandler(),
+ ctx.rowHandler().factory(ctx.getTypeFactory(), rowType),
+ 1);
+
+ return new CollectNode<>(ctx, rowType, collector);
}
/** {@inheritDoc} */
@@ -236,4 +270,30 @@ public class CollectNode<Row> extends AbstractNode<Row> implements SingleNode<Ro
outBuf = new ArrayList<>(cap);
}
}
+
+ /** */
+ private static class Counter<Row> extends Collector<Row> {
+ /** */
+ private long cnt;
+
+ /** */
+ private Counter(RowHandler<Row> hnd, RowHandler.RowFactory<Row> rowFactory, int cap) {
+ super(hnd, rowFactory, cap);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected Object outData() {
+ return cnt;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void push(Row row) {
+ ++cnt;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void clear() {
+ cnt = 0;
+ }
+ }
}
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 d46cdb6af72..7f38201e501 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.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteReceiver;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableFunctionScan;
@@ -190,6 +191,14 @@ public class IgniteMdFragmentMapping implements MetadataHandler<FragmentMappingM
rel.getTable().unwrap(IgniteTable.class).colocationGroup(ctx));
}
+ /**
+ * See {@link IgniteMdFragmentMapping#fragmentMapping(RelNode, RelMetadataQuery, MappingQueryContext)}
+ */
+ public FragmentMapping fragmentMapping(IgniteIndexCount 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 9cee53a2ece..606766fa7ee 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.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
@@ -153,6 +154,11 @@ public class Cloner implements IgniteRelVisitor<IgniteRel> {
return rel.clone(cluster, F.asList());
}
+ /** {@inheritDoc} */
+ @Override public IgniteRel visit(IgniteIndexCount rel) {
+ return rel.clone(cluster, F.asList());
+ }
+
/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteTableScan 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 a90de5119b7..65abac5aae7 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.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
@@ -147,6 +148,11 @@ public class IgniteRelShuttle implements IgniteRelVisitor<IgniteRel> {
return processNode(rel);
}
+ /** {@inheritDoc} */
+ @Override public IgniteRel visit(IgniteIndexCount 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 8a0471677e9..bb4de36b4e5 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
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.processors.query.calcite.rule.FilterConverterR
import org.apache.ignite.internal.processors.query.calcite.rule.FilterSpoolMergeToHashIndexSpoolRule;
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.LogicalScanConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.MergeJoinConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.NestedLoopJoinConverterRule;
@@ -236,6 +237,7 @@ public enum PlannerPhase {
ValuesConverterRule.INSTANCE,
LogicalScanConverterRule.INDEX_SCAN,
LogicalScanConverterRule.TABLE_SCAN,
+ IndexCountRule.INSTANCE,
CollectRule.INSTANCE,
HashAggregateConverterRule.COLOCATED,
HashAggregateConverterRule.MAP_REDUCE,
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexCount.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexCount.java
new file mode 100644
index 00000000000..a9bb03e5527
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexCount.java
@@ -0,0 +1,165 @@
+/*
+ * 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.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.type.SqlTypeName;
+
+import static java.util.Collections.singletonList;
+
+/**
+ * Returns number of index records.
+ */
+public class IgniteIndexCount extends AbstractRelNode implements SourceAwareIgniteRel {
+ /** */
+ private static final double INDEX_TRAVERSE_COST_DIVIDER = 1000;
+
+ /** */
+ private final RelOptTable tbl;
+
+ /** */
+ private final String idxName;
+
+ /** */
+ private final long sourceId;
+
+ /**
+ * Constructor for deserialization.
+ *
+ * @param input Serialized representation.
+ */
+ public IgniteIndexCount(RelInput input) {
+ super(input.getCluster(), input.getTraitSet());
+
+ idxName = input.getString("index");
+ tbl = input.getTable("table");
+
+ Object srcIdObj = input.get("sourceId");
+ if (srcIdObj != null)
+ sourceId = ((Number)srcIdObj).longValue();
+ else
+ sourceId = -1L;
+ }
+
+ /**
+ * Ctor.
+ *
+ * @param cluster Cluster that this relational expression belongs to.
+ * @param traits Traits of this relational expression.
+ * @param tbl Table definition.
+ * @param idxName Index name.
+ */
+ public IgniteIndexCount(
+ RelOptCluster cluster,
+ RelTraitSet traits,
+ RelOptTable tbl,
+ String idxName
+ ) {
+ this(-1, cluster, traits, tbl, idxName);
+ }
+
+ /**
+ * Ctor.
+ *
+ * @param sourceId Source id.
+ * @param cluster Cluster that this relational expression belongs to.
+ * @param traits Traits of this relational expression.
+ * @param tbl Table definition.
+ * @param idxName Index name.
+ */
+ private IgniteIndexCount(
+ long sourceId,
+ RelOptCluster cluster,
+ RelTraitSet traits,
+ RelOptTable tbl,
+ String idxName
+ ) {
+ super(cluster, traits);
+
+ this.idxName = idxName;
+ this.tbl = tbl;
+ this.sourceId = sourceId;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected RelDataType deriveRowType() {
+ RelDataTypeFactory tf = getCluster().getTypeFactory();
+
+ return tf.createStructType(singletonList(tf.createSqlType(SqlTypeName.BIGINT)), singletonList("COUNT"));
+ }
+
+ /** */
+ public String indexName() {
+ return idxName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public double estimateRowCount(RelMetadataQuery mq) {
+ // Requesting index count always produces just one record.
+ return 1.0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+ return planner.getCostFactory().makeCost(1.0, tbl.getRowCount() / INDEX_TRAVERSE_COST_DIVIDER, 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long sourceId() {
+ return sourceId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelOptTable getTable() {
+ return tbl;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RelWriter explainTerms(RelWriter pw) {
+ return super.explainTerms(pw)
+ .item("index", idxName)
+ .item("table", tbl.getQualifiedName())
+ .itemIf("sourceId", sourceId, sourceId != -1L);
+ }
+
+ /** {@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 IgniteIndexCount(sourceId, cluster, traitSet, tbl, idxName);
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteRel clone(long srcId) {
+ return new IgniteIndexCount(srcId, getCluster(), traitSet, tbl, idxName);
+ }
+}
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 56ebc3fea35..fc5084507de 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
@@ -69,6 +69,11 @@ public interface IgniteRelVisitor<T> {
*/
T visit(IgniteIndexScan rel);
+ /**
+ * See {@link IgniteRelVisitor#visit(IgniteRel)}
+ */
+ T visit(IgniteIndexCount rel);
+
/**
* See {@link IgniteRelVisitor#visit(IgniteRel)}
*/
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
index 0668173e39c..6291a5433b6 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
@@ -95,7 +95,7 @@ public class IgniteTableScan extends ProjectableFilterableTableScan implements S
* @param cond Filters.
* @param requiredColunms Participating colunms.
*/
- public IgniteTableScan(
+ private IgniteTableScan(
long sourceId,
RelOptCluster cluster,
RelTraitSet traits,
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java
new file mode 100644
index 00000000000..475637dfac3
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java
@@ -0,0 +1,108 @@
+/*
+ * 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 java.util.Collections;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelDistribution;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
+import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
+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.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.trait.RewindabilityTrait;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.immutables.value.Value;
+
+/** Tries to optimize 'COUNT(*)' to use number of index records. */
+@Value.Enclosing
+public class IndexCountRule extends RelRule<IndexCountRule.Config> {
+ /** */
+ public static final IndexCountRule INSTANCE = Config.DEFAULT.toRule();
+
+ /** Ctor. */
+ private IndexCountRule(IndexCountRule.Config cfg) {
+ super(cfg);
+ }
+
+ /** */
+ @Override public void onMatch(RelOptRuleCall call) {
+ LogicalAggregate aggr = call.rel(0);
+ IgniteLogicalTableScan scan = call.rel(1);
+ IgniteTable table = scan.getTable().unwrap(IgniteTable.class);
+ IgniteIndex idx = table.getIndex(QueryUtils.PRIMARY_KEY_INDEX);
+
+ if (
+ idx == null ||
+ table.isIndexRebuildInProgress() ||
+ scan.condition() != null ||
+ aggr.getGroupCount() > 0 ||
+ aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||
+ !a.getArgList().isEmpty() || a.hasFilter())
+ )
+ return;
+
+ RelTraitSet idxTraits = aggr.getTraitSet()
+ .replace(IgniteConvention.INSTANCE)
+ .replace(table.distribution().getType() == RelDistribution.Type.HASH_DISTRIBUTED ?
+ IgniteDistributions.random() : table.distribution())
+ .replace(RewindabilityTrait.REWINDABLE);
+
+ IgniteIndexCount idxCnt = new IgniteIndexCount(
+ scan.getCluster(),
+ idxTraits,
+ scan.getTable(),
+ idx.name()
+ );
+
+ RelBuilder b = call.builder();
+
+ // Also cast DECIMAL of SUM0 to BIGINT(Long) of COUNT().
+ call.transformTo(b.push(idxCnt)
+ .aggregate(b.groupKey(), Collections.nCopies(aggr.getAggCallList().size(),
+ b.aggregateCall(SqlStdOperatorTable.SUM0, b.field(0))))
+ .project(Commons.transform(Ord.zip(b.fields()),
+ f -> b.cast(f.e, aggr.getRowType().getFieldList().get(f.i).getType().getSqlTypeName())))
+ .build()
+ );
+ }
+
+ /** The rule config. */
+ @Value.Immutable
+ public interface Config extends RelRule.Config {
+ /** */
+ IndexCountRule.Config DEFAULT = ImmutableIndexCountRule.Config.of()
+ .withDescription("IndexCountRule")
+ .withOperandSupplier(r -> r.operand(LogicalAggregate.class)
+ .oneInput(i -> i.operand(IgniteLogicalTableScan.class).anyInputs()));
+
+ /** {@inheritDoc} */
+ @Override default IndexCountRule toRule() {
+ return new IndexCountRule(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 6ebc8e8eb08..59eecbbab34 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
@@ -28,7 +28,10 @@ import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.IgniteCheckedException;
+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.processors.query.calcite.exec.ExecutionContext;
import org.apache.ignite.internal.processors.query.calcite.exec.IndexScan;
@@ -37,6 +40,8 @@ import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLog
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;
import org.jetbrains.annotations.Nullable;
/**
@@ -50,13 +55,13 @@ public class CacheIndexImpl implements IgniteIndex {
private final String idxName;
/** */
- private final Index idx;
+ private final @Nullable Index idx;
/** */
private final IgniteCacheTable tbl;
/** */
- public CacheIndexImpl(RelCollation collation, String name, Index idx, IgniteCacheTable tbl) {
+ public CacheIndexImpl(RelCollation collation, String name, @Nullable Index idx, IgniteCacheTable tbl) {
this.collation = collation;
idxName = name;
this.idx = idx;
@@ -109,6 +114,28 @@ public class CacheIndexImpl implements IgniteIndex {
return Collections.emptyList();
}
+ /** {@inheritDoc} */
+ @Override public long count(ExecutionContext<?> ectx, ColocationGroup grp) {
+ long cnt = 0;
+
+ if (idx != null && grp.nodeIds().contains(ectx.localNodeId())) {
+ IndexingQueryFilter filter = new IndexingQueryFilterImpl(tbl.descriptor().cacheContext().kernalContext(),
+ ectx.topologyVersion(), grp.partitions(ectx.localNodeId()));
+
+ InlineIndex iidx = idx.unwrap(InlineIndex.class);
+
+ try {
+ for (int i = 0; i < iidx.segmentsCount(); ++i)
+ cnt += iidx.count(i, new IndexQueryContext(filter, null, ectx.mvccSnapshot()));
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteException("Unable to count index records.", e);
+ }
+ }
+
+ return cnt;
+ }
+
/** {@inheritDoc} */
@Override public IndexConditions toIndexCondition(
RelOptCluster cluster,
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 0d29208da17..5753302dbf0 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
@@ -86,4 +86,13 @@ public interface IgniteIndex {
Function<Row, Row> rowTransformer,
@Nullable ImmutableBitSet requiredColumns
);
+
+ /**
+ * Calculates index records number.
+ *
+ * @param ectx Execution context.
+ * @param grp Colocation group.
+ * @return Index records number for {@code group}.
+ */
+ public long count(ExecutionContext<?> ectx, ColocationGroup grp);
}
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 bf83d9ca4f6..050678107cc 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
@@ -97,6 +97,11 @@ public class SystemViewIndexImpl implements IgniteIndex {
);
}
+ /** {@inheritDoc} */
+ @Override public long count(ExecutionContext<?> ectx, ColocationGroup grp) {
+ return tbl.descriptor().systemView().size();
+ }
+
/** {@inheritDoc} */
@Override public IndexConditions toIndexCondition(
RelOptCluster cluster,
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteStdSqlOperatorTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteStdSqlOperatorTable.java
index 3b19f4665ca..c1e8d900b4a 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteStdSqlOperatorTable.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteStdSqlOperatorTable.java
@@ -77,6 +77,7 @@ public class IgniteStdSqlOperatorTable extends ReflectiveSqlOperatorTable {
// Aggregates.
register(SqlStdOperatorTable.COUNT);
register(SqlStdOperatorTable.SUM);
+ register(SqlStdOperatorTable.SUM0);
register(SqlStdOperatorTable.AVG);
register(SqlStdOperatorTable.MIN);
register(SqlStdOperatorTable.MAX);
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 4a9e40ce034..ee6f7fd5765 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
@@ -37,6 +37,8 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.CollectNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.IndexSpoolNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.ProjectNode;
@@ -45,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.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
@@ -64,10 +67,28 @@ import static org.apache.ignite.internal.processors.query.calcite.CalciteQueryPr
*/
public class LogicalRelImplementorTest extends GridCommonAbstractTest {
/** */
- @Test
- public void testIndexScanRewriter() {
- IgniteTypeFactory tf = Commons.typeFactory();
+ private LogicalRelImplementor<Object[]> relImplementor;
+
+ /** */
+ private RelOptCluster cluster;
+
+ /** */
+ private ScanAwareTable tbl;
+
+ /** */
+ private BaseQueryContext qctx;
+
+ /** */
+ private RexBuilder rexBuilder;
+
+ /** */
+ private IgniteTypeFactory tf;
+
+ /** */
+ @Override protected void beforeTest() throws Exception {
+ super.beforeTest();
+ tf = Commons.typeFactory();
RelDataTypeFactory.Builder b = new RelDataTypeFactory.Builder(tf);
RelDataType sqlTypeInt = tf.createSqlType(SqlTypeName.INTEGER);
@@ -80,14 +101,12 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
RelDataType rowType = b.build();
- ScanAwareTable tbl = new ScanAwareTable(rowType);
-
- tbl.addIndex("IDX", 2);
+ tbl = new ScanAwareTable(rowType);
IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
publicSchema.addTable("TBL", tbl);
- BaseQueryContext qctx = BaseQueryContext.builder()
+ qctx = BaseQueryContext.builder()
.frameworkConfig(
newConfigBuilder(FRAMEWORK_CONFIG)
.defaultSchema(createRootSchema(false).add(publicSchema.getName(), publicSchema))
@@ -114,7 +133,7 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
}
};
- LogicalRelImplementor<Object[]> relImplementor = new LogicalRelImplementor<>(
+ relImplementor = new LogicalRelImplementor<>(
ectx,
null,
null,
@@ -122,9 +141,52 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
null
);
- // Construct relational operator corresponding to SQL: "SELECT val, id, id + 1 FROM TBL WHERE id = 1"
- RelOptCluster cluster = Commons.emptyCluster();
- RexBuilder rexBuilder = cluster.getRexBuilder();
+ cluster = Commons.emptyCluster();
+
+ rexBuilder = cluster.getRexBuilder();
+ }
+
+ /**
+ * Tests IndexCount execution plan is changed to Collect/Scan when index is unavailable.
+ */
+ @Test
+ public void testIndexCountRewriter() {
+ IgniteIndexCount idxCnt = new IgniteIndexCount(cluster, cluster.traitSet(),
+ qctx.catalogReader().getTable(F.asList("PUBLIC", "TBL")), QueryUtils.PRIMARY_KEY_INDEX);
+
+ checkCollectNode(relImplementor.visit(idxCnt));
+
+ tbl.addIndex(QueryUtils.PRIMARY_KEY_INDEX, 2);
+
+ Node<?> node = relImplementor.visit(idxCnt);
+
+ assertTrue(node instanceof ScanNode);
+ assertNull(node.sources());
+ assertEquals(node.rowType(),
+ tf.createStructType(F.asList(tf.createSqlType(SqlTypeName.BIGINT)), F.asList("COUNT")));
+
+ tbl.markIndexRebuildInProgress(true);
+
+ checkCollectNode(relImplementor.visit(idxCnt));
+ }
+
+ /** */
+ private void checkCollectNode(Node<Object[]> node) {
+ assertTrue(node instanceof CollectNode);
+ assertTrue(node.sources() != null && node.sources().size() == 1);
+ assertTrue(node.sources().get(0) instanceof ScanNode);
+ assertNull(node.sources().get(0).sources());
+ assertEquals(tbl.getRowType(tf), node.sources().get(0).rowType());
+ }
+
+ /** */
+ @Test
+ public void testIndexScanRewriter() {
+ tbl.addIndex(QueryUtils.PRIMARY_KEY_INDEX, 2);
+
+ RelDataType rowType = tbl.getRowType(tf);
+ RelDataType sqlTypeInt = rowType.getFieldList().get(2).getType();
+ RelDataType sqlTypeVarchar = rowType.getFieldList().get(3).getType();
// Projects, filters and required columns.
List<RexNode> project = F.asList(
@@ -143,7 +205,7 @@ public class LogicalRelImplementorTest extends GridCommonAbstractTest {
ImmutableBitSet requiredColumns = ImmutableBitSet.of(2, 3);
// Collations.
- RelCollation idxCollation = tbl.getIndex("IDX").collation();
+ RelCollation idxCollation = tbl.getIndex(QueryUtils.PRIMARY_KEY_INDEX).collation();
RelCollation colCollation = idxCollation.apply(Mappings.target(requiredColumns.asList(),
tbl.getRowType(tf).getFieldCount()));
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 d55b4633c98..6e8dd456fb4 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
@@ -28,6 +28,7 @@ import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.cache.query.FieldsQueryCursor;
import org.apache.ignite.cache.query.QueryCursor;
@@ -58,6 +59,9 @@ import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
*/
@WithSystemProperty(key = "calcite.debug", value = "false")
public class AbstractBasicIntegrationTest extends GridCommonAbstractTest {
+ /** */
+ protected static final String TABLE_NAME = "person";
+
/** */
protected static IgniteEx client;
@@ -142,11 +146,17 @@ public class AbstractBasicIntegrationTest extends GridCommonAbstractTest {
/** */
protected IgniteCache<Integer, Employer> createAndPopulateTable() {
+ return createAndPopulateTable(2, CacheMode.PARTITIONED);
+ }
+
+ /** */
+ protected IgniteCache<Integer, Employer> createAndPopulateTable(int backups, CacheMode cacheMode) {
IgniteCache<Integer, Employer> person = client.getOrCreateCache(new CacheConfiguration<Integer, Employer>()
- .setName("person")
+ .setName(TABLE_NAME)
.setSqlSchema("PUBLIC")
- .setQueryEntities(F.asList(new QueryEntity(Integer.class, Employer.class).setTableName("person")))
- .setBackups(2)
+ .setQueryEntities(F.asList(new QueryEntity(Integer.class, Employer.class).setTableName(TABLE_NAME)))
+ .setCacheMode(cacheMode)
+ .setBackups(backups)
);
int idx = 0;
@@ -237,6 +247,11 @@ public class AbstractBasicIntegrationTest extends GridCommonAbstractTest {
return delegate.scan(execCtx, grp, filters, lowerIdxConditions, upperIdxConditions, rowTransformer,
requiredColumns);
}
+
+ /** {@inheritDoc} */
+ @Override public long count(ExecutionContext<?> ectx, ColocationGroup grp) {
+ return delegate.count(ectx, grp);
+ }
}
/** */
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 7532c2fa7e1..105ea61324c 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
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.query.calcite.integration;
import java.util.List;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;
@@ -30,7 +31,23 @@ import org.junit.Test;
public class AggregatesIntegrationTest extends AbstractBasicIntegrationTest {
/** */
@Test
- public void countOfNonNumericField() {
+ public void testCountWithBackupsAndCacheModes() {
+ for (int b = 0; b < 2; ++b) {
+ createAndPopulateTable(b, CacheMode.PARTITIONED);
+
+ assertQuery("select count(*) from person").returns(5L).check();
+
+ client.destroyCache(TABLE_NAME);
+ }
+
+ createAndPopulateTable(0, CacheMode.REPLICATED);
+
+ assertQuery("select count(*) from person").returns(5L).check();
+ }
+
+ /** */
+ @Test
+ public void testCountOfNonNumericField() {
createAndPopulateTable();
assertQuery("select count(name) from person").returns(4L).check();
@@ -38,6 +55,9 @@ public class AggregatesIntegrationTest extends AbstractBasicIntegrationTest {
assertQuery("select count(1) from person").returns(5L).check();
assertQuery("select count(null) from person").returns(0L).check();
+ assertQuery("select count(DISTINCT name) from person").returns(3L).check();
+ assertQuery("select count(DISTINCT 1) from person").returns(1L).check();
+
assertQuery("select count(*) from person where salary < 0").returns(0L).check();
assertQuery("select count(*) from person where salary < 0 and salary > 0").returns(0L).check();
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 4d6e98604d6..39f32840ec7 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
@@ -19,15 +19,21 @@ package org.apache.ignite.internal.processors.query.calcite.integration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.cluster.ClusterState;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.cache.query.index.IndexProcessor;
import org.apache.ignite.internal.managers.indexing.IndexesRebuildTask;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
+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.schema.IgniteCacheTable;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.processors.query.schema.IndexRebuildCancelToken;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorClosure;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
@@ -196,6 +202,53 @@ public class IndexRebuildIntegrationTest extends AbstractBasicIntegrationTest {
checkRebuildIndexQuery(grid(1), checker, checker);
}
+ /**
+ * Test IndexCount is disabled at index rebuilding.
+ */
+ @Test
+ public void testIndexCountAtUnavailableIndex() throws IgniteCheckedException {
+ int records = 50;
+ int iterations = 500;
+
+ CalciteQueryProcessor srvEngine = Commons.lookupComponent(grid(0).context(), CalciteQueryProcessor.class);
+
+ for (int backups = -1; backups < 3; ++backups) {
+ String ddl = "CREATE TABLE tbl3 (id INT PRIMARY KEY, val VARCHAR, val2 VARCHAR) WITH ";
+
+ ddl += backups < 0 ? "TEMPLATE=REPLICATED" : "TEMPLATE=PARTITIONED,backups=" + backups;
+
+ executeSql(ddl);
+
+ for (int i = 0; i < records; i++)
+ executeSql("INSERT INTO tbl3 VALUES (?, ?, ?)", i, "val" + i, "val" + i);
+
+ IgniteCacheTable tbl3 = (IgniteCacheTable)srvEngine.schemaHolder().schema("PUBLIC").getTable("TBL3");
+
+ AtomicBoolean stop = new AtomicBoolean();
+
+ IgniteInternalFuture<?> fut = GridTestUtils.runAsync(() -> {
+ boolean lever = true;
+
+ while (!stop.get())
+ tbl3.markIndexRebuildInProgress(lever = !lever);
+ });
+
+ try {
+ for (int i = 0; i < iterations; ++i)
+ assertQuery("select COUNT(*) from tbl3").returns((long)records).check();
+ }
+ finally {
+ stop.set(true);
+
+ tbl3.markIndexRebuildInProgress(false);
+
+ executeSql("DROP TABLE tbl3");
+ }
+
+ fut.get();
+ }
+ }
+
/** */
@Test
public void testRebuildOnRemoteNodeCorrelated() throws Exception {
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractAggregatePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractAggregatePlannerTest.java
index dde5390e631..7d87ca5eaa4 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractAggregatePlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractAggregatePlannerTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.query.calcite.planner;
import java.util.Arrays;
+import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
import org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext;
@@ -33,42 +34,28 @@ import org.jetbrains.annotations.NotNull;
@SuppressWarnings({"TypeMayBeWeakened"})
public class AbstractAggregatePlannerTest extends AbstractPlannerTest {
/**
- * @return REPLICATED test table (ID, VAL0, VAL1, GRP0, GRP1)
+ * @return REPLICATED test table (ID, VAL0, VAL1, GRP0, GRP1) with given defined distribution.
*/
- @NotNull protected TestTable createBroadcastTable() {
- IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
- TestTable tbl = new TestTable(
- new RelDataTypeFactory.Builder(f)
- .add("ID", f.createJavaType(Integer.class))
- .add("VAL0", f.createJavaType(Integer.class))
- .add("VAL1", f.createJavaType(Integer.class))
- .add("GRP0", f.createJavaType(Integer.class))
- .add("GRP1", f.createJavaType(Integer.class))
- .build()) {
-
+ @NotNull protected TestTable createTable(IgniteDistribution distr) {
+ return new TestTable(createType()) {
@Override public IgniteDistribution distribution() {
- return IgniteDistributions.broadcast();
+ return distr;
}
};
- return tbl;
+ }
+
+ /**
+ * @return REPLICATED test table (ID, VAL0, VAL1, GRP0, GRP1)
+ */
+ @NotNull protected TestTable createBroadcastTable() {
+ return createTable(IgniteDistributions.broadcast());
}
/**
* @return PARTITIONED test table (ID, VAL0, VAL1, GRP0, GRP1)
*/
@NotNull protected TestTable createAffinityTable() {
- IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
- return new TestTable(
- new RelDataTypeFactory.Builder(f)
- .add("ID", f.createJavaType(Integer.class))
- .add("VAL0", f.createJavaType(Integer.class))
- .add("VAL1", f.createJavaType(Integer.class))
- .add("GRP0", f.createJavaType(Integer.class))
- .add("GRP1", f.createJavaType(Integer.class))
- .build()) {
-
+ return new TestTable(createType()) {
@Override public ColocationGroup colocationGroup(MappingQueryContext ctx) {
return ColocationGroup.forAssignments(Arrays.asList(
select(nodes, 0, 1),
@@ -84,4 +71,19 @@ public class AbstractAggregatePlannerTest extends AbstractPlannerTest {
}
};
}
+
+ /**
+ * @return Test table rel type.
+ */
+ private static RelDataType createType() {
+ IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+ return new RelDataTypeFactory.Builder(f)
+ .add("ID", f.createJavaType(Integer.class))
+ .add("VAL0", f.createJavaType(Integer.class))
+ .add("VAL1", f.createJavaType(Integer.class))
+ .add("GRP0", f.createJavaType(Integer.class))
+ .add("GRP1", f.createJavaType(Integer.class))
+ .build();
+ }
}
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 96a83aee98d..403d7465876 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
@@ -18,12 +18,18 @@
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.type.RelDataTypeFactory;
import org.apache.calcite.sql.fun.SqlAvgAggFunction;
import org.apache.calcite.sql.fun.SqlCountAggFunction;
+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.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteMapHashAggregate;
import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteReduceHashAggregate;
@@ -42,6 +48,77 @@ import org.junit.Test;
*/
@SuppressWarnings({"TooBroadScope", "FieldCanBeLocal", "TypeMayBeWeakened"})
public class HashAggregatePlannerTest extends AbstractAggregatePlannerTest {
+ /**
+ * Tests COUNT(...) plan with and without IndexCount optimization.
+ */
+ @Test
+ 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) {
+ TestTable tbl = createTable(distr);
+ publicSchema.addTable("TEST", tbl);
+
+ assertNoIndexCount("SELECT COUNT(*) FROM TEST", publicSchema);
+
+ tbl.addIndex(QueryUtils.PRIMARY_KEY_INDEX, 0);
+
+ assertIndexCount("SELECT COUNT(*) FROM TEST", publicSchema);
+ assertIndexCount("SELECT COUNT(1) FROM TEST", publicSchema);
+ assertIndexCount("SELECT COUNT(*) FROM (SELECT * FROM TEST)", publicSchema);
+ assertIndexCount("SELECT COUNT(1) FROM (SELECT * FROM TEST)", publicSchema);
+
+ assertIndexCount("SELECT COUNT(*), COUNT(*), COUNT(1) FROM TEST", publicSchema);
+
+ // Count on certain fields can't be optimized. Nulls are count included.
+ assertNoIndexCount("SELECT COUNT(VAL0) FROM TEST", publicSchema);
+ assertNoIndexCount("SELECT COUNT(DISTINCT VAL0) FROM TEST", publicSchema);
+
+ assertNoIndexCount("SELECT COUNT(*), COUNT(VAL0) FROM TEST", publicSchema);
+
+ assertNoIndexCount("SELECT COUNT(1), COUNT(VAL0) FROM TEST", publicSchema);
+ assertNoIndexCount("SELECT COUNT(DISTINCT 1), COUNT(VAL0) FROM TEST", publicSchema);
+
+ assertNoIndexCount("SELECT COUNT(*) FILTER (WHERE VAL0>1) FROM TEST", publicSchema);
+
+ // IndexCount can't be used with a condition, groups, other aggregates or distincts.
+ assertNoIndexCount("SELECT COUNT(*) FROM TEST WHERE VAL0>1", publicSchema);
+ assertNoIndexCount("SELECT COUNT(1) FROM TEST WHERE VAL0>1", publicSchema);
+
+ assertNoIndexCount("SELECT COUNT(*), SUM(VAL0) FROM TEST", publicSchema);
+
+ assertNoIndexCount("SELECT VAL0, COUNT(*) FROM TEST GROUP BY VAL0", publicSchema);
+
+ assertNoIndexCount("SELECT COUNT(*) FROM TEST GROUP BY VAL0", publicSchema);
+
+ assertNoIndexCount("SELECT COUNT(*) FILTER (WHERE VAL0>1) FROM TEST", publicSchema);
+
+ publicSchema.addTable("TEST2", createBroadcastTable());
+
+ assertNoIndexCount("SELECT COUNT(*) FROM (SELECT T1.VAL0, T2.VAL1 FROM TEST T1, " +
+ "TEST2 T2 WHERE T1.GRP0 = T2.GRP0)", publicSchema);
+
+ publicSchema.removeTable("TEST");
+ }
+ }
+
+ /** */
+ private void assertIndexCount(String sql, IgniteSchema schema) throws Exception {
+ assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
+ .and(nodeOrAnyChild(isInstanceOf(IgniteIndexCount.class)))));
+ }
+
+ /** */
+ private void assertNoIndexCount(String sql, IgniteSchema publicSchema) throws Exception {
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteIndexCount.class)).negate());
+ }
+
/**
* @throws Exception If failed.
*/
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 da86c456ac6..114322ac88f 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
@@ -19,14 +19,21 @@ package org.apache.ignite.internal.processors.query.calcite.planner;
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.IgniteIndexCount;
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.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.agg.IgniteColocatedHashAggregate;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
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.SUM0;
+
/**
* Planner test for index rebuild.
*/
@@ -42,7 +49,7 @@ public class IndexRebuildPlannerTest extends AbstractPlannerTest {
super.setup();
tbl = createTable("TBL", 100, IgniteDistributions.single(), "ID", Integer.class, "VAL", String.class)
- .addIndex("IDX", 0);
+ .addIndex(QueryUtils.PRIMARY_KEY_INDEX, 0);
publicSchema = createSchema(tbl);
}
@@ -63,6 +70,28 @@ public class IndexRebuildPlannerTest extends AbstractPlannerTest {
assertPlan(sql, publicSchema, isInstanceOf(IgniteIndexScan.class));
}
+ /** Test IndexCount is disabled when index is unavailable. */
+ @Test
+ public void testIndexCountAtIndexRebuild() throws Exception {
+ String sql = "SELECT COUNT(*) FROM TBL";
+
+ assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == SUM0).count() == 1)
+ .and(nodeOrAnyChild(isInstanceOf(IgniteIndexCount.class)))));
+
+ tbl.markIndexRebuildInProgress(true);
+
+ assertPlan(sql, publicSchema, isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == COUNT).count() == 1)
+ .and(nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))));
+
+ tbl.markIndexRebuildInProgress(false);
+
+ assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(IgniteAggregate.class)
+ .and(a -> a.getAggCallList().stream().filter(agg -> agg.getAggregation().getKind() == SUM0).count() == 1)
+ .and(nodeOrAnyChild(isInstanceOf(IgniteIndexCount.class)))));
+ }
+
/** */
@Test
public void testConcurrentIndexRebuildStateChange() throws Exception {
@@ -90,4 +119,32 @@ public class IndexRebuildPlannerTest extends AbstractPlannerTest {
fut.get();
}
+
+ /**
+ * Test IndexCount is disabled when index becomes unavailable.
+ */
+ @Test
+ public void testIndexCountAtConcurrentIndexRebuild() throws Exception {
+ String sql = "SELECT COUNT(*) FROM TBL";
+
+ AtomicBoolean stop = new AtomicBoolean();
+
+ IgniteInternalFuture<?> fut = GridTestUtils.runAsync(() -> {
+ boolean lever = true;
+
+ while (!stop.get())
+ tbl.markIndexRebuildInProgress(lever = !lever);
+ });
+
+ try {
+ for (int i = 0; i < 1000; i++)
+ assertPlan(sql, publicSchema, nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+ .and(input(isInstanceOf(IgniteIndexCount.class)).or(input(isInstanceOf(IgniteTableScan.class))))));
+ }
+ finally {
+ stop.set(true);
+ }
+
+ fut.get();
+ }
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
index ae338ce608e..3e0f38aeb30 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
@@ -100,7 +100,7 @@ public class LimitOffsetPlannerTest extends AbstractPlannerTest {
.and(s -> doubleFromRex(s.fetch, -1) == 5.0)
.and(s -> s.offset == null))))));
- // No special liited sort required if LIMIT is not set.
+ // No special limited sort required if LIMIT is not set.
assertPlan("SELECT * FROM TEST ORDER BY ID OFFSET 10", publicSchema,
isInstanceOf(IgniteLimit.class)
.and(input(isInstanceOf(IgniteExchange.class)
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
index ccef0d25796..26fd26d26de 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
@@ -229,7 +229,7 @@ public class TestTable implements IgniteCacheTable {
/** {@inheritDoc} */
@Override public void removeIndex(String idxName) {
- throw new AssertionError();
+ indexes.remove(idxName);
}
/** {@inheritDoc} */