You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ignite.apache.org by GitBox <gi...@apache.org> on 2022/06/17 11:18:45 UTC

[GitHub] [ignite] alex-plekhanov commented on a diff in pull request #10050: IGNITE-16920 : Calcite Engine. COUNT() lacks trivial optimization. : fix accumulator

alex-plekhanov commented on code in PR #10050:
URL: https://github.com/apache/ignite/pull/10050#discussion_r899793779


##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||

Review Comment:
   How project prevents to calculate count?



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);

Review Comment:
   Perhaps it's redundant, primart key should always exist.



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||
+                aggr.getGroupCount() > 0 ||
+                table.isIndexRebuildInProgress() ||
+                aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||
+                    !a.getArgList().isEmpty())
+        )
+            return;
+
+        RelTraitSet idxTraits = aggr.getTraitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replace(IgniteDistributions.random())
+            .replace(RewindabilityTrait.REWINDABLE);
+
+        IgniteIndexCount idxCnt = new IgniteIndexCount(
+            scan.getCluster(),
+            idxTraits,
+            scan.getTable(),
+            idx.name(),
+            aggr.getRowType()
+        );
+
+        AggregateCall idxSumAggCall = AggregateCall.create(
+            SqlStdOperatorTable.SUM0,
+            false,
+            false,
+            false,
+            ImmutableIntList.of(0),
+            -1,
+            null,
+            RelCollations.EMPTY,
+            0,
+            idxCnt,
+            null,
+            null);
+
+        List<AggregateCall> indCntSumFunLst = Stream.generate(() -> idxSumAggCall).limit(aggr.getAggCallList().size())
+            .collect(Collectors.toList());
+
+        LogicalAggregate newRel = new LogicalAggregate(
+            aggr.getCluster(),
+            aggr.getTraitSet(),
+            Collections.emptyList(),
+            idxCnt,
+            aggr.getGroupSet(),
+            aggr.getGroupSets(),
+            indCntSumFunLst
+        );
+
+        // SUM0/DECIMAL to COUNT()/Long converter.
+        List<RexNode> proj = new ArrayList<>();
+        RexBuilder rexBuilder = scan.getCluster().getRexBuilder();
+
+        for (int i = 0; i < aggr.getAggCallList().size(); ++i)
+            proj.add(rexBuilder.makeCast(aggr.getAggCallList().get(i).getType(), rexBuilder.makeInputRef(newRel, i)));
+
+        IgniteProject castToLongNode = new IgniteProject(
+            newRel.getCluster(),
+            aggr.getTraitSet().replace(IgniteConvention.INSTANCE),
+            newRel,
+            proj, aggr.getRowType());
+
+        call.transformTo(castToLongNode, ImmutableMap.of(castToLongNode, aggr));

Review Comment:
   Second parameter here is not required since only one possible transformation is provided.



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||
+                aggr.getGroupCount() > 0 ||
+                table.isIndexRebuildInProgress() ||
+                aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||

Review Comment:
   We should also exclude COUNT(DISTINCT)



##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java:
##########
@@ -42,6 +46,77 @@
  */
 @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");
+        TestTable tbl = createBroadcastTable();
+        publicSchema.addTable("TEST", tbl);
+
+        assertPlan("SELECT COUNT(*) FROM TEST", publicSchema,
+            hasChildThat(isInstanceOf(IgniteIndexCount.class)).negate());
+
+        // Check with 'primary' index.
+        tbl.addIndex(QueryUtils.PRIMARY_KEY_INDEX, 0);
+
+        assertIndexCount("SELECT COUNT(*) FROM TEST", publicSchema);
+
+        // Check with other index.
+        tbl.removeIndex(QueryUtils.PRIMARY_KEY_INDEX);
+
+        tbl.addIndex("idx", 1);
+
+        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(*), COUNT(VAL0) FROM TEST", publicSchema);
+
+        assertNoIndexCount("SELECT COUNT(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);
+    }
+
+    /** */
+    private void assertIndexCount(String sql, IgniteSchema publicSchema) throws Exception {
+        assertPlan(
+            sql,
+            publicSchema,
+            nodeOrAnyChild(isInstanceOf(IgniteColocatedHashAggregate.class)
+                .and(input(isInstanceOf(IgniteExchange.class)

Review Comment:
   There shouldn't be IgniteExchange before IgniteColocated*Aggregate (this type of aggregate assumes that distribution is already correct)



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/externalize/RelJson.java:
##########
@@ -948,6 +948,7 @@ private Object toJson(SqlOperator operator) {
         map.put("name", operator.getName());
         map.put("kind", toJson(operator.kind));
         map.put("syntax", toJson(operator.getSyntax()));
+        map.put("class", toJson(operator.getClass().getName()));

Review Comment:
   Register SUM0 in IgniteStdSqlOperatorTable instead



##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java:
##########
@@ -42,6 +46,77 @@
  */
 @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");
+        TestTable tbl = createBroadcastTable();

Review Comment:
   Other distributions should be tested too



##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashAggregatePlannerTest.java:
##########
@@ -42,6 +46,77 @@
  */
 @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");
+        TestTable tbl = createBroadcastTable();
+        publicSchema.addTable("TEST", tbl);
+
+        assertPlan("SELECT COUNT(*) FROM TEST", publicSchema,
+            hasChildThat(isInstanceOf(IgniteIndexCount.class)).negate());
+
+        // Check with 'primary' index.
+        tbl.addIndex(QueryUtils.PRIMARY_KEY_INDEX, 0);
+
+        assertIndexCount("SELECT COUNT(*) FROM TEST", publicSchema);
+
+        // Check with other index.
+        tbl.removeIndex(QueryUtils.PRIMARY_KEY_INDEX);

Review Comment:
   Not sure we should test such a case



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java:
##########
@@ -406,6 +420,54 @@ public LogicalRelImplementor(
         }
     }
 
+    /** {@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()) {
+            CacheTableDescriptor descr = rel.getTable().unwrap(CacheTableDescriptor.class);
+            GridCacheContext<?, ?> cctx = descr.cacheContext();
+            GridKernalContext kctx = cctx.kernalContext();
+
+            IndexName idxName = new IndexName(cctx.name(), kctx.query().schemaName(cctx),
+                descr.typeDescription().tableName(), idx.name());
+
+            IndexQueryContext qryCtx = null;
+
+            if (descr.cacheInfo().config().getBackups() > 0) {
+                IndexingQueryFilterImpl filter = new IndexingQueryFilterImpl(kctx, AffinityTopologyVersion.NONE, null);
+
+                qryCtx = new IndexQueryContext(filter, null, null);
+            }
+
+            return new IndexCountNode<>(kctx.indexProcessor().index(idxName).unwrap(InlineIndex.class), ctx, qryCtx);
+        }
+        else {
+            // Index is unavailable. Work with table scan.
+            RelNode relInput = new IgniteTableScan(rel.getCluster(), rel.getTraitSet(), rel.getTable());

Review Comment:
   Not sure it's a good idea to create RelNodes here, perhaps we should create execution nodes directly. Perhaps we can modify CollectNode and reuse it to calculate size (instead of creating aggregate node).
   Also, there should be test for this logic (see LogicalRelImplementorTest)
   



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||
+                aggr.getGroupCount() > 0 ||
+                table.isIndexRebuildInProgress() ||
+                aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||
+                    !a.getArgList().isEmpty())
+        )
+            return;
+
+        RelTraitSet idxTraits = aggr.getTraitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replace(IgniteDistributions.random())

Review Comment:
   `table.distribution().getType() == RelDistribution.Type.HASH_DISTRIBUTED ? IgniteDistributions.random() : table.distribution()`



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexCount.java:
##########
@@ -0,0 +1,128 @@
+/*
+ * 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 static org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost.ROW_PASS_THROUGH_COST;
+
+/**
+ * Returns number of index records.
+ */
+public class IgniteIndexCount extends AbstractRelNode implements SourceAwareIgniteRel {
+    /** */
+    private final RelOptTable tbl;
+
+    /** */
+    private final String idxName;
+
+    /**
+     * Constructor for deserialization.
+     *
+     * @param input Serialized representation.
+     */
+    public IgniteIndexCount(RelInput input) {
+        super(input.getCluster(), input.getTraitSet());
+
+        idxName = input.getString("index");
+        tbl = input.getTable("table");
+        rowType = input.getRowType("type");
+    }
+
+    /**
+     * 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.
+     * @param type Data type.
+     */
+    public IgniteIndexCount(
+        RelOptCluster cluster,
+        RelTraitSet traits,
+        RelOptTable tbl,
+        String idxName,
+        RelDataType type
+    ) {
+        super(cluster, traits);
+
+        this.idxName = idxName;
+        this.tbl = tbl;
+        this.rowType = type;
+    }
+
+    /** */
+    public String indexName() {
+        return idxName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double estimateRowCount(RelMetadataQuery mq) {
+        // Requesting index count always produces just one record.
+        return 1.0d;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+        return planner.getCostFactory().makeCost(1.0, 1.0, tbl.getRowCount() * ROW_PASS_THROUGH_COST);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long sourceId() {
+        return -1L;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelOptTable getTable() {
+        return tbl;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelWriter explainTerms(RelWriter pw) {
+        return super.explainTerms(pw)
+            .item("index", idxName)
+            .item("type", rowType)

Review Comment:
   rowType is always the same, do we need to store it and pass to other nodes?



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||
+                aggr.getGroupCount() > 0 ||
+                table.isIndexRebuildInProgress() ||
+                aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||
+                    !a.getArgList().isEmpty())
+        )
+            return;
+
+        RelTraitSet idxTraits = aggr.getTraitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replace(IgniteDistributions.random())
+            .replace(RewindabilityTrait.REWINDABLE);
+
+        IgniteIndexCount idxCnt = new IgniteIndexCount(
+            scan.getCluster(),
+            idxTraits,
+            scan.getTable(),
+            idx.name(),
+            aggr.getRowType()
+        );
+
+        AggregateCall idxSumAggCall = AggregateCall.create(
+            SqlStdOperatorTable.SUM0,
+            false,
+            false,
+            false,
+            ImmutableIntList.of(0),
+            -1,
+            null,
+            RelCollations.EMPTY,
+            0,
+            idxCnt,
+            null,
+            null);
+
+        List<AggregateCall> indCntSumFunLst = Stream.generate(() -> idxSumAggCall).limit(aggr.getAggCallList().size())
+            .collect(Collectors.toList());

Review Comment:
   Collections.nCopies()



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexCount.java:
##########
@@ -0,0 +1,128 @@
+/*
+ * 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 static org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost.ROW_PASS_THROUGH_COST;
+
+/**
+ * Returns number of index records.
+ */
+public class IgniteIndexCount extends AbstractRelNode implements SourceAwareIgniteRel {
+    /** */
+    private final RelOptTable tbl;
+
+    /** */
+    private final String idxName;
+
+    /**
+     * Constructor for deserialization.
+     *
+     * @param input Serialized representation.
+     */
+    public IgniteIndexCount(RelInput input) {
+        super(input.getCluster(), input.getTraitSet());
+
+        idxName = input.getString("index");
+        tbl = input.getTable("table");
+        rowType = input.getRowType("type");
+    }
+
+    /**
+     * 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.
+     * @param type Data type.
+     */
+    public IgniteIndexCount(
+        RelOptCluster cluster,
+        RelTraitSet traits,
+        RelOptTable tbl,
+        String idxName,
+        RelDataType type
+    ) {
+        super(cluster, traits);
+
+        this.idxName = idxName;
+        this.tbl = tbl;
+        this.rowType = type;
+    }
+
+    /** */
+    public String indexName() {
+        return idxName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double estimateRowCount(RelMetadataQuery mq) {
+        // Requesting index count always produces just one record.
+        return 1.0d;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+        return planner.getCostFactory().makeCost(1.0, 1.0, tbl.getRowCount() * ROW_PASS_THROUGH_COST);

Review Comment:
   We don't use IO cost currently, so third parameter should be 0.
   IgniteIndexCount is not O(1), so CPU cost should depends on rows count, but with big divider. I propose something like rows count / 1000.



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||
+                aggr.getGroupCount() > 0 ||
+                table.isIndexRebuildInProgress() ||
+                aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||
+                    !a.getArgList().isEmpty())
+        )
+            return;
+
+        RelTraitSet idxTraits = aggr.getTraitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replace(IgniteDistributions.random())
+            .replace(RewindabilityTrait.REWINDABLE);
+
+        IgniteIndexCount idxCnt = new IgniteIndexCount(
+            scan.getCluster(),
+            idxTraits,
+            scan.getTable(),
+            idx.name(),
+            aggr.getRowType()

Review Comment:
   It's not correct, IgniteIndexCount produces single field output.



##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/IndexRebuildPlannerTest.java:
##########
@@ -63,6 +66,27 @@ public void testIndexRebuild() throws Exception {
         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(IgniteColocatedHashAggregate.class)

Review Comment:
   aggregate function should be checked



##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/IndexRebuildPlannerTest.java:
##########
@@ -63,6 +66,27 @@ public void testIndexRebuild() throws Exception {
         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(IgniteColocatedHashAggregate.class)
+            .and(input(isInstanceOf(IgniteExchange.class).and(input(isInstanceOf(IgniteIndexCount.class)))))));
+
+        tbl.markIndexRebuildInProgress(true);
+
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteColocatedHashAggregate.class)

Review Comment:
   aggregate function should be checked



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/IndexCountRule.java:
##########
@@ -0,0 +1,152 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.ImmutableIntList;
+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.IgniteProject;
+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.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.indexes().get(QueryUtils.PRIMARY_KEY_INDEX);
+
+        if (idx == null)
+            idx = table.indexes().values().stream().findFirst().orElse(null);
+
+        if (
+            idx == null ||
+                scan.condition() != null ||
+                scan.projects() != null ||
+                aggr.getGroupCount() > 0 ||
+                table.isIndexRebuildInProgress() ||
+                aggr.getAggCallList().stream().anyMatch(a -> a.getAggregation().getKind() != SqlKind.COUNT ||
+                    !a.getArgList().isEmpty())
+        )
+            return;
+
+        RelTraitSet idxTraits = aggr.getTraitSet()
+            .replace(IgniteConvention.INSTANCE)
+            .replace(IgniteDistributions.random())
+            .replace(RewindabilityTrait.REWINDABLE);
+
+        IgniteIndexCount idxCnt = new IgniteIndexCount(
+            scan.getCluster(),
+            idxTraits,
+            scan.getTable(),
+            idx.name(),
+            aggr.getRowType()
+        );
+
+        AggregateCall idxSumAggCall = AggregateCall.create(
+            SqlStdOperatorTable.SUM0,
+            false,
+            false,
+            false,
+            ImmutableIntList.of(0),
+            -1,
+            null,
+            RelCollations.EMPTY,
+            0,
+            idxCnt,
+            null,
+            null);
+
+        List<AggregateCall> indCntSumFunLst = Stream.generate(() -> idxSumAggCall).limit(aggr.getAggCallList().size())
+            .collect(Collectors.toList());
+
+        LogicalAggregate newRel = new LogicalAggregate(
+            aggr.getCluster(),
+            aggr.getTraitSet(),
+            Collections.emptyList(),
+            idxCnt,
+            aggr.getGroupSet(),
+            aggr.getGroupSets(),
+            indCntSumFunLst
+        );
+
+        // SUM0/DECIMAL to COUNT()/Long converter.
+        List<RexNode> proj = new ArrayList<>();
+        RexBuilder rexBuilder = scan.getCluster().getRexBuilder();
+
+        for (int i = 0; i < aggr.getAggCallList().size(); ++i)
+            proj.add(rexBuilder.makeCast(aggr.getAggCallList().get(i).getType(), rexBuilder.makeInputRef(newRel, i)));
+
+        IgniteProject castToLongNode = new IgniteProject(
+            newRel.getCluster(),
+            aggr.getTraitSet().replace(IgniteConvention.INSTANCE),
+            newRel,
+            proj, aggr.getRowType());

Review Comment:
   Can be simplified to something like 
   ```
           RelBuilder builder = call.builder();
   
           call.transformTo(builder.push(idxCnt)
               .aggregate(builder.groupKey(), Collections.nCopies(aggr.getAggCallList().size(), builder.aggregateCall(SqlStdOperatorTable.SUM0, builder.field(0))))
               .project(Commons.transform(Ord.zip(builder.fields()), f -> builder.cast(f.e, aggr.getRowType().getFieldList().get(f.i).getType().getSqlTypeName())))
               .build()
           );
   ```



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java:
##########
@@ -406,6 +420,54 @@ public LogicalRelImplementor(
         }
     }
 
+    /** {@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()) {
+            CacheTableDescriptor descr = rel.getTable().unwrap(CacheTableDescriptor.class);
+            GridCacheContext<?, ?> cctx = descr.cacheContext();
+            GridKernalContext kctx = cctx.kernalContext();

Review Comment:
   LogicalRelImplementor should not contain any core-module classes (cache contex, kernel context, inline index, query filter, etc). This class is common for Ignite 2 and Ignite 3.
   To get InlineIndex is enough to do idx.unwrap(InlineIndex.class)



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java:
##########
@@ -406,6 +420,54 @@ public LogicalRelImplementor(
         }
     }
 
+    /** {@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()) {
+            CacheTableDescriptor descr = rel.getTable().unwrap(CacheTableDescriptor.class);
+            GridCacheContext<?, ?> cctx = descr.cacheContext();
+            GridKernalContext kctx = cctx.kernalContext();
+
+            IndexName idxName = new IndexName(cctx.name(), kctx.query().schemaName(cctx),
+                descr.typeDescription().tableName(), idx.name());
+
+            IndexQueryContext qryCtx = null;
+
+            if (descr.cacheInfo().config().getBackups() > 0) {

Review Comment:
   It's not a correct logic. ColocationGroup filtering should be used here to filter certain partitions. See CacheIndexImpl.scan method.
   I propose to create a new method in IgniteIndex class (something like `scanCount()` or `countProvider()`) and return iterator here (like for IgniteIndex.scan), pass this iterator to ScanNode.
   Some integrations tests should be added with repicated and partitioned caches with different backups count.



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/IndexCountNode.java:
##########
@@ -0,0 +1,79 @@
+/*
+ * 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.rel;
+
+import org.apache.calcite.sql.type.SqlTypeName;
+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;
+
+/** Extracts number of index records. */
+public class IndexCountNode<Row> extends AbstractNode<Row> implements Downstream<Row> {

Review Comment:
   This node shouldn't implements `Downstream`. Perhaps `ScanNode` should be used instead of creating the new node.



##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexCount.java:
##########
@@ -0,0 +1,128 @@
+/*
+ * 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 static org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost.ROW_PASS_THROUGH_COST;
+
+/**
+ * Returns number of index records.
+ */
+public class IgniteIndexCount extends AbstractRelNode implements SourceAwareIgniteRel {
+    /** */
+    private final RelOptTable tbl;
+
+    /** */
+    private final String idxName;
+
+    /**
+     * Constructor for deserialization.
+     *
+     * @param input Serialized representation.
+     */
+    public IgniteIndexCount(RelInput input) {
+        super(input.getCluster(), input.getTraitSet());
+
+        idxName = input.getString("index");
+        tbl = input.getTable("table");
+        rowType = input.getRowType("type");
+    }
+
+    /**
+     * 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.
+     * @param type Data type.
+     */
+    public IgniteIndexCount(
+        RelOptCluster cluster,
+        RelTraitSet traits,
+        RelOptTable tbl,
+        String idxName,
+        RelDataType type
+    ) {
+        super(cluster, traits);
+
+        this.idxName = idxName;
+        this.tbl = tbl;
+        this.rowType = type;
+    }
+
+    /** */
+    public String indexName() {
+        return idxName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double estimateRowCount(RelMetadataQuery mq) {
+        // Requesting index count always produces just one record.
+        return 1.0d;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
+        return planner.getCostFactory().makeCost(1.0, 1.0, tbl.getRowCount() * ROW_PASS_THROUGH_COST);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long sourceId() {

Review Comment:
   Should be the same logic as for IgniteTableScan



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org