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 0984230ad3fd..acb238f5b009 100644 --- a/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java +++ b/core/src/main/java/org/apache/calcite/plan/SubstitutionVisitor.java @@ -27,6 +27,7 @@ import org.apache.calcite.rel.mutable.MutableAggregate; import org.apache.calcite.rel.mutable.MutableCalc; import org.apache.calcite.rel.mutable.MutableFilter; +import org.apache.calcite.rel.mutable.MutableIntersect; import org.apache.calcite.rel.mutable.MutableJoin; import org.apache.calcite.rel.mutable.MutableRel; import org.apache.calcite.rel.mutable.MutableRelVisitor; @@ -134,7 +135,8 @@ public class SubstitutionVisitor { AggregateToAggregateUnifyRule.INSTANCE, AggregateOnCalcToAggregateUnifyRule.INSTANCE, UnionToUnionUnifyRule.INSTANCE, - UnionOnCalcsToUnionUnifyRule.INSTANCE); + UnionOnCalcsToUnionUnifyRule.INSTANCE, + IntersectToIntersectUnifyRule.INSTANCE); /** * Factory for a builder for relational expressions. @@ -1630,6 +1632,32 @@ && sameRelCollectionNoOrderConsidered(queryGrandInputs, targetInputs)) { } } + /** + * A {@link SubstitutionVisitor.UnifyRule} that matches a + * {@link MutableIntersect} to a {@link MutableIntersect} where the query and target + * have the same inputs but might not have the same order. + */ + private static class IntersectToIntersectUnifyRule extends AbstractUnifyRule { + public static final IntersectToIntersectUnifyRule INSTANCE = + new IntersectToIntersectUnifyRule(); + + private IntersectToIntersectUnifyRule() { + super(any(MutableIntersect.class), any(MutableIntersect.class), 0); + } + + public UnifyResult apply(UnifyRuleCall call) { + final MutableIntersect query = (MutableIntersect) call.query; + final MutableIntersect target = (MutableIntersect) call.target; + final List queryInputs = new ArrayList<>(query.getInputs()); + final List targetInputs = new ArrayList<>(target.getInputs()); + if (query.isAll() == target.isAll() + && sameRelCollectionNoOrderConsidered(queryInputs, targetInputs)) { + return call.result(target); + } + return null; + } + } + /** Check if list0 and list1 contains the same nodes -- order is not considered. */ private static boolean sameRelCollectionNoOrderConsidered( List list0, List list1) { diff --git a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java index b8a1db193f8e..c990a482d123 100644 --- a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java +++ b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java @@ -2837,6 +2837,7 @@ private void checkSatisfiable(RexNode e, String s) { checkNoMaterialize(sql0 + " union " + sql1, sql0 + " union all " + sql1, HR_FKUK_MODEL); } + @Test public void testUnionOnCalcsToUnion() { String mv = "" + "select \"deptno\", \"salary\"\n" @@ -2857,6 +2858,30 @@ private void checkSatisfiable(RexNode e, String s) { checkMaterialize(mv, query); } + @Test public void testIntersectToIntersect0() { + final String mv = "" + + "select \"deptno\" from \"emps\"\n" + + "intersect\n" + + "select \"deptno\" from \"depts\""; + final String query = "" + + "select \"deptno\" from \"depts\"\n" + + "intersect\n" + + "select \"deptno\" from \"emps\""; + checkMaterialize(mv, query, true); + } + + @Test public void testIntersectToIntersect1() { + final String mv = "" + + "select \"deptno\" from \"emps\"\n" + + "intersect all\n" + + "select \"deptno\" from \"depts\""; + final String query = "" + + "select \"deptno\" from \"depts\"\n" + + "intersect all\n" + + "select \"deptno\" from \"emps\""; + checkMaterialize(mv, query, true); + } + private static List>> list3(E[][][] as) { final ImmutableList.Builder>> builder = ImmutableList.builder();