This is an automated email from the ASF dual-hosted git repository.
asolimando pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 25daf51444 [CALCITE-7125] Impossible to get a plan with partial
aggregate push-down via IntersectToDistinctRule
25daf51444 is described below
commit 25daf51444b6de23c6bb72c319ad5b9568b83e22
Author: Alessandro Solimando <[email protected]>
AuthorDate: Tue Jul 22 16:14:05 2025 +0200
[CALCITE-7125] Impossible to get a plan with partial aggregate push-down
via IntersectToDistinctRule
---
.../org/apache/calcite/rel/rules/CoreRules.java | 12 +-
.../calcite/rel/rules/IntersectToDistinctRule.java | 143 +++++++++++++++++----
.../java/org/apache/calcite/test/JdbcTest.java | 27 ----
.../org/apache/calcite/test/RelOptRulesTest.java | 33 +++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 110 ++++++++++++++--
core/src/test/resources/sql/set-op.iq | 68 ++++++++++
6 files changed, 331 insertions(+), 62 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
index 1e222b639e..462f227f26 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java
@@ -375,12 +375,18 @@ private CoreRules() {}
public static final IntersectReorderRule INTERSECT_REORDER =
IntersectReorderRule.Config.DEFAULT.toRule();
- /** Rule that translates a distinct
- * {@link Intersect} into a group of operators
- * composed of {@link Union}, {@link Aggregate}, etc. */
+ /** Rule that translates a distinct {@link Intersect} into a group of
operators
+ * composed of {@link Union}, {@link Aggregate}, etc. The rule also applies a
+ * partial aggregation pushdown into the union branches. */
public static final IntersectToDistinctRule INTERSECT_TO_DISTINCT =
IntersectToDistinctRule.Config.DEFAULT.toRule();
+ /** As {@link #INTERSECT_TO_DISTINCT} but not applying (partial) aggregate
pushdown
+ * into the union branches (behaviour introduced in CALCITE-6893). */
+ @RuleConfig(value = "NO_AGGREGATE_PUSHDOWN")
+ public static final IntersectToDistinctRule
INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN =
+ IntersectToDistinctRule.Config.NO_AGGREGATE_PUSHDOWN.toRule();
+
/** Rule that translates a {@link Intersect}
* into a {@link Exists} subquery. */
public static final IntersectToExistsRule INTERSECT_TO_EXISTS =
diff --git
a/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
b/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
index ebd5ac8f58..a8d2713e09 100644
---
a/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
+++
b/core/src/main/java/org/apache/calcite/rel/rules/IntersectToDistinctRule.java
@@ -16,18 +16,23 @@
*/
package org.apache.calcite.rel.rules;
+import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Intersect;
import org.apache.calcite.rel.logical.LogicalIntersect;
+import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RelBuilder.AggCall;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.Util;
import org.immutables.value.Value;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@@ -41,30 +46,12 @@
* {@link org.apache.calcite.rel.core.Union},
* {@link org.apache.calcite.rel.core.Aggregate}, etc.
*
- * <h2>Example</h2>
- *
- * <p>Original query:
- * <pre>{@code
- * SELECT job FROM "scott".emp WHERE deptno = 10
- * INTERSECT
- * SELECT job FROM "scott".emp WHERE deptno = 20
- * }</pre>
- *
- * <p>Query after conversion:
- * <pre>{@code
- * SELECT job
- * FROM (
- * SELECT job, 0 AS i FROM "scott".emp WHERE deptno = 10
- * UNION ALL
- * SELECT job, 1 AS i FROM "scott".emp WHERE deptno = 20
- * )
- * GROUP BY job
- * HAVING COUNT(*) FILTER (WHERE i = 0) > 0
- * AND COUNT(*) FILTER (WHERE i = 1) > 0
- * }</pre>
+ * <p>The rule has a configuration option to control whether it should also
perform
+ * a (partial) aggregation pushdown in the union branches (default behavior).
*
* @see org.apache.calcite.rel.rules.UnionToDistinctRule
* @see CoreRules#INTERSECT_TO_DISTINCT
+ * @see CoreRules#INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN
*/
@Value.Enclosing
public class IntersectToDistinctRule
@@ -85,8 +72,38 @@ public IntersectToDistinctRule(Class<? extends Intersect>
intersectClass,
}
//~ Methods ----------------------------------------------------------------
-
@Override public void onMatch(RelOptRuleCall call) {
+ if (config.isAggregatePushdown()) {
+ onMatchAggregatePushdown(call);
+ } else {
+ onMatchAggregateOnUnion(call);
+ }
+ }
+
+ /**
+ * Variant not performing a partial aggregation pushdown.
+ *
+ * <p>Original query:
+ * <pre>{@code
+ * SELECT job FROM "scott".emp WHERE deptno = 10
+ * INTERSECT
+ * SELECT job FROM "scott".emp WHERE deptno = 20
+ * }</pre>
+ *
+ * <p>Query after conversion:
+ * <pre>{@code
+ * SELECT job
+ * FROM (
+ * SELECT job, 0 AS i FROM "scott".emp WHERE deptno = 10
+ * UNION ALL
+ * SELECT job, 1 AS i FROM "scott".emp WHERE deptno = 20
+ * )
+ * GROUP BY job
+ * HAVING COUNT(*) FILTER (WHERE i = 0) > 0
+ * AND COUNT(*) FILTER (WHERE i = 1) > 0
+ * }</pre>
+ */
+ public void onMatchAggregateOnUnion(RelOptRuleCall call) {
final Intersect intersect = call.rel(0);
if (intersect.all) {
return; // nothing we can do
@@ -126,12 +143,86 @@ public IntersectToDistinctRule(Class<? extends Intersect>
intersectClass,
call.transformTo(relBuilder.build());
}
+ /**
+ * Variant performing a partial aggregation pushdown.
+ *
+ * <p>Original query:
+ * <pre>{@code
+ * SELECT job FROM "scott".emp WHERE deptno = 10
+ * INTERSECT
+ * SELECT job FROM "scott".emp WHERE deptno = 20
+ * }</pre>
+ *
+ * <p>Query after conversion:
+ * <pre>{@code
+ * SELECT job
+ * FROM (
+ * SELECT job, COUNT(*) AS c
+ * FROM (
+ * SELECT job, COUNT(*) FROM "scott".emp
+ * WHERE deptno = 10 GROUP BY job
+ * UNION ALL
+ * SELECT job, COUNT(*) FROM "scott".emp
+ * WHERE deptno = 20 GROUP BY job)
+ * GROUP BY job)
+ * WHERE c = 2
+ * }</pre>
+ */
+ public void onMatchAggregatePushdown(RelOptRuleCall call) {
+ final Intersect intersect = call.rel(0);
+ if (intersect.all) {
+ return; // nothing we can do
+ }
+ final RelOptCluster cluster = intersect.getCluster();
+ final RexBuilder rexBuilder = cluster.getRexBuilder();
+ final RelBuilder relBuilder = call.builder();
+
+ // 1st level aggregate: create an aggregate(col_0, ..., col_n, count(*)),
for each branch
+ for (RelNode input : intersect.getInputs()) {
+ relBuilder.push(input);
+ relBuilder.aggregate(relBuilder.groupKey(relBuilder.fields()),
+ relBuilder.countStar(null));
+ }
+
+ // create a union above all the branches
+ final int branchCount = intersect.getInputs().size();
+ relBuilder.union(true, branchCount);
+ final RelNode union = relBuilder.peek();
+
+ // 2nd level aggregate: create an aggregate(col_0, ..., col_n, count(*)),
for each branch
+ // the index of the counter is union.getRowType().getFieldList().size() - 1
+ final int fieldCount = union.getRowType().getFieldCount();
+
+ final ImmutableBitSet groupSet =
+ ImmutableBitSet.range(fieldCount - 1);
+ relBuilder.aggregate(relBuilder.groupKey(groupSet),
+ relBuilder.countStar(null));
+
+ // add a filter count(*) = #branches
+ relBuilder.filter(
+ relBuilder.equals(relBuilder.field(fieldCount - 1),
+ rexBuilder.makeBigintLiteral(new BigDecimal(branchCount))));
+
+ // Project all but the last field
+ relBuilder.project(Util.skipLast(relBuilder.fields()));
+
+ // the schema for intersect distinct matches that of the relation,
+ // built here with an extra last column for the count,
+ // which is projected out by the final project we added
+ call.transformTo(relBuilder.build());
+ }
+
/** Rule configuration. */
@Value.Immutable
public interface Config extends RelRule.Config {
Config DEFAULT = ImmutableIntersectToDistinctRule.Config.of()
.withOperandFor(LogicalIntersect.class);
+ Config NO_AGGREGATE_PUSHDOWN = DEFAULT
+ .withDescription("IntersectToDistinctRule(NoAggregatePushDown)")
+ .as(Config.class)
+ .withAggregatePushdown(false);
+
@Override default IntersectToDistinctRule toRule() {
return new IntersectToDistinctRule(this);
}
@@ -141,5 +232,13 @@ default Config withOperandFor(Class<? extends Intersect>
intersectClass) {
return withOperandSupplier(b -> b.operand(intersectClass).anyInputs())
.as(Config.class);
}
+
+ /** Whether to apply partial aggregate pushdown; default true. */
+ @Value.Default default boolean isAggregatePushdown() {
+ return true;
+ }
+
+ /** Sets {@link #isAggregatePushdown()} ()}. */
+ Config withAggregatePushdown(boolean aggregatePushdown);
}
}
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 5add262ee5..64fb5b0caf 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -4144,33 +4144,6 @@ public void checkOrderBy(final boolean desc,
.returnsUnordered("empid=150; name=Sebastian");
}
- /**
- * Test case of
- * <a
href="https://issues.apache.org/jira/browse/CALCITE-6893">[CALCITE-6893]
- * Remove agg from Union children in IntersectToDistinctRule</a>. */
- @Test void testIntersectToDistinct() {
- final String sql = ""
- + "select \"empid\", \"name\" from \"hr\".\"emps\" where
\"deptno\"=10\n"
- + "intersect\n"
- + "select \"empid\", \"name\" from \"hr\".\"emps\" where
\"empid\">=150";
- final String[] returns = new String[] {
- "empid=150; name=Sebastian"};
-
- CalciteAssert.hr()
- .query(sql)
- .explainContains("EnumerableIntersect")
- .returnsUnordered(returns);
-
- CalciteAssert.hr()
- .query(sql)
- .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>)
- p -> {
- p.removeRule(EnumerableRules.ENUMERABLE_INTERSECT_RULE);
- })
- .explainContains("EnumerableUnion(all=[true])")
- .returnsUnordered(returns);
- }
-
/** Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-6904">[CALCITE-6904]
* IS_NOT_DISTINCT_FROM is converted error in EnumerableJoinRule</a>. */
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index df8789414d..c8fdd3111c 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -3686,6 +3686,22 @@ private void
checkPushJoinThroughUnionOnRightDoesNotMatchSemiOrAntiJoin(JoinRelT
.check();
}
+ /** Tests {@link org.apache.calcite.rel.rules.IntersectToDistinctRule} with
+ * the option to not perform aggregation pushdown (see
+ * {@link CoreRules#INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN}).
+ * which rewrites an {@link Intersect} operator with 3 inputs. */
+ @Test void testIntersectToDistinctNoAggregatePushdown() {
+ final String sql = "select * from emp where deptno = 10\n"
+ + "intersect\n"
+ + "select * from emp where deptno = 20\n"
+ + "intersect\n"
+ + "select * from emp where deptno = 30\n";
+ sql(sql)
+ .withRule(CoreRules.INTERSECT_MERGE,
+ CoreRules.INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN)
+ .check();
+ }
+
/** Tests that {@link org.apache.calcite.rel.rules.IntersectToDistinctRule}
* correctly ignores an {@code INTERSECT ALL}. It can only handle
* {@code INTERSECT DISTINCT}. */
@@ -3701,6 +3717,23 @@ private void
checkPushJoinThroughUnionOnRightDoesNotMatchSemiOrAntiJoin(JoinRelT
.check();
}
+ /** Tests {@link org.apache.calcite.rel.rules.IntersectToDistinctRule} with
+ * the option to not perform aggregation pushdown (see
+ * {@link CoreRules#INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN}).
+ * correctly ignores an {@code INTERSECT ALL}. It can only handle
+ * {@code INTERSECT DISTINCT}. */
+ @Test void testIntersectToDistinctAllNoAggregatePushdown() {
+ final String sql = "select * from emp where deptno = 10\n"
+ + "intersect\n"
+ + "select * from emp where deptno = 20\n"
+ + "intersect all\n"
+ + "select * from emp where deptno = 30\n";
+ sql(sql)
+ .withRule(CoreRules.INTERSECT_MERGE,
+ CoreRules.INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN)
+ .check();
+ }
+
/** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-6880">
* [CALCITE-6880] Implement IntersectToSemiJoinRule</a>. */
@Test void testIntersectToSemiJoin() {
diff --git
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 0e1f49959e..8beb8062ad 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -6329,26 +6329,70 @@ LogicalIntersect(all=[false])
<Resource name="planAfter">
<![CDATA[
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- LogicalFilter(condition=[AND(>($9, 0), >($10, 0), >($11, 0))])
- LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], count_i0=[COUNT()
FILTER $9], count_i1=[COUNT() FILTER $10], count_i2=[COUNT() FILTER $11])
- LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f10=[=($9, 0)],
$f11=[=($9, 1)], $f12=[=($9, 2)])
+ LogicalFilter(condition=[=($9, 3)])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], agg#0=[COUNT()])
+ LogicalUnion(all=[true])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], agg#0=[COUNT()])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 10)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], agg#0=[COUNT()])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 20)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], agg#0=[COUNT()])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 30)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testIntersectToDistinctAll">
+ <Resource name="sql">
+ <![CDATA[select * from emp where deptno = 10
+intersect
+select * from emp where deptno = 20
+intersect all
+select * from emp where deptno = 30
+]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalIntersect(all=[true])
+ LogicalIntersect(all=[false])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 10)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 20)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 30)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalIntersect(all=[true])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($9, 2)])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], agg#0=[COUNT()])
LogicalUnion(all=[true])
- LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], i=[0])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}],
agg#0=[COUNT()])
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
LogicalFilter(condition=[=($7, 10)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
- LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], i=[1])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}],
agg#0=[COUNT()])
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
LogicalFilter(condition=[=($7, 20)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
- LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], i=[2])
- LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
- LogicalFilter(condition=[=($7, 30)])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 30)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
- <TestCase name="testIntersectToDistinctAll">
+ <TestCase name="testIntersectToDistinctAllNoAggregatePushdown">
<Resource name="sql">
<![CDATA[select * from emp where deptno = 10
intersect
@@ -6391,6 +6435,52 @@ LogicalIntersect(all=[true])
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
LogicalFilter(condition=[=($7, 30)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testIntersectToDistinctNoAggregatePushdown">
+ <Resource name="sql">
+ <![CDATA[select * from emp where deptno = 10
+intersect
+select * from emp where deptno = 20
+intersect
+select * from emp where deptno = 30
+]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalIntersect(all=[false])
+ LogicalIntersect(all=[false])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 10)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 20)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 30)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[AND(>($9, 0), >($10, 0), >($11, 0))])
+ LogicalAggregate(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}], count_i0=[COUNT()
FILTER $9], count_i1=[COUNT() FILTER $10], count_i2=[COUNT() FILTER $11])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f10=[=($9, 0)],
$f11=[=($9, 1)], $f12=[=($9, 2)])
+ LogicalUnion(all=[true])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], i=[0])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 10)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], i=[1])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 20)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], i=[2])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+ LogicalFilter(condition=[=($7, 30)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
diff --git a/core/src/test/resources/sql/set-op.iq
b/core/src/test/resources/sql/set-op.iq
index 4b3917ad30..b52debdf3e 100644
--- a/core/src/test/resources/sql/set-op.iq
+++ b/core/src/test/resources/sql/set-op.iq
@@ -47,6 +47,74 @@ intersect
!ok
+!use scott
+
+!set planner-rules "
+-EnumerableRules.ENUMERABLE_INTERSECT_RULE,
+-AGGREGATE_REMOVE"
+# AGGREGATE_REMOVE is disabled as it would remove the inner COUNTs
+# as the grouping columns are unique
+
+# Intersect rewrite as aggregation + union with partial aggregation pushdown
+select empno, ename from emp where deptno = 10
+intersect
+select empno, ename from emp where empno >= 150;
+
+EnumerableCalc(expr#0..2=[{inputs}], expr#3=[2:BIGINT], expr#4=[=($t2, $t3)],
proj#0..1=[{exprs}], $condition=[$t4])
+ EnumerableAggregate(group=[{0, 1}], agg#0=[COUNT()])
+ EnumerableUnion(all=[true])
+ EnumerableAggregate(group=[{0, 1}], agg#0=[COUNT()])
+ EnumerableCalc(expr#0..7=[{inputs}], expr#8=[CAST($t7):INTEGER],
expr#9=[10], expr#10=[=($t8, $t9)], proj#0..7=[{exprs}], $condition=[$t10])
+ EnumerableTableScan(table=[[scott, EMP]])
+ EnumerableAggregate(group=[{0, 1}], agg#0=[COUNT()])
+ EnumerableCalc(expr#0..7=[{inputs}], expr#8=[CAST($t0):INTEGER NOT
NULL], expr#9=[150], expr#10=[>=($t8, $t9)], proj#0..7=[{exprs}],
$condition=[$t10])
+ EnumerableTableScan(table=[[scott, EMP]])
+!plan
++-------+--------+
+| EMPNO | ENAME |
++-------+--------+
+| 7782 | CLARK |
+| 7839 | KING |
+| 7934 | MILLER |
++-------+--------+
+(3 rows)
+
+!ok
+!set planner-rules original
+
+!use scott
+
+!set planner-rules "
+-CoreRules.INTERSECT_TO_DISTINCT,
+-EnumerableRules.ENUMERABLE_INTERSECT_RULE,
++CoreRules.INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN"
+
+# Intersect rewrite as aggregation + union without partial aggregation pushdown
+select empno, ename from emp where deptno = 10
+intersect
+select empno, ename from emp where empno >= 150;
+
+EnumerableCalc(expr#0..3=[{inputs}], expr#4=[0], expr#5=[>($t2, $t4)],
expr#6=[>($t3, $t4)], expr#7=[AND($t5, $t6)], proj#0..1=[{exprs}],
$condition=[$t7])
+ EnumerableAggregate(group=[{0, 1}], count_i0=[COUNT() FILTER $2],
count_i1=[COUNT() FILTER $3])
+ EnumerableCalc(expr#0..2=[{inputs}], expr#3=[0], expr#4=[=($t2, $t3)],
expr#5=[1], expr#6=[=($t2, $t5)], proj#0..1=[{exprs}], $f3=[$t4], $f4=[$t6])
+ EnumerableUnion(all=[true])
+ EnumerableCalc(expr#0..7=[{inputs}], expr#8=[0],
expr#9=[CAST($t7):INTEGER], expr#10=[10], expr#11=[=($t9, $t10)],
proj#0..1=[{exprs}], i=[$t8], $condition=[$t11])
+ EnumerableTableScan(table=[[scott, EMP]])
+ EnumerableCalc(expr#0..7=[{inputs}], expr#8=[1],
expr#9=[CAST($t0):INTEGER NOT NULL], expr#10=[150], expr#11=[>=($t9, $t10)],
proj#0..1=[{exprs}], i=[$t8], $condition=[$t11])
+ EnumerableTableScan(table=[[scott, EMP]])
+!plan
++-------+--------+
+| EMPNO | ENAME |
++-------+--------+
+| 7782 | CLARK |
+| 7839 | KING |
+| 7934 | MILLER |
++-------+--------+
+(3 rows)
+
+!ok
+!set planner-rules original
+
# Intersect all with null value rows
select * from
(select x, y from (values (cast(NULL as int), cast(NULL as varchar(1))),