Skip to content

Commit aa11fd6

Browse files
xiedeyantumihaibudiu
authored andcommitted
[CALCITE-6973] Add rule to convert Minus to Filter
1 parent 36f6ddd commit aa11fd6

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ private CoreRules() {}
383383
public static final IntersectToSemiJoinRule INTERSECT_TO_SEMI_JOIN =
384384
IntersectToSemiJoinRule.Config.DEFAULT.toRule();
385385

386+
/** Rule that translates a {@link Minus} to {@link Filter}. */
387+
public static final MinusToFilterRule MINUS_TO_FILTER =
388+
MinusToFilterRule.Config.DEFAULT.toRule();
389+
386390
/** Rule that translates a distinct
387391
* {@link Minus} into a group of operators
388392
* composed of {@link Union}, {@link Aggregate}, etc. */
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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.Filter;
23+
import org.apache.calcite.rel.core.Minus;
24+
import org.apache.calcite.rex.RexNode;
25+
import org.apache.calcite.rex.RexUtil;
26+
import org.apache.calcite.tools.RelBuilder;
27+
28+
import org.immutables.value.Value;
29+
30+
/**
31+
* Rule that replaces {@link Minus} operator with {@link Filter}
32+
* when both inputs are from the same source with only filter conditions differing.
33+
* Only inspect a single {@link Filter} layer in the inputs of {@link Minus}.
34+
* For inputs with nested {@link Filter}s, apply {@link CoreRules#FILTER_MERGE}
35+
* as a preprocessing step.
36+
*
37+
* <p>Example transformation:
38+
* <blockquote><pre>
39+
* SELECT mgr, comm FROM emp WHERE mgr = 12
40+
* EXCEPT
41+
* SELECT mgr, comm FROM emp WHERE comm = 5
42+
*
43+
* to
44+
*
45+
* SELECT DISTINCT mgr, comm FROM emp
46+
* WHERE mgr = 12 AND NOT(comm = 5)
47+
* </pre></blockquote>
48+
*/
49+
@Value.Enclosing
50+
public class MinusToFilterRule
51+
extends RelRule<MinusToFilterRule.Config>
52+
implements TransformationRule {
53+
54+
/** Creates an MinusToFilterRule. */
55+
protected MinusToFilterRule(Config config) {
56+
super(config);
57+
}
58+
59+
//~ Methods ----------------------------------------------------------------
60+
61+
@Override public void onMatch(RelOptRuleCall call) {
62+
final Minus minus = call.rel(0);
63+
if (minus.all || minus.getInputs().size() != 2) {
64+
return;
65+
}
66+
final RelBuilder builder = call.builder();
67+
final RelNode leftInput = call.rel(1);
68+
final Filter rightInput = call.rel(2);
69+
70+
if (!RexUtil.isDeterministic(rightInput.getCondition())) {
71+
return;
72+
}
73+
74+
RelNode leftBase;
75+
RexNode leftCond = null;
76+
if (leftInput instanceof Filter) {
77+
Filter leftFilter = (Filter) leftInput;
78+
leftBase = leftFilter.getInput().stripped();
79+
leftCond = leftFilter.getCondition();
80+
} else {
81+
leftBase = leftInput.stripped();
82+
}
83+
84+
final RelNode rightBase = rightInput.getInput().stripped();
85+
if (!leftBase.equals(rightBase)) {
86+
return;
87+
}
88+
89+
// Right input is Filter, right cond should be not null
90+
final RexNode finalCond = leftCond != null
91+
? builder.and(leftCond, builder.not(rightInput.getCondition()))
92+
: builder.not(rightInput.getCondition());
93+
94+
builder.push(leftBase)
95+
.filter(finalCond)
96+
.distinct();
97+
98+
call.transformTo(builder.build());
99+
}
100+
101+
/** Rule configuration. */
102+
@Value.Immutable
103+
public interface Config extends RelRule.Config {
104+
Config DEFAULT = ImmutableMinusToFilterRule.Config.of()
105+
.withOperandFor(Minus.class, RelNode.class, Filter.class);
106+
107+
@Override default MinusToFilterRule toRule() {
108+
return new MinusToFilterRule(this);
109+
}
110+
111+
/** Defines an operand tree for the given classes. */
112+
default Config withOperandFor(Class<? extends Minus> minusClass,
113+
Class<? extends RelNode> relNodeClass, Class<? extends Filter> filterClass) {
114+
return withOperandSupplier(
115+
b0 -> b0.operand(minusClass).inputs(
116+
b1 -> b1.operand(relNodeClass).anyInputs(),
117+
b2 -> b2.operand(filterClass).anyInputs()))
118+
.as(Config.class);
119+
}
120+
}
121+
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10421,4 +10421,30 @@ private void checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
1042110421
.withRule(CoreRules.JOIN_CONDITION_PUSH)
1042210422
.check();
1042310423
}
10424+
10425+
/** Test case of
10426+
* <a href="https://issues.apache.org/jira/browse/CALCITE-6973">[CALCITE-6973]
10427+
* Add rule for convert Minus to Filter</a>. */
10428+
@Test void testMinusToFilterRule() {
10429+
final String sql = "SELECT mgr, comm FROM emp WHERE mgr = 12\n"
10430+
+ "EXCEPT\n"
10431+
+ "SELECT mgr, comm FROM emp WHERE comm = 5\n";
10432+
sql(sql)
10433+
.withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
10434+
.withRule(CoreRules.MINUS_TO_FILTER)
10435+
.check();
10436+
}
10437+
10438+
/** Test case of
10439+
* <a href="https://issues.apache.org/jira/browse/CALCITE-6973">[CALCITE-6973]
10440+
* Add rule for convert Minus to Filter</a>. */
10441+
@Test void testMinusToFilterRule2() {
10442+
final String sql = "SELECT mgr, comm FROM emp\n"
10443+
+ "EXCEPT\n"
10444+
+ "SELECT mgr, comm FROM emp WHERE comm = 5\n";
10445+
sql(sql)
10446+
.withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
10447+
.withRule(CoreRules.MINUS_TO_FILTER)
10448+
.check();
10449+
}
1042410450
}

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9012,6 +9012,59 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2])
90129012
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], $f3=[1])
90139013
LogicalFilter(condition=[=($7, 30)])
90149014
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9015+
]]>
9016+
</Resource>
9017+
</TestCase>
9018+
<TestCase name="testMinusToFilterRule">
9019+
<Resource name="sql">
9020+
<![CDATA[SELECT mgr, comm FROM emp WHERE mgr = 12
9021+
EXCEPT
9022+
SELECT mgr, comm FROM emp WHERE comm = 5
9023+
]]>
9024+
</Resource>
9025+
<Resource name="planBefore">
9026+
<![CDATA[
9027+
LogicalMinus(all=[false])
9028+
LogicalFilter(condition=[=($0, 12)])
9029+
LogicalProject(MGR=[$3], COMM=[$6])
9030+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9031+
LogicalFilter(condition=[=($1, 5)])
9032+
LogicalProject(MGR=[$3], COMM=[$6])
9033+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9034+
]]>
9035+
</Resource>
9036+
<Resource name="planAfter">
9037+
<![CDATA[
9038+
LogicalAggregate(group=[{0, 1}])
9039+
LogicalFilter(condition=[AND(=($0, 12), <>($1, 5))])
9040+
LogicalProject(MGR=[$3], COMM=[$6])
9041+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9042+
]]>
9043+
</Resource>
9044+
</TestCase>
9045+
<TestCase name="testMinusToFilterRule2">
9046+
<Resource name="planBefore">
9047+
<![CDATA[
9048+
LogicalMinus(all=[false])
9049+
LogicalProject(MGR=[$3], COMM=[$6])
9050+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9051+
LogicalFilter(condition=[=($1, 5)])
9052+
LogicalProject(MGR=[$3], COMM=[$6])
9053+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9054+
]]>
9055+
</Resource>
9056+
<Resource name="planAfter">
9057+
<![CDATA[
9058+
LogicalAggregate(group=[{0, 1}])
9059+
LogicalFilter(condition=[<>($1, 5)])
9060+
LogicalProject(MGR=[$3], COMM=[$6])
9061+
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
9062+
]]>
9063+
</Resource>
9064+
<Resource name="sql">
9065+
<![CDATA[SELECT mgr, comm FROM emp
9066+
EXCEPT
9067+
SELECT mgr, comm FROM emp WHERE comm = 5
90159068
]]>
90169069
</Resource>
90179070
</TestCase>

0 commit comments

Comments
 (0)