diff --git a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java index 20683053210..17f707cee1e 100644 --- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java +++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java @@ -559,7 +559,7 @@ assert rowTypesAreEquivalent( final List attempted = new ArrayList<>(); List> substitutions = new ArrayList<>(); - + int globalInputIndex = 0; for (;;) { int count = 0; MutableRel queryDescendant = query; @@ -575,9 +575,11 @@ assert rowTypesAreEquivalent( } } final MutableRel next = MutableRels.preOrderTraverseNext(queryDescendant); + final int currentInputIndex = queryDescendant.getInputs().size() > globalInputIndex + ? globalInputIndex : 0; final MutableRel childOrNext = queryDescendant.getInputs().isEmpty() - ? next : queryDescendant.getInputs().get(0); + ? next : queryDescendant.getInputs().get(currentInputIndex); for (MutableRel targetDescendant : targetDescendants) { for (UnifyRule rule : applicableRules(queryDescendant, targetDescendant)) { @@ -616,6 +618,7 @@ assert rowTypesAreEquivalent( substitutions.add(ImmutableList.copyOf(attempted)); attempted.clear(); queryDescendant = next; + globalInputIndex++; continue outer; } // We will try walking the query tree all over again to see diff --git a/core/src/test/java/org/apache/calcite/test/MaterializedViewSubstitutionVisitorTest.java b/core/src/test/java/org/apache/calcite/test/MaterializedViewSubstitutionVisitorTest.java index dca173b5719..c38fedc5638 100644 --- a/core/src/test/java/org/apache/calcite/test/MaterializedViewSubstitutionVisitorTest.java +++ b/core/src/test/java/org/apache/calcite/test/MaterializedViewSubstitutionVisitorTest.java @@ -1849,6 +1849,43 @@ protected final MaterializedViewFixture sql(String materialize, sql(mv2, query2).ok(); } + /** Test case for + * [CALCITE-6193] + * If a query has more than one subexpression that matches a materialized view, + * only the first is substituted. */ + @Test void testStopTryIncorrectSubtree() { + final String mv = "" + + "select \"empid\", \"deptno\"\n" + + "from \"emps\"\n" + + "group by \"empid\", \"deptno\""; + final String matchQuery = "select \"deptno\"\n" + + "from \"emps\"\n" + + "group by \"deptno\"\n"; + final String query = "" + + "select t1.\"deptno\"\n" + + "from (\n" + + "select \"deptno\"\n" + + "from \"emps\"\n" + + "union all\n" + + matchQuery + + ") as t1 inner join (\n" + + matchQuery + + ") as t2 on t1.\"deptno\" = t2.\"deptno\"\n"; + sql(mv, query) + .checkingThatResultContains("" + + "LogicalCalc(expr#0..1=[{inputs}], deptno=[$t0])\n" + + " LogicalJoin(condition=[=($0, $1)], joinType=[inner])\n" + + " LogicalUnion(all=[true])\n" + + " LogicalCalc(expr#0..1=[{inputs}], deptno=[$t1])\n" + + " LogicalCalc(expr#0..4=[{inputs}], proj#0..1=[{exprs}])\n" + + " LogicalTableScan(table=[[hr, emps]])\n" + + " LogicalAggregate(group=[{1}])\n" + + " EnumerableTableScan(table=[[hr, MV0]])\n" + + " LogicalAggregate(group=[{1}])\n" + + " EnumerableTableScan(table=[[hr, MV0]])") + .ok(); + } + /** Fixture for tests for whether expressions are satisfiable, * specifically {@link SubstitutionVisitor#mayBeSatisfiable(RexNode)}. */ private static class SatisfiabilityFixture {