Skip to content

Commit 2e02a7b

Browse files
committed
[CALCITE-6891] Implement IntersectReorderRule
1 parent c72e267 commit 2e02a7b

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

core/src/main/java/org/apache/calcite/rel/rules/CoreRules.java

+5
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,11 @@ private CoreRules() {}
349349
public static final UnionMergeRule INTERSECT_MERGE =
350350
UnionMergeRule.Config.INTERSECT.toRule();
351351

352+
/** Planner rule that reorders inputs of an {@link Intersect} to put smaller inputs first.
353+
* This helps reduce the size of intermediate results. */
354+
public static final IntersectReorderRule INTERSECT_REORDER =
355+
IntersectReorderRule.Config.DEFAULT.toRule();
356+
352357
/** Rule that translates a distinct
353358
* {@link Intersect} into a group of operators
354359
* composed of {@link Union}, {@link Aggregate}, etc. */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.rel.rules;
18+
19+
import org.apache.calcite.plan.RelOptRuleCall;
20+
import org.apache.calcite.plan.RelRule;
21+
import org.apache.calcite.rel.RelNode;
22+
import org.apache.calcite.rel.core.Intersect;
23+
import org.apache.calcite.rel.logical.LogicalIntersect;
24+
import org.apache.calcite.rel.metadata.RelMetadataQuery;
25+
import org.apache.calcite.tools.RelBuilder;
26+
import org.apache.calcite.util.Pair;
27+
28+
import org.immutables.value.Value;
29+
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
import java.util.stream.Collectors;
33+
34+
/**
35+
* Planner rule that reorders inputs of an {@link Intersect} to put smaller inputs first.
36+
* This helps reduce the size of intermediate results.
37+
*
38+
* <p>Intersect(A, B, ...) where B is smallest will reorder to Intersect(B, A, ...)
39+
*/
40+
@Value.Enclosing
41+
public class IntersectReorderRule extends RelRule<IntersectReorderRule.Config>
42+
implements SubstitutionRule {
43+
/** Creates an IntersectReorderRule. */
44+
protected IntersectReorderRule(Config config) {
45+
super(config);
46+
}
47+
48+
@Override public void onMatch(RelOptRuleCall call) {
49+
final Intersect intersect = call.rel(0);
50+
final RelMetadataQuery mq = call.getMetadataQuery();
51+
final List<RelNode> inputs = intersect.getInputs();
52+
53+
final List<Pair<RelNode, Double>> inputsWithRowCounts = new ArrayList<>();
54+
55+
for (RelNode input : inputs) {
56+
Double rowCount = mq.getRowCount(input);
57+
inputsWithRowCounts.add(Pair.of(input, rowCount));
58+
}
59+
60+
inputsWithRowCounts.sort((a, b) -> Double.compare(a.right, b.right));
61+
62+
boolean needsReorder = false;
63+
for (int i = 0; i < inputs.size(); i++) {
64+
if (inputs.get(i) != inputsWithRowCounts.get(i).left) {
65+
needsReorder = true;
66+
break;
67+
}
68+
}
69+
70+
if (!needsReorder) {
71+
return;
72+
}
73+
74+
final List<RelNode> newInputs = inputsWithRowCounts.stream()
75+
.map(pair -> pair.left)
76+
.collect(Collectors.toList());
77+
78+
final RelBuilder relBuilder = call.builder();
79+
relBuilder.pushAll(newInputs);
80+
relBuilder.intersect(intersect.all, newInputs.size());
81+
82+
call.transformTo(relBuilder.build());
83+
}
84+
85+
/** Rule configuration. */
86+
@Value.Immutable
87+
public interface Config extends RelRule.Config {
88+
Config DEFAULT = ImmutableIntersectReorderRule.Config.of()
89+
.withOperandSupplier(b0 ->
90+
b0.operand(LogicalIntersect.class)
91+
.predicate(intersect -> intersect.getInputs().size() > 1)
92+
.anyInputs())
93+
.withDescription("IntersectReorderRule");
94+
95+
@Override default IntersectReorderRule toRule() {
96+
return new IntersectReorderRule(this);
97+
}
98+
}
99+
}

core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java

+31
Original file line numberDiff line numberDiff line change
@@ -10048,4 +10048,35 @@ private void checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
1004810048
sql(sql).withRule(CoreRules.INTERSECT_TO_EXISTS)
1004910049
.checkUnchanged();
1005010050
}
10051+
10052+
/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6891">
10053+
* [CALCITE-6891] Implement IntersectReorderRule</a>. */
10054+
@Test void testIntersectReorderRule() {
10055+
final String sql = "select deptno from emp\n"
10056+
+ "intersect\n"
10057+
+ "select deptno from dept\n";
10058+
10059+
sql(sql).withVolcanoPlanner(true, p -> {
10060+
p.addRule(CoreRules.INTERSECT_REORDER);
10061+
p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
10062+
p.addRule(EnumerableRules.ENUMERABLE_INTERSECT_RULE);
10063+
p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
10064+
p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
10065+
}).check();
10066+
}
10067+
10068+
/** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6891">
10069+
* [CALCITE-6891] Implement IntersectReorderRule</a>. */
10070+
@Test void testIntersectReorderRuleSameRowCount() {
10071+
final String sql = "select deptno from emp where deptno > 10\n"
10072+
+ "intersect\n"
10073+
+ "select deptno from emp where deptno > 5\n";
10074+
sql(sql).withVolcanoPlanner(true, p -> {
10075+
p.addRule(CoreRules.INTERSECT_REORDER);
10076+
p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
10077+
p.addRule(EnumerableRules.ENUMERABLE_INTERSECT_RULE);
10078+
p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
10079+
p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
10080+
}).check();
10081+
}
1005110082
}

core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml

+56
Original file line numberDiff line numberDiff line change
@@ -5978,6 +5978,62 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], T=[$10])
59785978
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[7934], $f10=[CURRENT_TIMESTAMP])
59795979
LogicalFilter(condition=[=($0, 7934)])
59805980
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
5981+
]]>
5982+
</Resource>
5983+
</TestCase>
5984+
<TestCase name="testIntersectReorderRule">
5985+
<Resource name="sql">
5986+
<![CDATA[select deptno from emp
5987+
intersect
5988+
select deptno from dept
5989+
]]>
5990+
</Resource>
5991+
<Resource name="planBefore">
5992+
<![CDATA[
5993+
LogicalIntersect(all=[false])
5994+
LogicalProject(DEPTNO=[$7])
5995+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
5996+
LogicalProject(DEPTNO=[$0])
5997+
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
5998+
]]>
5999+
</Resource>
6000+
<Resource name="planAfter">
6001+
<![CDATA[
6002+
EnumerableIntersect(all=[false])
6003+
EnumerableProject(DEPTNO=[$0])
6004+
EnumerableTableScan(table=[[CATALOG, SALES, DEPT]])
6005+
EnumerableProject(DEPTNO=[$7])
6006+
EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
6007+
]]>
6008+
</Resource>
6009+
</TestCase>
6010+
<TestCase name="testIntersectReorderRuleSameRowCount">
6011+
<Resource name="sql">
6012+
<![CDATA[select deptno from emp where deptno > 10
6013+
intersect
6014+
select deptno from emp where deptno > 5
6015+
]]>
6016+
</Resource>
6017+
<Resource name="planBefore">
6018+
<![CDATA[
6019+
LogicalIntersect(all=[false])
6020+
LogicalProject(DEPTNO=[$7])
6021+
LogicalFilter(condition=[>($7, 10)])
6022+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
6023+
LogicalProject(DEPTNO=[$7])
6024+
LogicalFilter(condition=[>($7, 5)])
6025+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
6026+
]]>
6027+
</Resource>
6028+
<Resource name="planAfter">
6029+
<![CDATA[
6030+
EnumerableIntersect(all=[false])
6031+
EnumerableProject(DEPTNO=[$7])
6032+
EnumerableFilter(condition=[>($7, 10)])
6033+
EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
6034+
EnumerableProject(DEPTNO=[$7])
6035+
EnumerableFilter(condition=[>($7, 5)])
6036+
EnumerableTableScan(table=[[CATALOG, SALES, EMP]])
59816037
]]>
59826038
</Resource>
59836039
</TestCase>

0 commit comments

Comments
 (0)