You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jc...@apache.org on 2017/05/31 11:42:18 UTC

calcite git commit: [CALCITE-1802] Add post-aggregation step for Union in materialized view rewriting

Repository: calcite
Updated Branches:
  refs/heads/master f6af061ce -> c8462e1ac


[CALCITE-1802] Add post-aggregation step for Union in materialized view rewriting


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/c8462e1a
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/c8462e1a
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/c8462e1a

Branch: refs/heads/master
Commit: c8462e1acb01c0ff1b5d62a1ef318bfcc01301c0
Parents: f6af061
Author: Jesus Camacho Rodriguez <jc...@apache.org>
Authored: Wed May 31 12:38:58 2017 +0100
Committer: Jesus Camacho Rodriguez <jc...@apache.org>
Committed: Wed May 31 12:42:05 2017 +0100

----------------------------------------------------------------------
 .../rel/rules/AbstractMaterializedViewRule.java | 110 +++++++++++++++----
 .../calcite/test/MaterializationTest.java       |  33 ++++++
 site/_docs/materialized_views.md                |  45 +++++++-
 3 files changed, 164 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/c8462e1a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
index bb352d2..d9e7aa6 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AbstractMaterializedViewRule.java
@@ -426,25 +426,9 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
               }
 
               // d. Generate final rewriting (union).
-              // We add a Project on top to ensure the output type of the expression.
-              RelBuilder builder = call.builder();
-              builder.push(unionInputQuery);
-              builder.push(unionInputView);
-              builder.union(true);
-              List<RexNode> exprList = new ArrayList<>(builder.peek().getRowType().getFieldCount());
-              List<String> nameList = new ArrayList<>(builder.peek().getRowType().getFieldCount());
-              for (int i = 0; i < builder.peek().getRowType().getFieldCount(); i++) {
-                // We can take unionInputQuery as it is query based.
-                RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
-                exprList.add(
-                    rexBuilder.ensureType(
-                        field.getType(),
-                        rexBuilder.makeInputRef(builder.peek(), i),
-                        true));
-                nameList.add(field.getName());
-              }
-              builder.project(exprList, nameList);
-              call.transformTo(builder.build());
+              final RelNode result = createUnion(call.builder(), rexBuilder,
+                  topProject, unionInputQuery, unionInputView);
+              call.transformTo(result);
             } else if (compensationPreds != null) {
               RexNode compensationColumnsEquiPred = compensationPreds.getLeft();
               RexNode otherCompensationPred = RexUtil.composeConjunction(
@@ -545,6 +529,14 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
       EquivalenceClasses viewEC, EquivalenceClasses queryEC);
 
   /**
+   * If the view will be used in a union rewriting, this method is responsible for
+   * generating the union and any other operator needed on top of it, e.g., a Project
+   * operator.
+   */
+  protected abstract RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder,
+      RelNode topProject, RelNode unionInputQuery, RelNode unionInputView);
+
+  /**
    * This method is responsible for rewriting the query using the given view query.
    *
    * <p>The input node is a Scan on the view table and possibly a compensation Filter
@@ -701,6 +693,27 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
       return relBuilder.build();
     }
 
+    @Override protected RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder,
+        RelNode topProject, RelNode unionInputQuery, RelNode unionInputView) {
+      relBuilder.push(unionInputQuery);
+      relBuilder.push(unionInputView);
+      relBuilder.union(true);
+      List<RexNode> exprList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
+      List<String> nameList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
+      for (int i = 0; i < relBuilder.peek().getRowType().getFieldCount(); i++) {
+        // We can take unionInputQuery as it is query based.
+        RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
+        exprList.add(
+            rexBuilder.ensureType(
+                field.getType(),
+                rexBuilder.makeInputRef(relBuilder.peek(), i),
+                true));
+        nameList.add(field.getName());
+      }
+      relBuilder.project(exprList, nameList);
+      return relBuilder.build();
+    }
+
     @Override protected RelNode unify(
         RelBuilder relBuilder,
         RexBuilder rexBuilder,
@@ -990,11 +1003,53 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
       // Generate query rewriting.
       relBuilder.push(aggregateInput);
       relBuilder.filter(simplify.simplify(queryCompensationPred));
-      RelNode result = aggregate.copy(
+      return aggregate.copy(
           aggregate.getTraitSet(), ImmutableList.of(relBuilder.build()));
+    }
+
+    @Override protected RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder,
+        RelNode topProject, RelNode unionInputQuery, RelNode unionInputView) {
+      // Union
+      relBuilder.push(unionInputQuery);
+      relBuilder.push(unionInputView);
+      relBuilder.union(true);
+      List<RexNode> exprList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
+      List<String> nameList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
+      for (int i = 0; i < relBuilder.peek().getRowType().getFieldCount(); i++) {
+        // We can take unionInputQuery as it is query based.
+        RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
+        exprList.add(
+            rexBuilder.ensureType(
+                field.getType(),
+                rexBuilder.makeInputRef(relBuilder.peek(), i),
+                true));
+        nameList.add(field.getName());
+      }
+      relBuilder.project(exprList, nameList);
+      // Rollup aggregate
+      Aggregate aggregate = (Aggregate) unionInputQuery;
+      final ImmutableBitSet groupSet = ImmutableBitSet.range(aggregate.getGroupCount());
+      final List<AggCall> aggregateCalls = new ArrayList<>();
+      for (int i = 0; i < aggregate.getAggCallList().size(); i++) {
+        AggregateCall aggCall = aggregate.getAggCallList().get(i);
+        aggregateCalls.add(
+            relBuilder.aggregateCall(
+                SubstitutionVisitor.getRollup(aggCall.getAggregation()),
+                aggCall.isDistinct(),
+                null,
+                aggCall.name,
+                ImmutableList.of(
+                    rexBuilder.makeInputRef(
+                        relBuilder.peek(), aggregate.getGroupCount() + i))));
+      }
+      RelNode result = relBuilder
+          .aggregate(relBuilder.groupKey(groupSet, false, null), aggregateCalls)
+          .build();
       if (topProject != null) {
+        // Top project
         return topProject.copy(topProject.getTraitSet(), ImmutableList.of(result));
       }
+      // Result
       return result;
     }
 
@@ -1071,12 +1126,17 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
           return null;
         }
       }
+      boolean containsDistinctAgg = false;
       for (int idx = 0; idx < queryAggregate.getAggCallList().size(); idx++) {
         if (references != null && !references.get(queryAggregate.getGroupCount() + idx)) {
           // Ignore
           continue;
         }
         AggregateCall queryAggCall = queryAggregate.getAggCallList().get(idx);
+        if (queryAggCall.filterArg >= 0) {
+          // Not supported currently
+          return null;
+        }
         List<Integer> queryAggCallIndexes = new ArrayList<>();
         for (int aggCallIdx : queryAggCall.getArgList()) {
           queryAggCallIndexes.add(m.get(aggCallIdx).iterator().next());
@@ -1086,7 +1146,8 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
           if (queryAggCall.getAggregation() != viewAggCall.getAggregation()
               || queryAggCall.isDistinct() != viewAggCall.isDistinct()
               || queryAggCall.getArgList().size() != viewAggCall.getArgList().size()
-              || queryAggCall.getType() != viewAggCall.getType()) {
+              || queryAggCall.getType() != viewAggCall.getType()
+              || viewAggCall.filterArg >= 0) {
             // Continue
             continue;
           }
@@ -1096,6 +1157,9 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
           }
           aggregateMapping.set(queryAggregate.getGroupCount() + idx,
               viewAggregate.getGroupCount() + j);
+          if (queryAggCall.isDistinct()) {
+            containsDistinctAgg = true;
+          }
           break;
         }
       }
@@ -1107,6 +1171,10 @@ public abstract class AbstractMaterializedViewRule extends RelOptRule {
           .build();
       if (queryAggregate.getGroupCount() != viewAggregate.getGroupCount()
           || matchModality == MatchModality.VIEW_PARTIAL) {
+        if (containsDistinctAgg) {
+          // Cannot rollup DISTINCT aggregate
+          return null;
+        }
         // Target is coarser level of aggregation. Generate an aggregate.
         rewritingMapping = Mappings.create(MappingType.FUNCTION,
             topViewProject != null ? topViewProject.getRowType().getFieldCount()

http://git-wip-us.apache.org/repos/asf/calcite/blob/c8462e1a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
----------------------------------------------------------------------
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 c09082f..115f0f3 100644
--- a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
+++ b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
@@ -1286,7 +1286,9 @@ public class MaterializationTest {
             + "group by \"dependents\".\"empid\"",
       HR_FKUK_MODEL,
       CalciteAssert.checkResultContains(
+          "EnumerableAggregate(group=[{0}])",
           "EnumerableUnion(all=[true])",
+          "EnumerableAggregate(group=[{2}])",
           "EnumerableTableScan(table=[[hr, m0]])",
           "expr#5=[10], expr#6=[>($t0, $t5)], expr#7=[11], expr#8=[<=($t0, $t7)]"));
   }
@@ -1328,7 +1330,9 @@ public class MaterializationTest {
             + "group by \"dependents\".\"empid\"",
       HR_FKUK_MODEL,
       CalciteAssert.checkResultContains(
+          "EnumerableAggregate(group=[{0}])",
           "EnumerableUnion(all=[true])",
+          "EnumerableAggregate(group=[{2}])",
           "EnumerableTableScan(table=[[hr, m0]])",
           "expr#13=[OR($t10, $t12)], expr#14=[AND($t6, $t8, $t13)]"));
   }
@@ -1495,6 +1499,35 @@ public class MaterializationTest {
                 + "    EnumerableTableScan(table=[[hr, depts]])"));
   }
 
+  @Test public void testJoinAggregateMaterializationAggregateFuncs9() {
+    checkMaterialize(
+        "select \"dependents\".\"empid\", \"emps\".\"deptno\", count(distinct \"salary\") as s\n"
+            + "from \"emps\"\n"
+            + "join \"dependents\" on (\"emps\".\"empid\" = \"dependents\".\"empid\")\n"
+            + "group by \"dependents\".\"empid\", \"emps\".\"deptno\"",
+        "select \"emps\".\"deptno\", count(distinct \"salary\") as s\n"
+            + "from \"emps\"\n"
+            + "join \"dependents\" on (\"emps\".\"empid\" = \"dependents\".\"empid\")\n"
+            + "group by \"dependents\".\"empid\", \"emps\".\"deptno\"",
+        HR_FKUK_MODEL,
+        CalciteAssert.checkResultContains(
+            "EnumerableCalc(expr#0..2=[{inputs}], deptno=[$t1], S=[$t2])\n"
+                + "  EnumerableTableScan(table=[[hr, m0]])"));
+  }
+
+  @Test public void testJoinAggregateMaterializationAggregateFuncs10() {
+    checkNoMaterialize(
+        "select \"dependents\".\"empid\", \"emps\".\"deptno\", count(distinct \"salary\") as s\n"
+            + "from \"emps\"\n"
+            + "join \"dependents\" on (\"emps\".\"empid\" = \"dependents\".\"empid\")\n"
+            + "group by \"dependents\".\"empid\", \"emps\".\"deptno\"",
+        "select \"emps\".\"deptno\", count(distinct \"salary\") as s\n"
+            + "from \"emps\"\n"
+            + "join \"dependents\" on (\"emps\".\"empid\" = \"dependents\".\"empid\")\n"
+            + "group by \"emps\".\"deptno\"",
+        HR_FKUK_MODEL);
+  }
+
   @Test public void testJoinMaterialization4() {
     checkMaterialize(
       "select \"empid\" \"deptno\" from \"emps\"\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/c8462e1a/site/_docs/materialized_views.md
----------------------------------------------------------------------
diff --git a/site/_docs/materialized_views.md b/site/_docs/materialized_views.md
index 0f25279..c950c2a 100644
--- a/site/_docs/materialized_views.md
+++ b/site/_docs/materialized_views.md
@@ -78,17 +78,17 @@ To produce a larger number of rewritings, the rule relies on information exposed
 Let us illustrate with some examples the coverage of the view rewriting algorithm implemented in `AbstractMaterializedViewRule`. The examples are based on the following database schema.
 
 ```SQL
-CREATE TABLE `depts`(
+CREATE TABLE depts(
   deptno INT NOT NULL,
   deptname VARCHAR(20),
   PRIMARY KEY (deptno)
 );
-CREATE TABLE `locations`(
+CREATE TABLE locations(
   locationid INT NOT NULL,
   state CHAR(2),
   PRIMARY KEY (locationid)
 );
-CREATE TABLE `emps`(
+CREATE TABLE emps(
   empid INT NOT NULL,
   deptno INT NOT NULL,
   locationid INT NOT NULL,
@@ -284,6 +284,45 @@ WHERE salary > 10000 AND salary <= 12000
 ```
 
 
+###### Union rewriting with aggregate
+
+* Query:
+
+```SQL
+SELECT empid, deptname, SUM(salary) AS s
+FROM emps
+JOIN depts ON (emps.deptno = depts.deptno)
+WHERE salary > 10000
+GROUP BY empid, deptname
+```
+
+* Materialized view definition:
+
+```SQL
+SELECT empid, deptname, SUM(salary) AS s
+FROM emps
+JOIN depts ON (emps.deptno = depts.deptno)
+WHERE salary > 12000
+GROUP BY empid, deptname
+```
+
+* Rewriting:
+
+```SQL
+SELECT empid, deptname, SUM(s)
+FROM (
+SELECT empid, deptname, s
+FROM mv
+UNION ALL
+SELECT empid, deptname, SUM(salary) AS s
+FROM emps
+JOIN depts ON (emps.deptno = depts.deptno)
+WHERE salary > 10000 AND salary <= 12000
+GROUP BY empid, deptname) subq
+GROUP BY empid, deptname
+```
+
+
 ##### Limitations
 
 This rule still presents some limitations. In particular, the rewriting rule attempts to match all views against each query. We plan to implement more refined filtering techniques such as those described in [<a href="#ref-gl01">GL01</a>].