You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2016/11/12 22:05:53 UTC
calcite git commit: [CALCITE-1489] Add rule, AggregateValuesRule,
that applies to an Aggregate on an empty relation (Gian Merlino)
Repository: calcite
Updated Branches:
refs/heads/master b7e99bca9 -> 3f92157d5
[CALCITE-1489] Add rule, AggregateValuesRule, that applies to an Aggregate on an empty relation (Gian Merlino)
Close apache/calcite#324
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/3f92157d
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/3f92157d
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/3f92157d
Branch: refs/heads/master
Commit: 3f92157d5742dd10f3b828d22d7a753e0a2899cc
Parents: b7e99bc
Author: Gian Merlino <gi...@gmail.com>
Authored: Fri Nov 11 15:56:34 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Sat Nov 12 09:19:45 2016 -0800
----------------------------------------------------------------------
.../calcite/prepare/CalcitePrepareImpl.java | 4 +-
.../calcite/rel/rules/AggregateValuesRule.java | 100 +++++++++++++++++++
.../calcite/rel/rules/PruneEmptyRules.java | 2 +
.../apache/calcite/test/RelOptRulesTest.java | 15 +++
.../org/apache/calcite/test/RelOptTestBase.java | 22 ++--
.../org/apache/calcite/test/RelOptRulesTest.xml | 16 +++
6 files changed, 150 insertions(+), 9 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index cd2ad50..196254c 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -76,6 +76,7 @@ import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule;
import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
import org.apache.calcite.rel.rules.AggregateStarTableRule;
+import org.apache.calcite.rel.rules.AggregateValuesRule;
import org.apache.calcite.rel.rules.FilterAggregateTransposeRule;
import org.apache.calcite.rel.rules.FilterJoinRule;
import org.apache.calcite.rel.rules.FilterProjectTransposeRule;
@@ -243,7 +244,8 @@ public class CalcitePrepareImpl implements CalcitePrepare {
ReduceExpressionsRule.JOIN_INSTANCE,
ValuesReduceRule.FILTER_INSTANCE,
ValuesReduceRule.PROJECT_FILTER_INSTANCE,
- ValuesReduceRule.PROJECT_INSTANCE);
+ ValuesReduceRule.PROJECT_INSTANCE,
+ AggregateValuesRule.INSTANCE);
public CalcitePrepareImpl() {
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
new file mode 100644
index 0000000..b921dd9
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rel.rules;
+
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.Util;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Rule that applies {@link Aggregate} to a {@link Values} (currently just an
+ * empty {@code Value}s).
+ *
+ * <p>This is still useful because {@link PruneEmptyRules#AGGREGATE_INSTANCE}
+ * doesn't handle {@code Aggregate}, which is in turn because {@code Aggregate}
+ * of empty relations need some special handling: a single row will be
+ * generated, where each column's value depends on the specific aggregate calls
+ * (e.g. COUNT is 0, SUM is NULL).
+ *
+ * <p>Sample query where this matters:
+ *
+ * <blockquote><code>SELECT COUNT(*) FROM s.foo WHERE 1 = 0</code></blockquote>
+ *
+ * <p>This rule only applies to "grand totals", that is, {@code GROUP BY ()}.
+ * Any non-empty {@code GROUP BY} clause will return one row per group key
+ * value, and each group will consist of at least one row.
+ */
+public class AggregateValuesRule extends RelOptRule {
+ public static final AggregateValuesRule INSTANCE = new AggregateValuesRule();
+
+ private AggregateValuesRule() {
+ super(
+ operand(Aggregate.class, null, Predicates.not(Aggregate.IS_NOT_GRAND_TOTAL),
+ operand(Values.class, null, Values.IS_EMPTY, none())));
+ }
+
+ @Override public void onMatch(RelOptRuleCall call) {
+ final Aggregate aggregate = call.rel(0);
+ final Values values = call.rel(1);
+ Util.discard(values);
+ final RelBuilder relBuilder = call.builder();
+ final RexBuilder rexBuilder = relBuilder.getRexBuilder();
+
+ final List<RexLiteral> literals = new ArrayList<>();
+ for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+ switch (aggregateCall.getAggregation().getKind()) {
+ case COUNT:
+ case SUM0:
+ literals.add((RexLiteral) rexBuilder.makeLiteral(
+ BigDecimal.ZERO, aggregateCall.getType(), false));
+ break;
+
+ case MIN:
+ case MAX:
+ case SUM:
+ literals.add(rexBuilder.constantNull());
+ break;
+
+ default:
+ // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+ return;
+ }
+ }
+
+ call.transformTo(
+ relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+ .build());
+
+ // New plan is absolutely better than old plan.
+ call.getPlanner().setImportance(aggregate, 0.0);
+ }
+}
+
+// End AggregateValuesRule.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
index 094c290..51aefe1 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/PruneEmptyRules.java
@@ -265,6 +265,8 @@ public abstract class PruneEmptyRules {
* <li>{@code Aggregate(key: [], Empty)} is unchanged, because an aggregate
* without a GROUP BY key always returns 1 row, even over empty input
* </ul>
+ *
+ * @see AggregateValuesRule
*/
public static final RelOptRule AGGREGATE_INSTANCE =
new RemoveEmptySingleRule(Aggregate.class, Aggregate.IS_NOT_GRAND_TOTAL,
http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
----------------------------------------------------------------------
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 8ca9130..82269b3 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -47,6 +47,7 @@ import org.apache.calcite.rel.rules.AggregateProjectPullUpConstantsRule;
import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
import org.apache.calcite.rel.rules.AggregateUnionAggregateRule;
import org.apache.calcite.rel.rules.AggregateUnionTransposeRule;
+import org.apache.calcite.rel.rules.AggregateValuesRule;
import org.apache.calcite.rel.rules.CalcMergeRule;
import org.apache.calcite.rel.rules.CoerceInputsRule;
import org.apache.calcite.rel.rules.DateRangeRules;
@@ -1705,6 +1706,20 @@ public class RelOptRulesTest extends RelOptTestBase {
checkPlanning(tester, preProgram, new HepPlanner(program), sql, unchanged);
}
+ @Test public void testEmptyAggregateEmptyKeyWithAggregateValuesRule() {
+ HepProgram preProgram = HepProgram
+ .builder()
+ .addRuleInstance(ReduceExpressionsRule.FILTER_INSTANCE)
+ .addRuleInstance(PruneEmptyRules.PROJECT_INSTANCE)
+ .build();
+ HepProgram program = new HepProgramBuilder()
+ .addRuleInstance(AggregateValuesRule.INSTANCE)
+ .build();
+
+ final String sql = "select count(*), sum(empno) from emp where false";
+ sql(sql).withPre(preProgram).with(program).check();
+ }
+
@Test public void testReduceCasts() throws Exception {
HepProgram program = new HepProgramBuilder()
.addRuleInstance(ReduceExpressionsRule.PROJECT_INSTANCE)
http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java b/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
index e01fd5b..4c57833 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptTestBase.java
@@ -176,37 +176,43 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
/** Sets the SQL statement for a test. */
Sql sql(String sql) {
- return new Sql(sql, null, true, ImmutableMap.<Hook, Function>of());
+ return new Sql(sql, null, null, true, ImmutableMap.<Hook, Function>of());
}
/** Allows fluent testing. */
class Sql {
private final String sql;
+ private HepProgram preProgram;
private final HepPlanner hepPlanner;
private final boolean expand;
private final ImmutableMap<Hook, Function> hooks;
- Sql(String sql, HepPlanner hepPlanner, boolean expand,
- ImmutableMap<Hook, Function> hooks) {
+ Sql(String sql, HepProgram preProgram, HepPlanner hepPlanner,
+ boolean expand, ImmutableMap<Hook, Function> hooks) {
this.sql = sql;
+ this.preProgram = preProgram;
this.hepPlanner = hepPlanner;
this.expand = expand;
this.hooks = hooks;
}
+ public Sql withPre(HepProgram preProgram) {
+ return new Sql(sql, preProgram, hepPlanner, expand, hooks);
+ }
+
public Sql with(HepPlanner hepPlanner) {
- return new Sql(sql, hepPlanner, expand, hooks);
+ return new Sql(sql, preProgram, hepPlanner, expand, hooks);
}
public Sql with(HepProgram program) {
- return new Sql(sql, new HepPlanner(program), expand, hooks);
+ return new Sql(sql, preProgram, new HepPlanner(program), expand, hooks);
}
/** Adds a hook and a handler for that hook. Calcite will create a thread
* hook (by calling {@link Hook#addThread(com.google.common.base.Function)})
* just before running the query, and remove the hook afterwards. */
public <T> Sql withHook(Hook hook, Function<T, Void> handler) {
- return new Sql(sql, hepPlanner, expand,
+ return new Sql(sql, preProgram, hepPlanner, expand,
ImmutableMap.<Hook, Function>builder().putAll(hooks)
.put(hook, handler).build());
}
@@ -216,7 +222,7 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
}
public Sql expand(boolean expand) {
- return new Sql(sql, hepPlanner, expand, hooks);
+ return new Sql(sql, preProgram, hepPlanner, expand, hooks);
}
public void check() {
@@ -232,7 +238,7 @@ abstract class RelOptTestBase extends SqlToRelTestBase {
for (Map.Entry<Hook, Function> entry : hooks.entrySet()) {
closer.add(entry.getKey().addThread(entry.getValue()));
}
- checkPlanning(tester.withExpand(expand), null, hepPlanner, sql,
+ checkPlanning(tester.withExpand(expand), preProgram, hepPlanner, sql,
unchanged);
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/3f92157d/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
----------------------------------------------------------------------
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 92fa0f7..6c97d6a 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -208,6 +208,22 @@ LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])
]]>
</Resource>
</TestCase>
+ <TestCase name="testEmptyAggregateEmptyKeyWithAggregateValuesRule">
+ <Resource name="sql">
+ <![CDATA[select count(*), sum(empno) from emp where false]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalAggregate(group=[{}], EXPR$0=[COUNT()], EXPR$1=[SUM($0)])
+ LogicalValues(tuples=[[]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalValues(tuples=[[{ 0, null }]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testEmptyIntersect">
<Resource name="sql">
<![CDATA[select * from (values (30, 3))intersect