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 edabf76d4f56..a5befa40d9f6 100644 --- a/core/src/test/java/org/apache/calcite/test/MaterializedViewSubstitutionVisitorTest.java +++ b/core/src/test/java/org/apache/calcite/test/MaterializedViewSubstitutionVisitorTest.java @@ -24,13 +24,16 @@ import org.apache.calcite.plan.hep.HepProgram; import org.apache.calcite.plan.hep.HepProgramBuilder; import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.logical.LogicalCalc; import org.apache.calcite.rel.rules.CoreRules; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rel.type.RelDataTypeSystem; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexInputRef; import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexProgramBuilder; import org.apache.calcite.rex.RexSimplify; import org.apache.calcite.rex.RexUtil; import org.apache.calcite.sql.fun.SqlStdOperatorTable; @@ -80,9 +83,20 @@ public class MaterializedViewSubstitutionVisitorTest { @Override protected List optimize(RelNode queryRel, List materializationList) { RelOptMaterialization materialization = materializationList.get(0); + RelNode materializedRel = canonicalize(materialization.queryRel); + RelNode normalQueryRel = canonicalize(queryRel); + if (!(normalQueryRel instanceof LogicalCalc) && materializedRel instanceof LogicalCalc) { + final RelDataType rowType = normalQueryRel.getRowType(); + final RexProgramBuilder programBuilder = + new RexProgramBuilder(rowType, normalQueryRel.getCluster().getRexBuilder()); + final List queryFields = rowType.getFieldList(); + for (int i = 0; i < queryFields.size(); i++) { + programBuilder.addProject(RexInputRef.of(i, rowType), queryFields.get(i).getName()); + } + normalQueryRel = LogicalCalc.create(normalQueryRel, programBuilder.getProgram()); + } SubstitutionVisitor substitutionVisitor = - new SubstitutionVisitor(canonicalize(materialization.queryRel), - canonicalize(queryRel)); + new SubstitutionVisitor(materializedRel, normalQueryRel); return substitutionVisitor .go(materialization.tableRel); } @@ -449,6 +463,66 @@ protected final MaterializedViewFixture sql(String materialize, sql(mv, query).noMat(); } + /** + * Need matching because query could be expressed by mv, using trim mv's unused field, + * which is the top of Aggregate. + */ + @Test void testAggregateWithCalcTopInMv() { + String mv = "" + + "select \"deptno\", sum(\"salary\"), sum(\"commission\") + 1, sum(\"k\")\n" + + "from\n" + + " (select \"deptno\", \"salary\", \"commission\", 100 as \"k\"\n" + + " from \"emps\")\n" + + "group by \"deptno\""; + String query = "" + + "select \"deptno\", sum(\"salary\"), sum(\"k\")\n" + + "from\n" + + " (select \"deptno\", \"salary\", 100 as \"k\"\n" + + " from \"emps\")\n" + + "group by \"deptno\""; + sql(mv, query).ok(); + } + + /** Similar with {@link #testAggregateWithCalcTopInMv()}, + * but target's fields have no-equal sequence. */ + @Test void testAggregateWithCalcTopInMv2() { + String mv = "" + + "select \"deptno\", sum(\"commission\") + 1, sum(\"k\"), sum(\"salary\")\n" + + "from\n" + + " (select \"deptno\", \"salary\", \"commission\", 100 as \"k\"\n" + + " from \"emps\")\n" + + "group by \"deptno\""; + String query = "" + + "select \"deptno\", sum(\"salary\"), sum(\"k\")\n" + + "from\n" + + " (select \"deptno\", \"salary\", 100 as \"k\"\n" + + " from \"emps\")\n" + + "group by \"deptno\""; + sql(mv, query).ok(); + } + + /** + * Need matching because query could be expressed by mv, using trim mv's unused field, + * which is the top of Union. + */ + @Test void testUnionWithCalcTopInMv() { + String mv = "" + + "select \"deptno\", \"salary\", 'hello' as \"k\"\n" + + "from (" + + "select \"deptno\", \"salary\"\n" + + "from \"emps\"\n" + + "union\n" + + "select \"deptno\", \"salary\"\n" + + "from \"emps\")"; + String query = "" + + "select \"deptno\", \"salary\"\n" + + "from \"emps\"\n" + + "union\n" + + "select \"deptno\", \"salary\"\n" + + "from \"emps\""; + sql(mv, query).ok(); + } + /** * There will be a compensating Project added after matching of the Aggregate. * This rule targets to test if the Calc can be handled. @@ -861,11 +935,14 @@ protected final MaterializedViewFixture sql(String materialize, String m = "select * from \"emps\" where \"empid\" < 500"; sql(m, q) .checkingThatResultContains("" - + "LogicalUnion(all=[true])\n" - + " LogicalCalc(expr#0..4=[{inputs}], expr#5=[300], expr#6=[>($t0, $t5)], proj#0..4=[{exprs}], $condition=[$t6])\n" - + " LogicalTableScan(table=[[hr, emps]])\n" - + " LogicalCalc(expr#0..4=[{inputs}], expr#5=[200], expr#6=[<($t0, $t5)], proj#0..4=[{exprs}], $condition=[$t6])\n" - + " EnumerableTableScan(table=[[hr, MV0]])") + + "LogicalCalc(expr#0..4=[{inputs}], proj#0..4=[{exprs}])\n" + + " LogicalUnion(all=[true])\n" + + " LogicalCalc(expr#0..4=[{inputs}], expr#5=[300], expr#6=[>($t0, $t5)], proj#0." + + ".4=[{exprs}], $condition=[$t6])\n" + + " LogicalTableScan(table=[[hr, emps]])\n" + + " LogicalCalc(expr#0..4=[{inputs}], expr#5=[200], expr#6=[<($t0, $t5)], proj#0." + + ".4=[{exprs}], $condition=[$t6])\n" + + " EnumerableTableScan(table=[[hr, MV0]])") .ok(); }