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 2021/12/21 15:28:03 UTC
[ignite] branch sql-calcite updated: IGNITE-16107 Fix projects and filters merge into scan - Fixes #9647.
This is an automated email from the ASF dual-hosted git repository.
alexpl pushed a commit to branch sql-calcite
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/sql-calcite by this push:
new 788a11c IGNITE-16107 Fix projects and filters merge into scan - Fixes #9647.
788a11c is described below
commit 788a11c35bdaed2f1175af966316b0892e83c39b
Author: Aleksey Plekhanov <pl...@gmail.com>
AuthorDate: Tue Dec 21 17:32:31 2021 +0300
IGNITE-16107 Fix projects and filters merge into scan - Fixes #9647.
Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
.../query/calcite/prepare/PlannerPhase.java | 48 ++--
.../rel/logical/IgniteLogicalIndexScan.java | 6 -
.../query/calcite/rule/FilterConverterRule.java | 20 +-
.../calcite/rule/logical/FilterScanMergeRule.java | 161 ++++++-------
.../calcite/rule/logical/LogicalOrToUnionRule.java | 125 ++++++++---
.../calcite/rule/logical/ProjectScanMergeRule.java | 106 +++++----
.../processors/query/calcite/util/RexUtils.java | 19 --
.../ServerStatisticsIntegrationTest.java | 2 +-
.../planner/ProjectFilterScanMergePlannerTest.java | 249 +++++++++++++++++++++
.../apache/ignite/testsuites/PlannerTestSuite.java | 2 +
10 files changed, 508 insertions(+), 230 deletions(-)
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 c386b4c7..893821a 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
@@ -73,10 +73,12 @@ public enum PlannerPhase {
HEP_DECORRELATE("Heuristic phase to decorrelate subqueries") {
/** {@inheritDoc} */
@Override public RuleSet getRules(PlanningContext ctx) {
- return RuleSets.ofList(
- CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
- CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
- CoreRules.JOIN_SUB_QUERY_TO_CORRELATE
+ return ctx.rules(
+ RuleSets.ofList(
+ CoreRules.FILTER_SUB_QUERY_TO_CORRELATE,
+ CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE,
+ CoreRules.JOIN_SUB_QUERY_TO_CORRELATE
+ )
);
}
@@ -90,13 +92,17 @@ public enum PlannerPhase {
HEP_FILTER_PUSH_DOWN("Heuristic phase to push down filters") {
/** {@inheritDoc} */
@Override public RuleSet getRules(PlanningContext ctx) {
- return RuleSets.ofList(
- CoreRules.FILTER_MERGE,
- CoreRules.FILTER_AGGREGATE_TRANSPOSE,
- CoreRules.FILTER_SET_OP_TRANSPOSE,
- CoreRules.JOIN_CONDITION_PUSH,
- CoreRules.FILTER_INTO_JOIN,
- CoreRules.FILTER_PROJECT_TRANSPOSE
+ return ctx.rules(
+ RuleSets.ofList(
+ FilterScanMergeRule.TABLE_SCAN_SKIP_CORRELATED,
+
+ CoreRules.FILTER_MERGE,
+ CoreRules.FILTER_AGGREGATE_TRANSPOSE,
+ CoreRules.FILTER_SET_OP_TRANSPOSE,
+ CoreRules.JOIN_CONDITION_PUSH,
+ CoreRules.FILTER_INTO_JOIN,
+ CoreRules.FILTER_PROJECT_TRANSPOSE
+ )
);
}
@@ -110,14 +116,15 @@ public enum PlannerPhase {
HEP_PROJECT_PUSH_DOWN("Heuristic phase to push down and merge projects") {
/** {@inheritDoc} */
@Override public RuleSet getRules(PlanningContext ctx) {
- return RuleSets.ofList(
- ProjectScanMergeRule.TABLE_SCAN_SKIP_CORRELATED,
- ProjectScanMergeRule.INDEX_SCAN_SKIP_CORRELATED,
-
- CoreRules.JOIN_PUSH_EXPRESSIONS,
- CoreRules.PROJECT_MERGE,
- CoreRules.PROJECT_REMOVE,
- CoreRules.PROJECT_FILTER_TRANSPOSE
+ return ctx.rules(
+ RuleSets.ofList(
+ ProjectScanMergeRule.TABLE_SCAN_SKIP_CORRELATED,
+
+ CoreRules.JOIN_PUSH_EXPRESSIONS,
+ CoreRules.PROJECT_MERGE,
+ CoreRules.PROJECT_REMOVE,
+ CoreRules.PROJECT_FILTER_TRANSPOSE
+ )
);
}
@@ -210,7 +217,8 @@ public enum PlannerPhase {
FilterSpoolMergeToSortedIndexSpoolRule.INSTANCE,
FilterSpoolMergeToHashIndexSpoolRule.INSTANCE,
- LogicalOrToUnionRule.INSTANCE,
+ LogicalOrToUnionRule.FILTER_INSTANCE,
+ LogicalOrToUnionRule.SCAN_INSTANCE,
CorrelatedNestedLoopJoinRule.INSTANCE,
CorrelateToNestedLoopRule.INSTANCE,
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java
index 9c6c5ec..8e7aa30 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalIndexScan.java
@@ -18,19 +18,16 @@
package org.apache.ignite.internal.processors.query.calcite.rel.logical;
import java.util.List;
-
import com.google.common.collect.ImmutableList;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelTraitSet;
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.calcite.util.mapping.Mappings;
import org.apache.ignite.internal.processors.query.calcite.rel.AbstractIndexScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
-import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.processors.query.calcite.util.IndexConditions;
@@ -51,15 +48,12 @@ public class IgniteLogicalIndexScan extends AbstractIndexScan {
) {
IgniteTable tbl = table.unwrap(IgniteTable.class);
IgniteTypeFactory typeFactory = Commons.typeFactory(cluster);
- RelDataType rowType = tbl.getRowType(typeFactory, requiredColumns);
RelCollation collation = tbl.getIndex(idxName).collation();
if (requiredColumns != null) {
Mappings.TargetMapping targetMapping = Commons.mapping(requiredColumns,
tbl.getRowType(typeFactory).getFieldCount());
collation = collation.apply(targetMapping);
- if (proj != null)
- collation = TraitUtils.projectCollation(collation, proj, rowType);
}
IndexConditions idxCond = new IndexConditions();
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverterRule.java
index 7320c5c..009316f 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverterRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterConverterRule.java
@@ -29,6 +29,8 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTrait;
+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.RexUtils;
/**
@@ -46,13 +48,23 @@ public class FilterConverterRule extends AbstractIgniteConverterRule<LogicalFilt
/** {@inheritDoc} */
@Override protected PhysicalNode convert(RelOptPlanner planner, RelMetadataQuery mq, LogicalFilter rel) {
RelOptCluster cluster = rel.getCluster();
- RelTraitSet traits = rel.getTraitSet().replace(IgniteConvention.INSTANCE);
+ RelTraitSet traits = rel.getTraitSet()
+ .replace(IgniteConvention.INSTANCE)
+ .replace(IgniteDistributions.single());
Set<CorrelationId> corrIds = RexUtils.extractCorrelationIds(rel.getCondition());
- if (!corrIds.isEmpty())
- traits = traits.replace(CorrelationTrait.correlations(corrIds));
+ if (!corrIds.isEmpty()) {
+ traits = traits
+ .replace(CorrelationTrait.correlations(corrIds))
+ .replace(RewindabilityTrait.REWINDABLE);
+ }
- return new IgniteFilter(cluster, traits, rel.getInput(), rel.getCondition());
+ return new IgniteFilter(
+ cluster,
+ traits,
+ convert(rel.getInput(), traits.replace(CorrelationTrait.UNCORRELATED)),
+ rel.getCondition()
+ );
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java
index b4ff743..00ce133 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/FilterScanMergeRule.java
@@ -16,34 +16,21 @@
*/
package org.apache.ignite.internal.processors.query.calcite.rule.logical;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
-import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.logical.LogicalFilter;
-import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexUtil;
-import org.apache.calcite.util.ControlFlowException;
-import org.apache.calcite.util.mapping.Mappings;
import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
-import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
-import org.apache.ignite.internal.processors.query.calcite.trait.CorrelationTrait;
-import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
-import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.apache.ignite.internal.util.typedef.F;
@@ -54,41 +41,15 @@ public abstract class FilterScanMergeRule<T extends ProjectableFilterableTableSc
extends RelRule<FilterScanMergeRule.Config> {
/** Instance. */
public static final FilterScanMergeRule<IgniteLogicalIndexScan> INDEX_SCAN =
- new FilterScanMergeRule<IgniteLogicalIndexScan>(Config.INDEX_SCAN) {
- /** {@inheritDoc} */
- @Override protected IgniteLogicalIndexScan createNode(
- RelOptCluster cluster,
- IgniteLogicalIndexScan scan,
- RelTraitSet traits,
- RexNode cond) {
- Set<CorrelationId> corrIds = RexUtils.extractCorrelationIds(cond);
-
- if (!F.isEmpty(corrIds))
- traits = traits.replace(CorrelationTrait.correlations(corrIds));
-
- return IgniteLogicalIndexScan.create(cluster, traits, scan.getTable(), scan.indexName(),
- scan.projects(), cond, scan.requiredColumns());
- }
- };
+ new FilterIndexScanMergeRule(Config.INDEX_SCAN);
/** Instance. */
public static final FilterScanMergeRule<IgniteLogicalTableScan> TABLE_SCAN =
- new FilterScanMergeRule<IgniteLogicalTableScan>(Config.TABLE_SCAN) {
- /** {@inheritDoc} */
- @Override protected IgniteLogicalTableScan createNode(
- RelOptCluster cluster,
- IgniteLogicalTableScan scan,
- RelTraitSet traits,
- RexNode cond) {
- Set<CorrelationId> corrIds = RexUtils.extractCorrelationIds(cond);
-
- if (!F.isEmpty(corrIds))
- traits = traits.replace(CorrelationTrait.correlations(corrIds));
-
- return IgniteLogicalTableScan.create(cluster, traits, scan.getTable(), scan.projects(),
- cond, scan.requiredColumns());
- }
- };
+ new FilterTableScanMergeRule(Config.TABLE_SCAN);
+
+ /** Instance. */
+ public static final FilterScanMergeRule<IgniteLogicalTableScan> TABLE_SCAN_SKIP_CORRELATED =
+ new FilterTableScanMergeRule(Config.TABLE_SCAN_SKIP_CORRELATED);
/**
* Constructor.
@@ -108,66 +69,29 @@ public abstract class FilterScanMergeRule<T extends ProjectableFilterableTableSc
RexBuilder builder = RexUtils.builder(cluster);
RexNode condition = filter.getCondition();
- RexNode remaining = null;
-
- if (scan.condition() != null)
- condition = RexUtil.composeConjunction(builder, F.asList(scan.condition(), condition));
if (scan.projects() != null) {
- IgniteTypeFactory typeFactory = Commons.typeFactory(scan);
-
- IgniteTable tbl = scan.getTable().unwrap(IgniteTable.class);
-
- RelDataType cols = tbl.getRowType(typeFactory, scan.requiredColumns());
-
- Mappings.TargetMapping permutation = RexUtils.permutation(scan.projects(), cols, true);
-
- List<RexNode> conjunctions = RelOptUtil.conjunctions(condition);
-
- List<RexNode> condition0 = new ArrayList<>(conjunctions.size());
- List<RexNode> remaining0 = new ArrayList<>(conjunctions.size());
-
RexShuttle shuttle = new RexShuttle() {
@Override public RexNode visitInputRef(RexInputRef ref) {
- int targetRef = permutation.getTargetOpt(ref.getIndex());
- if (targetRef == -1)
- throw new ControlFlowException();
- return new RexInputRef(targetRef, ref.getType());
+ return scan.projects().get(ref.getIndex());
}
};
- for (RexNode cond0 : conjunctions) {
- try {
- condition0.add(shuttle.apply(cond0));
- }
- catch (ControlFlowException e) {
- remaining0.add(cond0);
- }
- }
-
- condition = RexUtil.composeConjunction(builder, condition0, false);
- remaining = RexUtil.composeConjunction(builder, remaining0, true);
+ condition = shuttle.apply(condition);
}
+ if (scan.condition() != null)
+ condition = RexUtil.composeConjunction(builder, F.asList(scan.condition(), condition));
+
// We need to replace RexInputRef with RexLocalRef because TableScan doesn't have inputs.
// TODO SEARCH support
condition = RexUtils.replaceInputRefs(RexUtil.expandSearch(builder, null, condition));
- RelTraitSet trait = scan.getTraitSet();
- CorrelationTrait filterCorr = TraitUtils.correlation(filter);
-
- if (filterCorr.correlated())
- trait = trait.replace(filterCorr);
+ // Set default traits, real traits will be calculated for physical node.
+ RelTraitSet trait = cluster.traitSet();
RelNode res = createNode(cluster, scan, trait, condition);
- if (remaining != null) {
- res = relBuilderFactory.create(cluster, null)
- .push(res)
- .filter(remaining)
- .build();
- }
-
call.transformTo(res);
}
@@ -175,22 +99,71 @@ public abstract class FilterScanMergeRule<T extends ProjectableFilterableTableSc
protected abstract T createNode(RelOptCluster cluster, T scan, RelTraitSet traits, RexNode cond);
/** */
+ private static class FilterIndexScanMergeRule extends FilterScanMergeRule<IgniteLogicalIndexScan> {
+ /** */
+ private FilterIndexScanMergeRule(FilterScanMergeRule.Config cfg) {
+ super(cfg);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteLogicalIndexScan createNode(
+ RelOptCluster cluster,
+ IgniteLogicalIndexScan scan,
+ RelTraitSet traits,
+ RexNode cond
+ ) {
+ return IgniteLogicalIndexScan.create(cluster, traits, scan.getTable(), scan.indexName(),
+ scan.projects(), cond, scan.requiredColumns());
+ }
+ }
+
+ /** */
+ private static class FilterTableScanMergeRule extends FilterScanMergeRule<IgniteLogicalTableScan> {
+ /** */
+ private FilterTableScanMergeRule(FilterScanMergeRule.Config cfg) {
+ super(cfg);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteLogicalTableScan createNode(
+ RelOptCluster cluster,
+ IgniteLogicalTableScan scan,
+ RelTraitSet traits,
+ RexNode cond
+ ) {
+ return IgniteLogicalTableScan.create(cluster, traits, scan.getTable(), scan.projects(),
+ cond, scan.requiredColumns());
+ }
+ }
+
+ /** */
@SuppressWarnings("ClassNameSameAsAncestorName")
public interface Config extends RelRule.Config {
/** */
Config DEFAULT = EMPTY.withRelBuilderFactory(RelFactories.LOGICAL_BUILDER).as(Config.class);
/** */
- Config TABLE_SCAN = DEFAULT.withScanRuleConfig(IgniteLogicalTableScan.class, "FilterTableScanMergeRule");
+ Config TABLE_SCAN = DEFAULT
+ .withScanRuleConfig(IgniteLogicalTableScan.class, "FilterTableScanMergeRule", false);
+
+ /** */
+ Config TABLE_SCAN_SKIP_CORRELATED = DEFAULT
+ .withScanRuleConfig(IgniteLogicalTableScan.class, "FilterTableScanMergeSkipCorrelatedRule", true);
/** */
- Config INDEX_SCAN = DEFAULT.withScanRuleConfig(IgniteLogicalIndexScan.class, "FilterIndexScanMergeRule");
+ Config INDEX_SCAN = DEFAULT
+ .withScanRuleConfig(IgniteLogicalIndexScan.class, "FilterIndexScanMergeRule", false);
/** */
- default Config withScanRuleConfig(Class<? extends ProjectableFilterableTableScan> scanCls, String desc) {
+ default Config withScanRuleConfig(
+ Class<? extends ProjectableFilterableTableScan> scanCls,
+ String desc,
+ boolean skipCorrelated
+ ) {
return withDescription(desc)
- .withOperandSupplier(b ->
- b.operand(LogicalFilter.class).oneInput(b1 -> b1.operand(scanCls).noInputs()))
+ .withOperandSupplier(b -> b.operand(LogicalFilter.class)
+ .predicate(p -> !skipCorrelated || !RexUtils.hasCorrelation(p.getCondition()))
+ .oneInput(b1 -> b1.operand(scanCls).noInputs()))
.as(Config.class);
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalOrToUnionRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalOrToUnionRule.java
index e91d148..ea0f6a6 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalOrToUnionRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalOrToUnionRule.java
@@ -25,20 +25,64 @@ import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.tools.RelBuilder;
+import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
+import org.jetbrains.annotations.Nullable;
/**
* Converts OR to UNION ALL.
*/
-public class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
- /** Instance. */
- public static final RelOptRule INSTANCE = Config.DEFAULT.toRule();
+public abstract class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
+ /** Rule instance to replace filters. */
+ public static final RelOptRule FILTER_INSTANCE = new LogicalOrToUnionRule(Config.FILTER) {
+ @Override protected RexNode getCondition(RelOptRuleCall call) {
+ final LogicalFilter rel = call.rel(0);
+
+ return rel.getCondition();
+ }
+
+ @Override protected RelNode getInput(RelOptRuleCall call) {
+ final LogicalFilter rel = call.rel(0);
+
+ return rel.getInput();
+ }
+
+ @Override protected void buildInput(RelBuilder relBldr, RelNode input, RexNode condition) {
+ relBldr.push(input).filter(condition);
+ }
+ };
+
+ /** Rule instance to replace table scans with condition. */
+ public static final RelOptRule SCAN_INSTANCE = new LogicalOrToUnionRule(Config.SCAN) {
+ @Override protected RexNode getCondition(RelOptRuleCall call) {
+ final IgniteLogicalTableScan rel = call.rel(0);
+
+ return rel.condition();
+ }
+
+ @Override protected RelNode getInput(RelOptRuleCall call) {
+ return call.rel(0);
+ }
+
+ @Override protected void buildInput(RelBuilder relBldr, RelNode input, RexNode condition) {
+ IgniteLogicalTableScan scan = (IgniteLogicalTableScan)input;
+
+ relBldr.push(IgniteLogicalTableScan.create(
+ scan.getCluster(),
+ scan.getTraitSet(),
+ scan.getTable(),
+ scan.projects(),
+ condition,
+ scan.requiredColumns()
+ ));
+ }
+ };
/**
* Constructor.
@@ -51,20 +95,14 @@ public class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
/** {@inheritDoc} */
@Override public void onMatch(RelOptRuleCall call) {
- final LogicalFilter rel = call.rel(0);
- final RelOptCluster cluster = rel.getCluster();
-
- RexNode dnf = RexUtil.toDnf(cluster.getRexBuilder(), rel.getCondition());
-
- if (!dnf.isA(SqlKind.OR))
- return;
+ final RelOptCluster cluster = call.rel(0).getCluster();
- List<RexNode> operands = RelOptUtil.disjunctions(dnf);
+ List<RexNode> operands = getOrOperands(cluster.getRexBuilder(), getCondition(call));
- if (operands.size() != 2 || RexUtil.find(SqlKind.IS_NULL).anyContain(operands))
+ if (operands == null)
return;
- RelNode input = rel.getInput(0);
+ RelNode input = getInput(call);
RelNode rel0 = createUnionAll(cluster, input, operands.get(0), operands.get(1));
@@ -73,6 +111,30 @@ public class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
));
}
+ /** */
+ protected abstract RexNode getCondition(RelOptRuleCall call);
+
+ /** */
+ protected abstract RelNode getInput(RelOptRuleCall call);
+
+ /** */
+ private static @Nullable List<RexNode> getOrOperands(RexBuilder rexBuilder, RexNode condition) {
+ RexNode dnf = RexUtil.toDnf(rexBuilder, condition);
+
+ if (!dnf.isA(SqlKind.OR))
+ return null;
+
+ List<RexNode> operands = RelOptUtil.disjunctions(dnf);
+
+ if (operands.size() != 2 || RexUtil.find(SqlKind.IS_NULL).anyContain(operands))
+ return null;
+
+ return operands;
+ }
+
+ /** */
+ protected abstract void buildInput(RelBuilder relBldr, RelNode input, RexNode condition);
+
/**
* Creates 'UnionAll' for conditions.
*
@@ -85,11 +147,10 @@ public class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
private RelNode createUnionAll(RelOptCluster cluster, RelNode input, RexNode op1, RexNode op2) {
RelBuilder relBldr = relBuilderFactory.create(cluster, null);
+ buildInput(relBldr, input, op1);
+ buildInput(relBldr, input, relBldr.and(op2, relBldr.or(relBldr.isNull(op1), relBldr.not(op1))));
+
return relBldr
- .push(input).filter(op1)
- .push(input).filter(
- relBldr.and(op2,
- relBldr.or(relBldr.isNull(op1), relBldr.not(op1))))
.union(true)
.build();
}
@@ -100,19 +161,21 @@ public class LogicalOrToUnionRule extends RelRule<LogicalOrToUnionRule.Config> {
/** */
Config DEFAULT = RelRule.Config.EMPTY
.withRelBuilderFactory(RelFactories.LOGICAL_BUILDER)
- .withDescription("LogicalOrToUnionRule")
- .as(Config.class)
- .withOperandFor(LogicalFilter.class);
-
- /** Defines an operand tree for the given classes. */
- default Config withOperandFor(Class<? extends Filter> filterClass) {
- return withOperandSupplier(o -> o.operand(filterClass).anyInputs())
- .as(Config.class);
- }
+ .as(Config.class);
- /** {@inheritDoc} */
- @Override default LogicalOrToUnionRule toRule() {
- return new LogicalOrToUnionRule(this);
- }
+ /** */
+ Config FILTER = DEFAULT
+ .withDescription("FilterLogicalOrToUnionRule")
+ .withOperandSupplier(o -> o.operand(LogicalFilter.class).anyInputs())
+ .as(Config.class);
+
+ /** */
+ Config SCAN = DEFAULT
+ .withDescription("ScanLogicalOrToUnionRule")
+ .withOperandSupplier(o -> o.operand(IgniteLogicalTableScan.class)
+ .predicate(scan -> scan.condition() != null)
+ .anyInputs()
+ )
+ .as(Config.class);
}
}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/ProjectScanMergeRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/ProjectScanMergeRule.java
index 4a5d81e..e15e843 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/ProjectScanMergeRule.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/ProjectScanMergeRule.java
@@ -18,7 +18,6 @@
package org.apache.ignite.internal.processors.query.calcite.rule.logical;
import java.util.List;
-
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelRule;
@@ -35,7 +34,6 @@ import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilter
import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
-import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
@@ -48,10 +46,6 @@ public abstract class ProjectScanMergeRule<T extends ProjectableFilterableTableS
new ProjectIndexScanMergeRule(Config.INDEX_SCAN);
/** Instance. */
- public static final ProjectScanMergeRule<IgniteLogicalIndexScan> INDEX_SCAN_SKIP_CORRELATED =
- new ProjectIndexScanMergeRule(Config.INDEX_SCAN_SKIP_CORRELATED);
-
- /** Instance. */
public static final ProjectScanMergeRule<IgniteLogicalTableScan> TABLE_SCAN =
new ProjectTableScanMergeRule(Config.TABLE_SCAN);
@@ -79,12 +73,6 @@ public abstract class ProjectScanMergeRule<T extends ProjectableFilterableTableS
}
/** {@inheritDoc} */
- @Override public boolean matches(RelOptRuleCall call) {
- T rel = call.rel(1);
- return rel.requiredColumns() == null;
- }
-
- /** {@inheritDoc} */
@Override public void onMatch(RelOptRuleCall call) {
LogicalProject relProject = call.rel(0);
T scan = call.rel(1);
@@ -92,54 +80,66 @@ public abstract class ProjectScanMergeRule<T extends ProjectableFilterableTableS
RelOptCluster cluster = scan.getCluster();
List<RexNode> projects = relProject.getProjects();
RexNode cond = scan.condition();
+ ImmutableBitSet requiredColumns = scan.requiredColumns();
+ List<RexNode> scanProjects = scan.projects();
- // projection changes input collation and distribution.
- RelTraitSet traits = scan.getTraitSet();
-
- traits = traits.replace(TraitUtils.projectCollation(
- TraitUtils.collation(traits), projects, scan.getRowType()));
-
- traits = traits.replace(TraitUtils.projectDistribution(
- TraitUtils.distribution(traits), projects, scan.getRowType()));
+ // Set default traits, real traits will be calculated for physical node.
+ RelTraitSet traits = cluster.traitSet();
IgniteTable tbl = scan.getTable().unwrap(IgniteTable.class);
IgniteTypeFactory typeFactory = Commons.typeFactory(cluster);
- ImmutableBitSet.Builder builder = ImmutableBitSet.builder();
-
- new RexShuttle() {
- @Override public RexNode visitInputRef(RexInputRef ref) {
- builder.set(ref.getIndex());
- return ref;
- }
- }.apply(projects);
- new RexShuttle() {
- @Override public RexNode visitLocalRef(RexLocalRef inputRef) {
- builder.set(inputRef.getIndex());
- return inputRef;
- }
- }.apply(cond);
-
- ImmutableBitSet requiredColumns = builder.build();
-
- Mappings.TargetMapping targetMapping = Commons.mapping(requiredColumns,
- tbl.getRowType(typeFactory).getFieldCount());
-
- projects = new RexShuttle() {
- @Override public RexNode visitInputRef(RexInputRef ref) {
- return new RexLocalRef(targetMapping.getTarget(ref.getIndex()), ref.getType());
- }
- }.apply(projects);
+ if (requiredColumns == null) {
+ assert scanProjects == null;
+
+ ImmutableBitSet.Builder builder = ImmutableBitSet.builder();
+
+ new RexShuttle() {
+ @Override public RexNode visitInputRef(RexInputRef ref) {
+ builder.set(ref.getIndex());
+ return ref;
+ }
+ }.apply(projects);
+
+ new RexShuttle() {
+ @Override public RexNode visitLocalRef(RexLocalRef inputRef) {
+ builder.set(inputRef.getIndex());
+ return inputRef;
+ }
+ }.apply(cond);
+
+ requiredColumns = builder.build();
+
+ Mappings.TargetMapping targetMapping = Commons.mapping(requiredColumns,
+ tbl.getRowType(typeFactory).getFieldCount());
+
+ projects = new RexShuttle() {
+ @Override public RexNode visitInputRef(RexInputRef ref) {
+ return new RexLocalRef(targetMapping.getTarget(ref.getIndex()), ref.getType());
+ }
+ }.apply(projects);
+
+ cond = new RexShuttle() {
+ @Override public RexNode visitLocalRef(RexLocalRef ref) {
+ return new RexLocalRef(targetMapping.getTarget(ref.getIndex()), ref.getType());
+ }
+ }.apply(cond);
+ }
+ else
+ projects = RexUtils.replaceInputRefs(projects);
+
+ if (scanProjects != null) {
+ // Merge projects.
+ projects = new RexShuttle() {
+ @Override public RexNode visitLocalRef(RexLocalRef ref) {
+ return scanProjects.get(ref.getIndex());
+ }
+ }.apply(projects);
+ }
if (RexUtils.isIdentity(projects, tbl.getRowType(typeFactory, requiredColumns), true))
projects = null;
- cond = new RexShuttle() {
- @Override public RexNode visitLocalRef(RexLocalRef ref) {
- return new RexLocalRef(targetMapping.getTarget(ref.getIndex()), ref.getType());
- }
- }.apply(cond);
-
call.transformTo(createNode(cluster, scan, traits, projects, cond, requiredColumns));
if (!RexUtils.hasCorrelation(relProject.getProjects()))
@@ -227,10 +227,6 @@ public abstract class ProjectScanMergeRule<T extends ProjectableFilterableTableS
IgniteLogicalIndexScan.class, "ProjectIndexScanMergeRule", false);
/** */
- Config INDEX_SCAN_SKIP_CORRELATED = DEFAULT.withScanRuleConfig(
- IgniteLogicalIndexScan.class, "ProjectIndexScanMergeSkipCorrelatedRule", true);
-
- /** */
default Config withScanRuleConfig(
Class<? extends ProjectableFilterableTableScan> scanCls,
String desc,
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
index f8e0ced..e1b361e 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
@@ -438,20 +438,6 @@ public class RexUtils {
}
/** */
- public static Mappings.TargetMapping permutation(List<RexNode> nodes, RelDataType inputRowType, boolean local) {
- final Mappings.TargetMapping mapping =
- Mappings.create(MappingType.PARTIAL_FUNCTION, nodes.size(), inputRowType.getFieldCount());
-
- Class<? extends RexSlot> clazz = local ? RexLocalRef.class : RexInputRef.class;
-
- for (Ord<RexNode> node : Ord.zip(nodes)) {
- if (clazz.isInstance(node.e))
- mapping.set(node.i, ((RexSlot)node.e).getIndex());
- }
- return mapping;
- }
-
- /** */
public static Mappings.TargetMapping inversePermutation(List<RexNode> nodes, RelDataType inputRowType, boolean local) {
final Mappings.TargetMapping mapping =
Mappings.create(MappingType.INVERSE_FUNCTION, nodes.size(), inputRowType.getFieldCount());
@@ -466,11 +452,6 @@ public class RexUtils {
}
/** */
- public static Mappings.TargetMapping permutation(List<RexNode> nodes, RelDataType inputRowType) {
- return permutation(nodes, inputRowType, false);
- }
-
- /** */
public static List<RexNode> replaceLocalRefs(List<RexNode> nodes) {
return LocalRefReplacer.INSTANCE.apply(nodes);
}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/ServerStatisticsIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/ServerStatisticsIntegrationTest.java
index e99b3ab..77b9d11 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/ServerStatisticsIntegrationTest.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/ServerStatisticsIntegrationTest.java
@@ -324,7 +324,7 @@ public class ServerStatisticsIntegrationTest extends AbstractBasicIntegrationTes
"%s > %d and %s < %d", numericField, -1, numericField, 101))
.matches(QueryChecker.containsResultRowCount(allRowCnt)).check();
- assertQuerySrv(String.format("select /*+ DISABLE_RULE('LogicalOrToUnionRule') */ * from all_types where " +
+ assertQuerySrv(String.format("select /*+ DISABLE_RULE('ScanLogicalOrToUnionRule') */ * from all_types where " +
"(%s > %d and %s < %d) or " +
"(int_field > -1 and int_field < 101)",
numericField, -1, numericField, 101))
diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
new file mode 100644
index 0000000..9336c3e
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query.calcite.planner;
+
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteAggregate;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests ProjectScanMergeRule and FilterScanMergeRule.
+ */
+public class ProjectFilterScanMergePlannerTest extends AbstractPlannerTest {
+ /** Public schema. */
+ private IgniteSchema publicSchema;
+
+ /** {@inheritDoc} */
+ @Before
+ @Override public void setup() {
+ super.setup();
+
+ publicSchema = new IgniteSchema("PUBLIC");
+
+ IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+ RelDataType type = new RelDataTypeFactory.Builder(f)
+ .add("A", f.createSqlType(SqlTypeName.INTEGER))
+ .add("B", f.createSqlType(SqlTypeName.INTEGER))
+ .add("C", f.createSqlType(SqlTypeName.INTEGER))
+ .build();
+
+ createTable(publicSchema, "TBL", type, IgniteDistributions.single(), null);
+ }
+
+ /** */
+ @Test
+ public void testProjectFilterMerge() throws Exception {
+ // Order of merge: ((scan + filter) + project).
+ assertPlan("SELECT a, b FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[$t0, $t1]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t2, 0)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 1, 2).equals(scan.requiredColumns()))
+ );
+ }
+
+ /** */
+ @Test
+ public void testIdentityFilterMerge() throws Exception {
+ // Order of merge: ((scan + filter) + identity).
+ assertPlan("SELECT a, b, c FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() == null)
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t2, 0)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 1, 2).equals(scan.requiredColumns()))
+ );
+ }
+
+ /** */
+ @Test
+ public void testProjectFilterMergeIndex() throws Exception {
+ // Test project and filter merge into index scan.
+ TestTable tbl = ((TestTable)publicSchema.getTable("TBL"));
+ tbl.addIndex(new IgniteIndex(RelCollations.of(2), "IDX_C", null, tbl));
+
+ // Without index condition shift.
+ assertPlan("SELECT a, b FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[$t0, $t1]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t2, 0)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 1, 2).equals(scan.requiredColumns()))
+ .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
+ .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ );
+
+ // Index condition shifted according to requiredColumns.
+ assertPlan("SELECT b FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[$t0]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t1, 0)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(1, 2).equals(scan.requiredColumns()))
+ .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
+ .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ );
+ }
+
+ /** */
+ @Test
+ public void testIdentityFilterMergeIndex() throws Exception {
+ // Test project and filter merge into index scan.
+ TestTable tbl = ((TestTable)publicSchema.getTable("TBL"));
+ tbl.addIndex(new IgniteIndex(RelCollations.of(2), "IDX_C", null, tbl));
+
+ // Without index condition shift.
+ assertPlan("SELECT a, b, c FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
+ .and(scan -> scan.projects() == null)
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t2, 0)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 1, 2).equals(scan.requiredColumns()))
+ .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
+ .and(scan -> "[=($t2, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ );
+
+ // Index condition shift and identity.
+ assertPlan("SELECT b, c FROM tbl WHERE c = 0", publicSchema, isInstanceOf(IgniteIndexScan.class)
+ .and(scan -> scan.projects() == null)
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t1, 0)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(1, 2).equals(scan.requiredColumns()))
+ .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().lowerCondition().toString()))
+ .and(scan -> "[=($t1, 0)]".equals(scan.indexConditions().upperCondition().toString()))
+ );
+ }
+
+ /** */
+ @Test
+ public void testProjectFilterProjectMerge() throws Exception {
+ // Inner query contains correlate, it prevents filter to be moved below project, and after HEP_FILTER_PUSH_DOWN
+ // phase we should have chain Project - Filter - Project - Scan. Whole this chain should be merged into a single
+ // table scan on the next phases. Order of merge: (((scan + inner project) + filter) + outer project).
+ String sql = "SELECT (SELECT a+2 FROM (SELECT c, a+1 AS a FROM tbl) AS t2 WHERE t2.c = t1.c) FROM tbl AS t1";
+
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[+(+($t0, 1), 2)]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t1, $cor0.C)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 2).equals(scan.requiredColumns()))
+ ))));
+ }
+
+ /** */
+ @Test
+ public void testIdentityFilterProjectMerge() throws Exception {
+ // The same as two projects merge, but outer project is identity and should be eliminated together with inner
+ // project by project to scan merge rule.
+ String sql = "SELECT (SELECT a FROM (SELECT a, a+1 FROM tbl) AS t2 WHERE t2.a = t1.a) FROM tbl AS t1";
+
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() == null)
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t0, $cor0.A)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0).equals(scan.requiredColumns()))
+ ))), "ProjectFilterTransposeRule");
+
+ // Filter on project that is not permutation should be merged too.
+ sql = "SELECT (SELECT a FROM (SELECT a+1 AS a FROM tbl) AS t2 WHERE t2.a = t1.a) FROM tbl AS t1";
+
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[+($t0, 1)]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=(+($t0, 1), $cor0.A)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0).equals(scan.requiredColumns()))
+ ))), "ProjectFilterTransposeRule");
+ }
+
+ /** */
+ @Test
+ public void testProjectFilterIdentityMerge() throws Exception {
+ // The same as two projects merge, but inner project is identity and should be eliminated by project to scan
+ // merge rule.
+ String sql = "SELECT (SELECT a+2 FROM (SELECT a, c FROM tbl) AS t2 WHERE t2.c = t1.c) FROM tbl AS t1";
+
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[+($t0, 2)]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t1, $cor0.C)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 2).equals(scan.requiredColumns()))
+ ))));
+ }
+
+ /** */
+ @Test
+ public void testIdentityFilterIdentityMerge() throws Exception {
+ // The same as two projects merge, but projects are identity and should be eliminated by project to scan
+ // merge rule.
+ String sql = "SELECT (SELECT c FROM (SELECT a AS c FROM tbl) AS t2 WHERE t2.c = t1.c) FROM tbl AS t1";
+
+ assertPlan(sql, publicSchema, hasChildThat(isInstanceOf(IgniteAggregate.class)
+ .and(input(isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() == null)
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "=($t0, $cor0.C)".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0).equals(scan.requiredColumns()))
+ ))));
+ }
+
+ /** */
+ @Test
+ public void testFilterProjectFilterMerge() throws Exception {
+ String sql = "SELECT * FROM (SELECT c, a FROM tbl WHERE a = 1) WHERE c = 1";
+
+ assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() != null)
+ .and(scan -> "[$t1, $t0]".equals(scan.projects().toString()))
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "AND(=($t0, 1), =($t1, 1))".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 2).equals(scan.requiredColumns())),
+ "ProjectFilterTransposeRule", "FilterProjectTransposeRule");
+ }
+
+ /** */
+ @Test
+ public void testFilterIdentityFilterMerge() throws Exception {
+ String sql = "SELECT * FROM (SELECT a, c FROM tbl WHERE a = 1) WHERE c = 1";
+
+ assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class)
+ .and(scan -> scan.projects() == null)
+ .and(scan -> scan.condition() != null)
+ .and(scan -> "AND(=($t0, 1), =($t1, 1))".equals(scan.condition().toString()))
+ .and(scan -> ImmutableBitSet.of(0, 2).equals(scan.requiredColumns())),
+ "ProjectFilterTransposeRule", "FilterProjectTransposeRule");
+ }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
index baa407e..5de95d3 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
@@ -29,6 +29,7 @@ import org.apache.ignite.internal.processors.query.calcite.planner.JoinWithUsing
import org.apache.ignite.internal.processors.query.calcite.planner.LimitOffsetPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.MergeJoinPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.PlannerTest;
+import org.apache.ignite.internal.processors.query.calcite.planner.ProjectFilterScanMergePlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.SetOpPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.SortAggregatePlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.SortedIndexSpoolPlannerTest;
@@ -65,6 +66,7 @@ import org.junit.runners.Suite;
StatisticsPlannerTest.class,
CorrelatedSubqueryPlannerTest.class,
JoinWithUsingPlannerTest.class,
+ ProjectFilterScanMergePlannerTest.class,
})
public class PlannerTestSuite {
}