This is an automated email from the ASF dual-hosted git repository.
zhenchen 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 860ec68813 [CALCITE-7302] Infinite loop with
JoinPushTransitivePredicatesRule
860ec68813 is described below
commit 860ec68813adc56694195b79c26292edfcf5d652
Author: Zhen Chen <[email protected]>
AuthorDate: Sat Nov 22 23:32:14 2025 +0800
[CALCITE-7302] Infinite loop with JoinPushTransitivePredicatesRule
---
.../calcite/rel/metadata/RelMdPredicates.java | 60 ++++++++++++++++++----
.../org/apache/calcite/test/RelOptRulesTest.java | 13 +++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 33 ++++++++++--
3 files changed, 92 insertions(+), 14 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index 44b3f1c1d5..11a6705158 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -710,30 +710,55 @@ static class JoinConditionBasedPredicateInference {
Mappings.TargetMapping leftMapping =
Mappings.createShiftMapping(nSysFields + nFieldsLeft, nSysFields,
0,
nFieldsLeft);
- leftChildPredicates =
+ RexNode lcp =
leftPredicates.accept(
new RexPermuteInputsShuttle(leftMapping, joinRel.getInput(0)));
+ leftChildPredicates = lcp;
- allExprs.add(leftChildPredicates);
- for (RexNode r : RelOptUtil.conjunctions(leftChildPredicates)) {
- exprFields.put(r, RelOptUtil.InputFinder.bits(r));
- allExprs.add(r);
+ if (lcp != null) {
+ allExprs.add(lcp);
+ for (RexNode r : RelOptUtil.conjunctions(lcp)) {
+ exprFields.put(r, RelOptUtil.InputFinder.bits(r));
+ allExprs.add(r);
+ }
+ RexNode simplified =
+ simplify.simplifyFilterPredicates(RelOptUtil.conjunctions(lcp));
+ if (simplified != null && !simplified.equals(lcp)) {
+ allExprs.add(simplified);
+ for (RexNode r : RelOptUtil.conjunctions(simplified)) {
+ exprFields.put(r, RelOptUtil.InputFinder.bits(r));
+ allExprs.add(r);
+ }
+ }
}
}
+
if (rightPredicates == null) {
rightChildPredicates = null;
} else {
Mappings.TargetMapping rightMapping =
Mappings.createShiftMapping(nSysFields + nFieldsLeft +
nFieldsRight,
nSysFields + nFieldsLeft, 0, nFieldsRight);
- rightChildPredicates =
+ RexNode rcp =
rightPredicates.accept(
new RexPermuteInputsShuttle(rightMapping,
joinRel.getInput(1)));
+ rightChildPredicates = rcp;
- allExprs.add(rightChildPredicates);
- for (RexNode r : RelOptUtil.conjunctions(rightChildPredicates)) {
- exprFields.put(r, RelOptUtil.InputFinder.bits(r));
- allExprs.add(r);
+ if (rcp != null) {
+ allExprs.add(rcp);
+ for (RexNode r : RelOptUtil.conjunctions(rcp)) {
+ exprFields.put(r, RelOptUtil.InputFinder.bits(r));
+ allExprs.add(r);
+ }
+ RexNode simplified =
+ simplify.simplifyFilterPredicates(RelOptUtil.conjunctions(rcp));
+ if (simplified != null && !simplified.equals(rcp)) {
+ allExprs.add(simplified);
+ for (RexNode r : RelOptUtil.conjunctions(simplified)) {
+ exprFields.put(r, RelOptUtil.InputFinder.bits(r));
+ allExprs.add(r);
+ }
+ }
}
}
@@ -864,7 +889,20 @@ public RelOptPredicateList inferPredicates(
private void infer(@Nullable RexNode predicates, Set<RexNode> allExprs,
List<RexNode> inferredPredicates, boolean includeEqualityInference,
ImmutableBitSet inferringFields) {
- for (RexNode r : RelOptUtil.conjunctions(predicates)) {
+ if (predicates == null) {
+ return;
+ }
+
+ // Normalize predicates by simplification to ensure consistent
deduplication.
+ // Prevents infinite loops when semantically equivalent predicates are
simplified
+ // to different forms (e.g., AND(>=, <=) to SEARCH).
+ RexNode normalizedPredicates =
+
simplify.simplifyFilterPredicates(RelOptUtil.conjunctions(predicates));
+ if (normalizedPredicates == null) {
+ return;
+ }
+
+ for (RexNode r : RelOptUtil.conjunctions(normalizedPredicates)) {
if (!includeEqualityInference
&& equalityPredicates.contains(r)) {
continue;
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 4369727f3f..2b429eac30 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -6521,6 +6521,19 @@ private HepProgram getTransitiveProgram() {
sql(sql).withPre(getTransitiveProgram()).withProgram(program).check();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7302">[CALCITE-7302]
+ * Infinite loop with JoinPushTransitivePredicatesRule</a>. */
+ @Test void testInfiniteLoopWithBetweenAnd() {
+ final String sql = "With dept_temp as"
+ + " (SELECT deptno, name FROM dept where deptno between 30 and 50),"
+ + "emp_temp as"
+ + " (Select ename, deptno from emp)"
+ + "select * from dept_temp inner join emp_temp on dept_temp.deptno =
emp_temp.deptno ";
+ sql(sql).withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES)
+ .check();
+ }
+
/** Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-2110">[CALCITE-2110]
* ArrayIndexOutOfBoundsException in RexSimplify when using
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 c63602eae6..bb53c538bd 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -6420,11 +6420,38 @@ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2],
MGR=[$3], HIREDATE=[$4], SAL=[$
<![CDATA[
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], EMPNO0=[$9], ENAME0=[$10],
JOB0=[$11], MGR0=[$12], HIREDATE0=[$13], SAL0=[$14], COMM0=[$15],
DEPTNO0=[$16], SLACKER0=[$17])
LogicalJoin(condition=[=($16, $7)], joinType=[inner])
+ LogicalFilter(condition=[NOT(OR(=($7, 4), =($7, 6)))])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalFilter(condition=[SEARCH($7, Sarg[(-∞..4), (4..6), (6..+∞)])])
- LogicalFilter(condition=[NOT(OR(=($7, 4), =($7, 6)))])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
- LogicalFilter(condition=[SEARCH($7, Sarg[(-∞..4), (4..6), (6..+∞)])])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testInfiniteLoopWithBetweenAnd">
+ <Resource name="sql">
+ <![CDATA[With dept_temp as (SELECT deptno, name FROM dept where deptno
between 30 and 50),emp_temp as (Select ename, deptno from emp)select * from
dept_temp inner join emp_temp on dept_temp.deptno = emp_temp.deptno ]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], ENAME=[$2], DEPTNO0=[$3])
+ LogicalJoin(condition=[=($0, $3)], joinType=[inner])
+ LogicalProject(DEPTNO=[$0], NAME=[$1])
+ LogicalFilter(condition=[AND(>=($0, 30), <=($0, 50))])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalProject(ENAME=[$1], DEPTNO=[$7])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], NAME=[$1], ENAME=[$2], DEPTNO0=[$3])
+ LogicalJoin(condition=[=($0, $3)], joinType=[inner])
+ LogicalProject(DEPTNO=[$0], NAME=[$1])
+ LogicalFilter(condition=[AND(>=($0, 30), <=($0, 50))])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalFilter(condition=[SEARCH($1, Sarg[[30..50]])])
+ LogicalProject(ENAME=[$1], DEPTNO=[$7])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>