This is an automated email from the ASF dual-hosted git repository.
tledkov pushed a commit to branch sql-calcite
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/sql-calcite by this push:
new ca387b5 IGNITE-14681 Calcite engine. Extend return type of sum()
aggregate function (#9148)
ca387b5 is described below
commit ca387b5c6a2ed6ca2439e0f65a904fa4fb925f6c
Author: Taras Ledkov <[email protected]>
AuthorDate: Wed Oct 20 18:37:16 2021 +0300
IGNITE-14681 Calcite engine. Extend return type of sum() aggregate function
(#9148)
---
.../query/calcite/exec/exp/agg/Accumulators.java | 232 +----
.../query/calcite/prepare/PlannerPhase.java | 2 +-
.../AggregateExpandDistinctAggregatesRule.java | 947 +++++++++++++++++++++
.../query/calcite/type/IgniteTypeSystem.java | 65 +-
.../query/calcite/CalciteQueryProcessorTest.java | 4 +-
.../query/calcite/exec/rel/BaseAggregateTest.java | 93 ++
.../integration/AggregatesIntegrationTest.java | 4 +-
.../integration/SortAggregateIntegrationTest.java | 7 +-
.../calcite/planner/AggregatePlannerTest.java | 113 +++
.../apache/ignite/testsuites/ScriptTestSuite.java | 2 +-
.../aggregates/test_aggr_string.test_ignore | 81 --
.../aggregates/test_perfect_ht.test_ignore | 2 +-
.../aggregates/test_scalar_aggr.test_ignore | 3 +-
.../test/sql/aggregate/aggregates/test_sum.test | 8 +-
.../sql/aggregate/aggregates/test_sum.test_ignore | 76 --
15 files changed, 1263 insertions(+), 376 deletions(-)
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
index 3d69449..8ee774c 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/agg/Accumulators.java
@@ -24,7 +24,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
-
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.type.RelDataType;
import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
@@ -93,32 +92,38 @@ public class Accumulators {
/** */
private static Supplier<Accumulator> sumFactory(AggregateCall call) {
switch (call.type.getSqlTypeName()) {
+ case BIGINT:
+ case DECIMAL:
+ return () -> new Sum(new DecimalSumEmptyIsZero());
+
case DOUBLE:
case REAL:
case FLOAT:
- return DoubleSum.FACTORY;
- case DECIMAL:
- return DecimalSum.FACTORY;
+ return () -> new Sum(new DoubleSumEmptyIsZero());
+
+ case TINYINT:
+ case SMALLINT:
case INTEGER:
- return IntSum.FACTORY;
- case BIGINT:
default:
- return LongSum.FACTORY;
+ return () -> new Sum(new LongSumEmptyIsZero());
}
}
/** */
private static Supplier<Accumulator> sumEmptyIsZeroFactory(AggregateCall
call) {
switch (call.type.getSqlTypeName()) {
+ case BIGINT:
+ case DECIMAL:
+ return DecimalSumEmptyIsZero.FACTORY;
+
case DOUBLE:
case REAL:
case FLOAT:
return DoubleSumEmptyIsZero.FACTORY;
- case DECIMAL:
- return DecimalSumEmptyIsZero.FACTORY;
+
+ case TINYINT:
+ case SMALLINT:
case INTEGER:
- return IntSumEmptyIsZero.FACTORY;
- case BIGINT:
default:
return LongSumEmptyIsZero.FACTORY;
}
@@ -364,193 +369,51 @@ public class Accumulators {
}
/** */
- private static class DoubleSum implements Accumulator {
+ private static class Sum implements Accumulator {
/** */
- public static final Supplier<Accumulator> FACTORY = DoubleSum::new;
-
- /** */
- private double sum;
+ private Accumulator acc;
/** */
private boolean empty = true;
- /** {@inheritDoc} */
- @Override public void add(Object... args) {
- Double in = (Double)args[0];
-
- if (in == null)
- return;
-
- empty = false;
- sum += in;
- }
-
- /** {@inheritDoc} */
- @Override public void apply(Accumulator other) {
- DoubleSum other0 = (DoubleSum)other;
-
- if (other0.empty)
- return;
-
- empty = false;
- sum += other0.sum;
- }
-
- /** {@inheritDoc} */
- @Override public Object end() {
- return empty ? null : sum;
- }
-
- /** {@inheritDoc} */
- @Override public List<RelDataType> argumentTypes(IgniteTypeFactory
typeFactory) {
- return
F.asList(typeFactory.createTypeWithNullability(typeFactory.createSqlType(DOUBLE),
true));
- }
-
- /** {@inheritDoc} */
- @Override public RelDataType returnType(IgniteTypeFactory typeFactory)
{
- return
typeFactory.createTypeWithNullability(typeFactory.createSqlType(DOUBLE), true);
- }
- }
-
- /** */
- private static class IntSum implements Accumulator {
- /** */
- public static final Supplier<Accumulator> FACTORY = IntSum::new;
-
/** */
- private int sum;
-
- /** */
- private boolean empty = true;
-
- /** {@inheritDoc} */
- @Override public void add(Object... args) {
- Integer in = (Integer)args[0];
-
- if (in == null)
- return;
-
- empty = false;
- sum += in;
+ public Sum(Accumulator acc) {
+ this.acc = acc;
}
/** {@inheritDoc} */
- @Override public void apply(Accumulator other) {
- IntSum other0 = (IntSum)other;
-
- if (other0.empty)
- return;
-
- empty = false;
- sum += other0.sum;
- }
-
- /** {@inheritDoc} */
- @Override public Object end() {
- return empty ? null : sum;
- }
-
- /** {@inheritDoc} */
- @Override public List<RelDataType> argumentTypes(IgniteTypeFactory
typeFactory) {
- return
F.asList(typeFactory.createTypeWithNullability(typeFactory.createSqlType(INTEGER),
true));
- }
-
- /** {@inheritDoc} */
- @Override public RelDataType returnType(IgniteTypeFactory typeFactory)
{
- return
typeFactory.createTypeWithNullability(typeFactory.createSqlType(INTEGER), true);
- }
- }
-
- /** */
- private static class LongSum implements Accumulator {
- /** */
- public static final Supplier<Accumulator> FACTORY = LongSum::new;
-
- /** */
- private long sum;
-
- /** */
- private boolean empty = true;
-
- /** {@inheritDoc} */
@Override public void add(Object... args) {
- Long in = (Long)args[0];
-
- if (in == null)
+ if (args[0] == null)
return;
empty = false;
- sum += in;
+ acc.add(args[0]);
}
/** {@inheritDoc} */
@Override public void apply(Accumulator other) {
- LongSum other0 = (LongSum)other;
+ Sum other0 = (Sum)other;
if (other0.empty)
return;
empty = false;
- sum += other0.sum;
+ acc.apply(other0.acc);
}
/** {@inheritDoc} */
@Override public Object end() {
- return empty ? null : sum;
+ return empty ? null : acc.end();
}
/** {@inheritDoc} */
@Override public List<RelDataType> argumentTypes(IgniteTypeFactory
typeFactory) {
- return
F.asList(typeFactory.createTypeWithNullability(typeFactory.createSqlType(BIGINT),
true));
- }
-
- /** {@inheritDoc} */
- @Override public RelDataType returnType(IgniteTypeFactory typeFactory)
{
- return
typeFactory.createTypeWithNullability(typeFactory.createSqlType(BIGINT), true);
- }
- }
-
- /** */
- private static class DecimalSum implements Accumulator {
- /** */
- public static final Supplier<Accumulator> FACTORY = DecimalSum::new;
-
- /** */
- private BigDecimal sum;
-
- /** {@inheritDoc} */
- @Override public void add(Object... args) {
- BigDecimal in = (BigDecimal)args[0];
-
- if (in == null)
- return;
-
- sum = sum == null ? in : sum.add(in);
- }
-
- /** {@inheritDoc} */
- @Override public void apply(Accumulator other) {
- DecimalSum other0 = (DecimalSum)other;
-
- if (other0.sum == null)
- return;
-
- sum = sum == null ? other0.sum : sum.add(other0.sum);
- }
-
- /** {@inheritDoc} */
- @Override public Object end() {
- return sum;
- }
-
- /** {@inheritDoc} */
- @Override public List<RelDataType> argumentTypes(IgniteTypeFactory
typeFactory) {
- return
F.asList(typeFactory.createTypeWithNullability(typeFactory.createSqlType(DECIMAL),
true));
+ return acc.argumentTypes(typeFactory);
}
/** {@inheritDoc} */
@Override public RelDataType returnType(IgniteTypeFactory typeFactory)
{
- return
typeFactory.createTypeWithNullability(typeFactory.createSqlType(DECIMAL), true);
+ return acc.returnType(typeFactory);
}
}
@@ -596,47 +459,6 @@ public class Accumulators {
}
/** */
- private static class IntSumEmptyIsZero implements Accumulator {
- /** */
- public static final Supplier<Accumulator> FACTORY =
IntSumEmptyIsZero::new;
-
- /** */
- private int sum;
-
- /** {@inheritDoc} */
- @Override public void add(Object... args) {
- Integer in = (Integer)args[0];
-
- if (in == null)
- return;
-
- sum += in;
- }
-
- /** {@inheritDoc} */
- @Override public void apply(Accumulator other) {
- IntSumEmptyIsZero other0 = (IntSumEmptyIsZero)other;
-
- sum += other0.sum;
- }
-
- /** {@inheritDoc} */
- @Override public Object end() {
- return sum;
- }
-
- /** {@inheritDoc} */
- @Override public List<RelDataType> argumentTypes(IgniteTypeFactory
typeFactory) {
- return
F.asList(typeFactory.createTypeWithNullability(typeFactory.createSqlType(INTEGER),
true));
- }
-
- /** {@inheritDoc} */
- @Override public RelDataType returnType(IgniteTypeFactory typeFactory)
{
- return
typeFactory.createTypeWithNullability(typeFactory.createSqlType(INTEGER), true);
- }
- }
-
- /** */
private static class LongSumEmptyIsZero implements Accumulator {
/** */
public static final Supplier<Accumulator> FACTORY =
LongSumEmptyIsZero::new;
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
index 77591c7..c386b4c7 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
@@ -23,7 +23,6 @@ import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalJoin;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalSort;
-import org.apache.calcite.rel.rules.AggregateExpandDistinctAggregatesRule;
import org.apache.calcite.rel.rules.AggregateMergeRule;
import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.rel.rules.FilterJoinRule.FilterIntoJoinRule;
@@ -61,6 +60,7 @@ import
org.apache.ignite.internal.processors.query.calcite.rule.logical.ExposeIn
import
org.apache.ignite.internal.processors.query.calcite.rule.logical.FilterScanMergeRule;
import
org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalOrToUnionRule;
import
org.apache.ignite.internal.processors.query.calcite.rule.logical.ProjectScanMergeRule;
+import
org.apache.ignite.internal.processors.query.calcite.rule.patch.AggregateExpandDistinctAggregatesRule;
import static
org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePrograms.cbo;
import static
org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePrograms.hep;
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/patch/AggregateExpandDistinctAggregatesRule.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/patch/AggregateExpandDistinctAggregatesRule.java
new file mode 100644
index 0000000..3fe76cc
--- /dev/null
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/patch/AggregateExpandDistinctAggregatesRule.java
@@ -0,0 +1,947 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.rule.patch;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelRule;
+import org.apache.calcite.rel.RelCollations;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.Aggregate.Group;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rel.rules.TransformationRule;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.fun.SqlSumEmptyIsZeroAggFunction;
+import org.apache.calcite.tools.RelBuilder;
+import org.apache.calcite.util.ImmutableBeans;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.ImmutableIntList;
+import org.apache.calcite.util.Optionality;
+import org.apache.calcite.util.Pair;
+import org.apache.calcite.util.Util;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Must be replaced by
@link{org.apache.calcite.rules.AggregateExpandDistinctAggregatesRule} after
upgrade the calcite
+ * version.
+ *
+ * TODO: https://issues.apache.org/jira/browse/IGNITE-15426
+ */
+public final class AggregateExpandDistinctAggregatesRule
+ extends RelRule<AggregateExpandDistinctAggregatesRule.Config> implements
TransformationRule {
+ /** Creates an AggregateExpandDistinctAggregatesRule. */
+ AggregateExpandDistinctAggregatesRule(Config config) {
+ super(config);
+ }
+
+ //~ Methods
----------------------------------------------------------------
+
+ /**
+ * Converts an aggregate with one distinct aggregate and one or more
non-distinct aggregates to multi-phase
+ * aggregates (see reference example below).
+ *
+ * @param relBuilder Contains the input relational expression
+ * @param aggregate Original aggregate
+ * @param argLists Arguments and filters to the distinct aggregate function
+ */
+ private static RelBuilder convertSingletonDistinct(RelBuilder relBuilder,
+ Aggregate aggregate, Set<Pair<List<Integer>, Integer>> argLists) {
+
+ // In this case, we are assuming that there is a single distinct
function.
+ // So make sure that argLists is of size one.
+ Preconditions.checkArgument(argLists.size() == 1);
+
+ // For example,
+ // SELECT deptno, COUNT(*), SUM(bonus), MIN(DISTINCT sal)
+ // FROM emp
+ // GROUP BY deptno
+ //
+ // becomes
+ //
+ // SELECT deptno, SUM(cnt), SUM(bonus), MIN(sal)
+ // FROM (
+ // SELECT deptno, COUNT(*) as cnt, SUM(bonus), sal
+ // FROM EMP
+ // GROUP BY deptno, sal) // Aggregate B
+ // GROUP BY deptno // Aggregate A
+ relBuilder.push(aggregate.getInput());
+
+ final List<AggregateCall> originalAggCalls =
aggregate.getAggCallList();
+ final ImmutableBitSet originalGroupSet = aggregate.getGroupSet();
+
+ // Add the distinct aggregate column(s) to the group-by columns,
+ // if not already a part of the group-by
+ final NavigableSet<Integer> bottomGroups = new
TreeSet<>(aggregate.getGroupSet().asList());
+ for (AggregateCall aggCall : originalAggCalls) {
+ if (aggCall.isDistinct()) {
+ bottomGroups.addAll(aggCall.getArgList());
+
+ break; // since we only have single distinct call
+ }
+ }
+ final ImmutableBitSet bottomGroupSet =
ImmutableBitSet.of(bottomGroups);
+
+ // Generate the intermediate aggregate B, the one on the bottom that
converts
+ // a distinct call to group by call.
+ // Bottom aggregate is the same as the original aggregate, except that
+ // the bottom aggregate has converted the DISTINCT aggregate to a
group by clause.
+ final List<AggregateCall> bottomAggregateCalls = new ArrayList<>();
+ for (AggregateCall aggCall : originalAggCalls) {
+ // Project the column corresponding to the distinct aggregate.
Project
+ // as-is all the non-distinct aggregates
+ if (!aggCall.isDistinct()) {
+ final AggregateCall newCall =
+ AggregateCall.create(aggCall.getAggregation(), false,
+ aggCall.isApproximate(), aggCall.ignoreNulls(),
+ aggCall.getArgList(), -1, aggCall.distinctKeys,
+ aggCall.collation, bottomGroupSet.cardinality(),
+ relBuilder.peek(), null, aggCall.name);
+ bottomAggregateCalls.add(newCall);
+ }
+ }
+
+ // Generate the aggregate B (see the reference example above)
+ relBuilder.push(
+ aggregate.copy(aggregate.getTraitSet(), relBuilder.build(),
+ bottomGroupSet, null, bottomAggregateCalls));
+
+ // Add aggregate A (see the reference example above), the top aggregate
+ // to handle the rest of the aggregation that the bottom aggregate
hasn't handled
+ final List<AggregateCall> topAggregateCalls = new ArrayList<>();
+ // Use the remapped arguments for the (non)distinct aggregate calls
+ int nonDistinctAggCallProcessedSoFar = 0;
+ for (AggregateCall aggCall : originalAggCalls) {
+ final AggregateCall newCall;
+ if (aggCall.isDistinct()) {
+ List<Integer> newArgList = new ArrayList<>();
+ for (int arg : aggCall.getArgList())
+ newArgList.add(bottomGroups.headSet(arg, false).size());
+ newCall =
+ AggregateCall.create(aggCall.getAggregation(),
+ false,
+ aggCall.isApproximate(),
+ aggCall.ignoreNulls(),
+ newArgList,
+ -1,
+ aggCall.distinctKeys,
+ aggCall.collation,
+ originalGroupSet.cardinality(),
+ relBuilder.peek(),
+ aggCall.getType(),
+ aggCall.name);
+ }
+ else {
+ // If aggregate B had a COUNT aggregate call the corresponding
aggregate at
+ // aggregate A must be SUM. For other aggregates, it remains
the same.
+ final int arg = bottomGroups.size() +
nonDistinctAggCallProcessedSoFar;
+ final List<Integer> newArgs = ImmutableList.of(arg);
+ RelDataTypeFactory typeFactory =
aggregate.getCluster().getTypeFactory();
+ if (aggCall.getAggregation().getKind() == SqlKind.COUNT) {
+ // pass 'null' type to infering type from input
+ newCall =
+ AggregateCall.create(new
SqlSumEmptyIsZeroAggFunction(), false,
+ aggCall.isApproximate(), aggCall.ignoreNulls(),
+ newArgs, -1, aggCall.distinctKeys,
aggCall.collation,
+ originalGroupSet.cardinality(), relBuilder.peek(),
+ null,
+ aggCall.getName());
+ }
+ else {
+ // pass 'null' type to infering type from input
+ newCall =
+ AggregateCall.create(aggCall.getAggregation(), false,
+ aggCall.isApproximate(), aggCall.ignoreNulls(),
+ newArgs, -1, aggCall.distinctKeys,
aggCall.collation,
+ originalGroupSet.cardinality(),
+ relBuilder.peek(), null, aggCall.name);
+ }
+ nonDistinctAggCallProcessedSoFar++;
+ }
+
+ topAggregateCalls.add(newCall);
+ }
+
+ // Populate the group-by keys with the remapped arguments for
aggregate A
+ // The top groupset is basically an identity (first X fields of
aggregate B's
+ // output), minus the distinct aggCall's input.
+ final Set<Integer> topGroupSet = new HashSet<>();
+ int groupSetToAdd = 0;
+ for (int bottomGroup : bottomGroups) {
+ if (originalGroupSet.get(bottomGroup))
+ topGroupSet.add(groupSetToAdd);
+ groupSetToAdd++;
+ }
+
+ try {
+ relBuilder.push(
+ aggregate.copy(aggregate.getTraitSet(), relBuilder.build(),
+ ImmutableBitSet.of(topGroupSet), null, topAggregateCalls));
+ }
+ catch (AssertionError e) {
+ throw e;
+ }
+
+ // Add projection node for case: SUM of COUNT(*):
+ // Type of the SUM may be larger than type of COUNT.
+ // CAST to original type must be added.
+ relBuilder.convert(aggregate.getRowType(), true);
+
+ return relBuilder;
+ }
+
+ /** */
+ private static void rewriteUsingGroupingSets(RelOptRuleCall call,
+ Aggregate aggregate) {
+ final Set<ImmutableBitSet> groupSetTreeSet =
+ new TreeSet<>(ImmutableBitSet.ORDERING);
+ // GroupSet to distinct filter arg map,
+ // filterArg will be -1 for non-distinct agg call.
+
+ // Using `Set` here because it's possible that two agg calls
+ // have different filterArgs but same groupSet.
+ final Map<ImmutableBitSet, Set<Integer>> distinctFilterArgMap = new
HashMap<>();
+ for (AggregateCall aggCall : aggregate.getAggCallList()) {
+ ImmutableBitSet groupSet;
+ int filterArg;
+ if (!aggCall.isDistinct()) {
+ filterArg = -1;
+ groupSet = aggregate.getGroupSet();
+ groupSetTreeSet.add(aggregate.getGroupSet());
+ }
+ else {
+ filterArg = aggCall.filterArg;
+ groupSet =
+ ImmutableBitSet.of(aggCall.getArgList())
+ .setIf(filterArg, filterArg >= 0)
+ .union(aggregate.getGroupSet());
+ groupSetTreeSet.add(groupSet);
+ }
+
+ Set<Integer> filterList = distinctFilterArgMap
+ .computeIfAbsent(groupSet, g -> new HashSet<>());
+ filterList.add(filterArg);
+ }
+
+ final ImmutableList<ImmutableBitSet> groupSets =
+ ImmutableList.copyOf(groupSetTreeSet);
+ final ImmutableBitSet fullGroupSet = ImmutableBitSet.union(groupSets);
+
+ final List<AggregateCall> distinctAggCalls = new ArrayList<>();
+ for (Pair<AggregateCall, String> aggCall :
aggregate.getNamedAggCalls()) {
+ if (!aggCall.left.isDistinct()) {
+ AggregateCall newAggCall =
+ aggCall.left.adaptTo(aggregate.getInput(),
+ aggCall.left.getArgList(), aggCall.left.filterArg,
+ aggregate.getGroupCount(), fullGroupSet.cardinality());
+ distinctAggCalls.add(newAggCall.withName(aggCall.right));
+ }
+ }
+
+ final RelBuilder relBuilder = call.builder();
+ relBuilder.push(aggregate.getInput());
+ final int groupCount = fullGroupSet.cardinality();
+
+ // Get the base ordinal of filter args for different groupSets.
+ final Map<Pair<ImmutableBitSet, Integer>, Integer> filters = new
LinkedHashMap<>();
+ int z = groupCount + distinctAggCalls.size();
+ for (ImmutableBitSet groupSet : groupSets) {
+ Set<Integer> filterArgList = distinctFilterArgMap.get(groupSet);
+ for (Integer filterArg : requireNonNull(filterArgList,
"filterArgList")) {
+ filters.put(Pair.of(groupSet, filterArg), z);
+ z += 1;
+ }
+ }
+
+ distinctAggCalls.add(
+ AggregateCall.create(SqlStdOperatorTable.GROUPING, false, false,
false,
+ ImmutableIntList.copyOf(fullGroupSet), -1,
+ null, RelCollations.EMPTY,
+ groupSets.size(), relBuilder.peek(), null, "$g"));
+
+ relBuilder.aggregate(
+ relBuilder.groupKey(fullGroupSet,
+ (Iterable<ImmutableBitSet>)groupSets),
+ distinctAggCalls);
+
+ // GROUPING returns an integer (0 or 1). Add a project to convert those
+ // values to BOOLEAN.
+ if (!filters.isEmpty()) {
+ final List<RexNode> nodes = new ArrayList<>(relBuilder.fields());
+ final RexNode nodeZ = nodes.remove(nodes.size() - 1);
+ for (Map.Entry<Pair<ImmutableBitSet, Integer>, Integer> entry :
filters.entrySet()) {
+ final long v = groupValue(fullGroupSet.asList(),
entry.getKey().left);
+ int distinctFilterArg = remap(fullGroupSet,
entry.getKey().right);
+ RexNode expr = relBuilder.equals(nodeZ, relBuilder.literal(v));
+ if (distinctFilterArg > -1) {
+ // 'AND' the filter of the distinct aggregate call and the
group value.
+ expr = relBuilder.and(expr,
+ relBuilder.call(SqlStdOperatorTable.IS_TRUE,
+ relBuilder.field(distinctFilterArg)));
+ }
+ // "f" means filter.
+ nodes.add(
+ relBuilder.alias(expr,
+ "$g_" + v + (distinctFilterArg < 0 ? "" : "_f_" +
distinctFilterArg)));
+ }
+ relBuilder.project(nodes);
+ }
+
+ int x = groupCount;
+ final ImmutableBitSet groupSet = aggregate.getGroupSet();
+ final List<AggregateCall> newCalls = new ArrayList<>();
+ for (AggregateCall aggCall : aggregate.getAggCallList()) {
+ final int newFilterArg;
+ final List<Integer> newArgList;
+ final SqlAggFunction aggregation;
+
+ if (!aggCall.isDistinct()) {
+ aggregation = SqlStdOperatorTable.MIN;
+ newArgList = ImmutableIntList.of(x++);
+ newFilterArg = requireNonNull(filters.get(Pair.of(groupSet,
-1)),
+ "filters.get(Pair.of(groupSet, -1))");
+ }
+ else {
+ aggregation = aggCall.getAggregation();
+ newArgList = remap(fullGroupSet, aggCall.getArgList());
+ final ImmutableBitSet newGroupSet =
ImmutableBitSet.of(aggCall.getArgList())
+ .setIf(aggCall.filterArg, aggCall.filterArg >= 0)
+ .union(groupSet);
+ newFilterArg = requireNonNull(filters.get(Pair.of(newGroupSet,
aggCall.filterArg)),
+ "filters.get(of(newGroupSet, aggCall.filterArg))");
+ }
+
+ final AggregateCall newCall =
+ AggregateCall.create(aggregation, false,
+ aggCall.isApproximate(), aggCall.ignoreNulls(),
+ newArgList, newFilterArg, aggCall.distinctKeys,
aggCall.collation,
+ aggregate.getGroupCount(), relBuilder.peek(), null,
aggCall.name);
+ newCalls.add(newCall);
+ }
+
+ relBuilder.aggregate(
+ relBuilder.groupKey(
+ remap(fullGroupSet, groupSet),
+ (Iterable<ImmutableBitSet>)
+ remap(fullGroupSet, aggregate.getGroupSets())),
+ newCalls);
+ relBuilder.convert(aggregate.getRowType(), true);
+ call.transformTo(relBuilder.build());
+ }
+
+ /**
+ * Returns the value that "GROUPING(fullGroupSet)" will return for
"groupSet".
+ *
+ * <p>It is important that {@code fullGroupSet} is not an
+ * {@link ImmutableBitSet}; the order of the bits matters.
+ */
+ static long groupValue(Collection<Integer> fullGroupSet,
+ ImmutableBitSet groupSet) {
+ long v = 0;
+ long x = 1L << (fullGroupSet.size() - 1);
+ assert ImmutableBitSet.of(fullGroupSet).contains(groupSet);
+ for (int i : fullGroupSet) {
+ if (!groupSet.get(i))
+ v |= x;
+
+ x >>= 1;
+ }
+ return v;
+ }
+
+ /** */
+ static ImmutableBitSet remap(ImmutableBitSet groupSet,
+ ImmutableBitSet bitSet) {
+ final ImmutableBitSet.Builder builder = ImmutableBitSet.builder();
+
+ for (Integer bit : bitSet)
+ builder.set(remap(groupSet, bit));
+
+ return builder.build();
+ }
+
+ /** */
+ static ImmutableList<ImmutableBitSet> remap(ImmutableBitSet groupSet,
+ Iterable<ImmutableBitSet> bitSets) {
+ final ImmutableList.Builder<ImmutableBitSet> builder =
+ ImmutableList.builder();
+
+ for (ImmutableBitSet bitSet : bitSets)
+ builder.add(remap(groupSet, bitSet));
+
+ return builder.build();
+ }
+
+ /** */
+ private static List<Integer> remap(ImmutableBitSet groupSet,
+ List<Integer> argList) {
+ ImmutableIntList list = ImmutableIntList.of();
+
+ for (int arg : argList)
+ list = list.append(remap(groupSet, arg));
+
+ return list;
+ }
+
+ /** */
+ private static int remap(ImmutableBitSet groupSet, int arg) {
+ return arg < 0 ? -1 : groupSet.indexOf(arg);
+ }
+
+ /**
+ * Converts an aggregate relational expression that contains just one
distinct aggregate function (or perhaps
+ * several over the same arguments) and no non-distinct aggregate
functions.
+ */
+ private static RelBuilder convertMonopole(RelBuilder relBuilder, Aggregate
aggregate,
+ List<Integer> argList, int filterArg) {
+ // For example,
+ // SELECT deptno, COUNT(DISTINCT sal), SUM(DISTINCT sal)
+ // FROM emp
+ // GROUP BY deptno
+ //
+ // becomes
+ //
+ // SELECT deptno, COUNT(distinct_sal), SUM(distinct_sal)
+ // FROM (
+ // SELECT DISTINCT deptno, sal AS distinct_sal
+ // FROM EMP GROUP BY deptno)
+ // GROUP BY deptno
+
+ // Project the columns of the GROUP BY plus the arguments
+ // to the agg function.
+ final Map<Integer, Integer> sourceOf = new HashMap<>();
+
+ createSelectDistinct(relBuilder, aggregate, argList, filterArg,
sourceOf);
+
+ // Create an aggregate on top, with the new aggregate list.
+ final List<AggregateCall> newAggCalls =
+ Lists.newArrayList(aggregate.getAggCallList());
+
+ rewriteAggCalls(newAggCalls, argList, sourceOf);
+
+ final int cardinality = aggregate.getGroupSet().cardinality();
+
+ relBuilder.push(
+ aggregate.copy(aggregate.getTraitSet(), relBuilder.build(),
+ ImmutableBitSet.range(cardinality), null, newAggCalls));
+ return relBuilder;
+ }
+
+ /**
+ * Converts all distinct aggregate calls to a given set of arguments.
+ *
+ * <p>This method is called several times, one for each set of arguments.
+ * Each time it is called, it generates a JOIN to a new SELECT DISTINCT
relational expression, and modifies the set
+ * of top-level calls.
+ *
+ * @param aggregate Original aggregate
+ * @param n Ordinal of this in a join. {@code relBuilder} contains the
input relational expression (either the
+ * original aggregate, the output from the previous call to this method.
{@code n} is 0 if we're converting the
+ * first distinct aggregate in a query with no non-distinct aggregates)
+ * @param argList Arguments to the distinct aggregate function
+ * @param filterArg Argument that filters input to aggregate function, or
-1
+ * @param refs Array of expressions which will be the projected by the
result of this rule. Those relating to this
+ * arg list will be modified
+ */
+ private static void doRewrite(RelBuilder relBuilder, Aggregate aggregate,
int n,
+ List<Integer> argList, int filterArg, List<@Nullable RexInputRef>
refs) {
+ final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
+ final List<RelDataTypeField> leftFields;
+
+ if (n == 0)
+ leftFields = null;
+ else
+ leftFields = relBuilder.peek().getRowType().getFieldList();
+
+ // Aggregate(
+ // child,
+ // {COUNT(DISTINCT 1), SUM(DISTINCT 1), SUM(2)})
+ //
+ // becomes
+ //
+ // Aggregate(
+ // Join(
+ // child,
+ // Aggregate(child, < all columns > {}),
+ // INNER,
+ // <f2 = f5>))
+ //
+ // E.g.
+ // SELECT deptno, SUM(DISTINCT sal), COUNT(DISTINCT gender), MAX(age)
+ // FROM Emps
+ // GROUP BY deptno
+ //
+ // becomes
+ //
+ // SELECT e.deptno, adsal.sum_sal, adgender.count_gender, e.max_age
+ // FROM (
+ // SELECT deptno, MAX(age) as max_age
+ // FROM Emps GROUP BY deptno) AS e
+ // JOIN (
+ // SELECT deptno, COUNT(gender) AS count_gender FROM (
+ // SELECT DISTINCT deptno, gender FROM Emps) AS dgender
+ // GROUP BY deptno) AS adgender
+ // ON e.deptno = adgender.deptno
+ // JOIN (
+ // SELECT deptno, SUM(sal) AS sum_sal FROM (
+ // SELECT DISTINCT deptno, sal FROM Emps) AS dsal
+ // GROUP BY deptno) AS adsal
+ // ON e.deptno = adsal.deptno
+ // GROUP BY e.deptno
+ //
+ // Note that if a query contains no non-distinct aggregates, then the
+ // very first join/group by is omitted. In the example above, if
+ // MAX(age) is removed, then the sub-select of "e" is not needed, and
+ // instead the two other group by's are joined to one another.
+
+ // Project the columns of the GROUP BY plus the arguments
+ // to the agg function.
+ final Map<Integer, Integer> sourceOf = new HashMap<>();
+ createSelectDistinct(relBuilder, aggregate, argList, filterArg,
sourceOf);
+
+ // Now compute the aggregate functions on top of the distinct dataset.
+ // Each distinct agg becomes a non-distinct call to the corresponding
+ // field from the right; for example,
+ // "COUNT(DISTINCT e.sal)"
+ // becomes
+ // "COUNT(distinct_e.sal)".
+ final List<AggregateCall> aggCallList = new ArrayList<>();
+ final List<AggregateCall> aggCalls = aggregate.getAggCallList();
+
+ final int groupCount = aggregate.getGroupCount();
+ int i = groupCount - 1;
+ for (AggregateCall aggCall : aggCalls) {
+ ++i;
+
+ // Ignore agg calls which are not distinct or have the wrong set
+ // arguments. If we're rewriting aggs whose args are {sal}, we will
+ // rewrite COUNT(DISTINCT sal) and SUM(DISTINCT sal) but ignore
+ // COUNT(DISTINCT gender) or SUM(sal).
+ if (!aggCall.isDistinct())
+ continue;
+
+ if (!aggCall.getArgList().equals(argList))
+ continue;
+
+ // Re-map arguments.
+ final int argCount = aggCall.getArgList().size();
+ final List<Integer> newArgs = new ArrayList<>(argCount);
+
+ for (Integer arg : aggCall.getArgList())
+ newArgs.add(requireNonNull(sourceOf.get(arg), () ->
"sourceOf.get(" + arg + ")"));
+
+ final int newFilterArg =
+ aggCall.filterArg < 0 ? -1
+ : requireNonNull(sourceOf.get(aggCall.filterArg),
+ () -> "sourceOf.get(" + aggCall.filterArg + ")");
+
+ final AggregateCall newAggCall =
+ AggregateCall.create(aggCall.getAggregation(), false,
+ aggCall.isApproximate(), aggCall.ignoreNulls(),
+ newArgs, newFilterArg, aggCall.distinctKeys,
aggCall.collation,
+ aggCall.getType(), aggCall.getName());
+
+ assert refs.get(i) == null;
+
+ if (leftFields == null) {
+ refs.set(i,
+ new RexInputRef(groupCount + aggCallList.size(),
+ newAggCall.getType()));
+ }
+ else {
+ refs.set(i,
+ new RexInputRef(leftFields.size() + groupCount
+ + aggCallList.size(), newAggCall.getType()));
+ }
+ aggCallList.add(newAggCall);
+ }
+
+ final Map<Integer, Integer> map = new HashMap<>();
+ for (Integer key : aggregate.getGroupSet())
+ map.put(key, map.size());
+
+ final ImmutableBitSet newGroupSet =
aggregate.getGroupSet().permute(map);
+ assert newGroupSet
+
.equals(ImmutableBitSet.range(aggregate.getGroupSet().cardinality()));
+ ImmutableList<ImmutableBitSet> newGroupingSets = null;
+
+ relBuilder.push(
+ aggregate.copy(aggregate.getTraitSet(), relBuilder.build(),
+ newGroupSet, newGroupingSets, aggCallList));
+
+ // If there's no left child yet, no need to create the join
+ if (leftFields == null)
+ return;
+
+ // Create the join condition. It is of the form
+ // 'left.f0 = right.f0 and left.f1 = right.f1 and ...'
+ // where {f0, f1, ...} are the GROUP BY fields.
+ final List<RelDataTypeField> distinctFields =
+ relBuilder.peek().getRowType().getFieldList();
+ final List<RexNode> conditions = new ArrayList<>();
+ for (i = 0; i < groupCount; ++i) {
+ // null values form its own group
+ // use "is not distinct from" so that the join condition
+ // allows null values to match.
+ conditions.add(
+ rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+ RexInputRef.of(i, leftFields),
+ new RexInputRef(leftFields.size() + i,
+ distinctFields.get(i).getType())));
+ }
+
+ // Join in the new 'select distinct' relation.
+ relBuilder.join(JoinRelType.INNER, conditions);
+ }
+
+ /** */
+ private static void rewriteAggCalls(
+ List<AggregateCall> newAggCalls,
+ List<Integer> argList,
+ Map<Integer, Integer> sourceOf) {
+ // Rewrite the agg calls. Each distinct agg becomes a non-distinct call
+ // to the corresponding field from the right; for example,
+ // "COUNT(DISTINCT e.sal)" becomes "COUNT(distinct_e.sal)".
+ for (int i = 0; i < newAggCalls.size(); i++) {
+ final AggregateCall aggCall = newAggCalls.get(i);
+
+ // Ignore agg calls which are not distinct or have the wrong set
+ // arguments. If we're rewriting aggregates whose args are {sal},
we will
+ // rewrite COUNT(DISTINCT sal) and SUM(DISTINCT sal) but ignore
+ // COUNT(DISTINCT gender) or SUM(sal).
+ if (!aggCall.isDistinct()
+ && aggCall.getAggregation().getDistinctOptionality() !=
Optionality.IGNORED)
+ continue;
+
+ if (!aggCall.getArgList().equals(argList))
+ continue;
+
+ // Re-map arguments.
+ final int argCount = aggCall.getArgList().size();
+ final List<Integer> newArgs = new ArrayList<>(argCount);
+ for (int j = 0; j < argCount; j++) {
+ final Integer arg = aggCall.getArgList().get(j);
+ newArgs.add(
+ requireNonNull(sourceOf.get(arg),
+ () -> "sourceOf.get(" + arg + ")"));
+ }
+ final AggregateCall newAggCall =
+ AggregateCall.create(aggCall.getAggregation(), false,
+ aggCall.isApproximate(), aggCall.ignoreNulls(), newArgs,
-1,
+ aggCall.distinctKeys, aggCall.collation,
+ aggCall.getType(), aggCall.getName());
+ newAggCalls.set(i, newAggCall);
+ }
+ }
+
+ /**
+ * Given an {@link org.apache.calcite.rel.core.Aggregate} and the ordinals
of the arguments to a particular call to
+ * an aggregate function, creates a 'select distinct' relational
expression which projects the group columns and
+ * those arguments but nothing else.
+ *
+ * <p>For example, given
+ *
+ * <blockquote>
+ * <pre>select f0, count(distinct f1), count(distinct f2)
+ * from t group by f0</pre>
+ * </blockquote>
+ *
+ * <p>and the argument list
+ *
+ * <blockquote>{2}</blockquote>
+ *
+ * <p>returns
+ *
+ * <blockquote>
+ * <pre>select distinct f0, f2 from t</pre>
+ * </blockquote>
+ *
+ * <p>The <code>sourceOf</code> map is populated with the source of each
+ * column; in this case sourceOf.get(0) = 0, and sourceOf.get(1) = 2.
+ *
+ * @param relBuilder Relational expression builder
+ * @param aggregate Aggregate relational expression
+ * @param argList Ordinals of columns to make distinct
+ * @param filterArg Ordinal of column to filter on, or -1
+ * @param sourceOf Out parameter, is populated with a map of where each
output field came from
+ * @return Aggregate relational expression which projects the required
columns
+ */
+ private static RelBuilder createSelectDistinct(RelBuilder relBuilder,
+ Aggregate aggregate, List<Integer> argList, int filterArg,
+ Map<Integer, Integer> sourceOf) {
+ relBuilder.push(aggregate.getInput());
+ final List<Pair<RexNode, String>> projects = new ArrayList<>();
+ final List<RelDataTypeField> childFields =
+ relBuilder.peek().getRowType().getFieldList();
+
+ for (int i : aggregate.getGroupSet()) {
+ sourceOf.put(i, projects.size());
+ projects.add(RexInputRef.of2(i, childFields));
+ }
+
+ for (Integer arg : argList) {
+ if (filterArg >= 0) {
+ // Implement
+ // agg(DISTINCT arg) FILTER $f
+ // by generating
+ // SELECT DISTINCT ... CASE WHEN $f THEN arg ELSE NULL END
AS arg
+ // and then applying
+ // agg(arg)
+ // as usual.
+ //
+ // It works except for (rare) agg functions that need to see
null
+ // values.
+ final RexBuilder rexBuilder =
aggregate.getCluster().getRexBuilder();
+ final RexInputRef filterRef = RexInputRef.of(filterArg,
childFields);
+ final Pair<RexNode, String> argRef = RexInputRef.of2(arg,
childFields);
+ RexNode condition =
+ rexBuilder.makeCall(SqlStdOperatorTable.CASE, filterRef,
+ argRef.left,
+ rexBuilder.makeNullLiteral(argRef.left.getType()));
+ sourceOf.put(arg, projects.size());
+ projects.add(Pair.of(condition, "i$" + argRef.right));
+
+ continue;
+ }
+
+ if (sourceOf.get(arg) != null)
+ continue;
+
+ sourceOf.put(arg, projects.size());
+ projects.add(RexInputRef.of2(arg, childFields));
+ }
+ relBuilder.project(Pair.left(projects), Pair.right(projects));
+
+ // Get the distinct values of the GROUP BY fields and the arguments
+ // to the agg functions.
+ relBuilder.push(
+ aggregate.copy(aggregate.getTraitSet(), relBuilder.build(),
+ ImmutableBitSet.range(projects.size()), null,
ImmutableList.of()));
+
+ return relBuilder;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onMatch(RelOptRuleCall call) {
+ final Aggregate aggregate = call.rel(0);
+
+ if (!aggregate.containsDistinctCall())
+ return;
+
+ // Find all of the agg expressions. We use a LinkedHashSet to ensure
determinism.
+ final List<AggregateCall> aggCalls = aggregate.getAggCallList();
+ // Find all aggregate calls with distinct
+ final List<AggregateCall> distinctAggCalls = aggCalls.stream()
+ .filter(AggregateCall::isDistinct).collect(Collectors.toList());
+ // Find all aggregate calls without distinct
+ final List<AggregateCall> nonDistinctAggCalls = aggCalls.stream()
+ .filter(aggCall ->
!aggCall.isDistinct()).collect(Collectors.toList());
+ final long filterCount = aggCalls.stream()
+ .filter(aggCall -> aggCall.filterArg >= 0).count();
+ final long unsupportedNonDistinctAggCallCount =
nonDistinctAggCalls.stream()
+ .filter(aggCall -> {
+ final SqlKind aggCallKind = aggCall.getAggregation().getKind();
+ // We only support COUNT/SUM/MIN/MAX for the "single" count
distinct optimization
+ switch (aggCallKind) {
+ case COUNT:
+ case SUM:
+ case SUM0:
+ case MIN:
+ case MAX:
+ return false;
+ default:
+ return true;
+ }
+ }).count();
+
+ // Argument list of distinct agg calls.
+ final Set<Pair<List<Integer>, Integer>> distinctCallArgLists =
distinctAggCalls.stream()
+ .map(aggCall -> Pair.of(aggCall.getArgList(), aggCall.filterArg))
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+
+ Preconditions.checkState(!distinctCallArgLists.isEmpty(),
+ "containsDistinctCall lied");
+
+ // If all of the agg expressions are distinct and have the same
+ // arguments then we can use a more efficient form.
+
+ // MAX, MIN, BIT_AND, BIT_OR always ignore distinct attribute,
+ // when they are mixed in with other distinct agg calls,
+ // we can still use this promotion.
+
+ // Treat the agg expression with Optionality.IGNORED as distinct and
+ // re-statistic the non-distinct agg call count and the distinct agg
+ // call arguments.
+ final List<AggregateCall> nonDistinctAggCallsOfIgnoredOptionality =
+ nonDistinctAggCalls.stream().filter(aggCall ->
+ aggCall.getAggregation().getDistinctOptionality() ==
Optionality.IGNORED)
+ .collect(Collectors.toList());
+ // Different with distinctCallArgLists, this list also contains args
that come from
+ // agg call which can ignore the distinct constraint.
+ final Set<Pair<List<Integer>, Integer>> distinctCallArgLists2 =
+ Stream.of(distinctAggCalls,
nonDistinctAggCallsOfIgnoredOptionality)
+ .flatMap(Collection::stream)
+ .map(aggCall -> Pair.of(aggCall.getArgList(),
aggCall.filterArg))
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+
+ if ((nonDistinctAggCalls.size() -
nonDistinctAggCallsOfIgnoredOptionality.size()) == 0
+ && distinctCallArgLists2.size() == 1
+ && aggregate.getGroupType() == Group.SIMPLE) {
+ final Pair<List<Integer>, Integer> pair =
+ Iterables.getOnlyElement(distinctCallArgLists2);
+
+ final RelBuilder relBuilder = call.builder();
+
+ convertMonopole(relBuilder, aggregate, pair.left, pair.right);
+
+ call.transformTo(relBuilder.build());
+
+ return;
+ }
+
+ if (config.isUsingGroupingSets()) {
+ rewriteUsingGroupingSets(call, aggregate);
+
+ return;
+ }
+
+ // If only one distinct aggregate and one or more non-distinct
aggregates,
+ // we can generate multi-phase aggregates
+ if (distinctAggCalls.size() == 1 // one distinct aggregate
+ && filterCount == 0 // no filter
+ && unsupportedNonDistinctAggCallCount == 0 // sum/min/max/count in
non-distinct aggregate
+ && !nonDistinctAggCalls.isEmpty()) { // one or more non-distinct
aggregates
+ final RelBuilder relBuilder = call.builder();
+
+ convertSingletonDistinct(relBuilder, aggregate,
distinctCallArgLists);
+
+ call.transformTo(relBuilder.build());
+
+ return;
+ }
+
+ // Create a list of the expressions which will yield the final result.
+ // Initially, the expressions point to the input field.
+ final List<RelDataTypeField> aggFields =
+ aggregate.getRowType().getFieldList();
+ final List<@Nullable RexInputRef> refs = new ArrayList<>();
+ final List<String> fieldNames = aggregate.getRowType().getFieldNames();
+ final ImmutableBitSet groupSet = aggregate.getGroupSet();
+ final int groupCount = aggregate.getGroupCount();
+ for (int i : Util.range(groupCount))
+ refs.add(RexInputRef.of(i, aggFields));
+
+ // Aggregate the original relation, including any non-distinct
aggregates.
+ final List<AggregateCall> newAggCallList = new ArrayList<>();
+ int i = -1;
+ for (AggregateCall aggCall : aggregate.getAggCallList()) {
+ ++i;
+ if (aggCall.isDistinct()) {
+ refs.add(null);
+ continue;
+ }
+
+ refs.add(
+ new RexInputRef(
+ groupCount + newAggCallList.size(),
+ aggFields.get(groupCount + i).getType()));
+
+ newAggCallList.add(aggCall);
+ }
+
+ // In the case where there are no non-distinct aggregates (regardless
of
+ // whether there are group bys), there's no need to generate the
+ // extra aggregate and join.
+ final RelBuilder relBuilder = call.builder();
+ relBuilder.push(aggregate.getInput());
+ int n = 0;
+
+ if (!newAggCallList.isEmpty()) {
+ final RelBuilder.GroupKey groupKey =
+ relBuilder.groupKey(groupSet,
+ (Iterable<ImmutableBitSet>)aggregate.getGroupSets());
+
+ relBuilder.aggregate(groupKey, newAggCallList);
+
+ ++n;
+ }
+
+ // For each set of operands, find and rewrite all calls which have that
+ // set of operands.
+ for (Pair<List<Integer>, Integer> argList : distinctCallArgLists)
+ doRewrite(relBuilder, aggregate, n++, argList.left, argList.right,
refs);
+
+ // It is assumed doRewrite above replaces nulls in refs
+ @SuppressWarnings("assignment.type.incompatible")
+ List<RexInputRef> nonNullRefs = refs;
+ relBuilder.project(nonNullRefs, fieldNames);
+ call.transformTo(relBuilder.build());
+ }
+
+ /** Rule configuration. */
+ public interface Config extends RelRule.Config {
+ /** */
+ Config DEFAULT = EMPTY
+ .withOperandSupplier(b ->
+ b.operand(LogicalAggregate.class).anyInputs())
+ .as(Config.class);
+
+ /** */
+ Config JOIN = DEFAULT.withUsingGroupingSets(false);
+
+ /** {@inheritDoc} */
+ @Override default AggregateExpandDistinctAggregatesRule toRule() {
+ return new AggregateExpandDistinctAggregatesRule(this);
+ }
+
+ /** Whether to use GROUPING SETS, default true. */
+ @ImmutableBeans.Property
+ @ImmutableBeans.BooleanDefault(true)
+ boolean isUsingGroupingSets();
+
+ /** Sets {@link #isUsingGroupingSets()}. */
+ Config withUsingGroupingSets(boolean usingGroupingSets);
+ }
+}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
index d01d182..dcde15b 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
@@ -18,9 +18,13 @@
package org.apache.ignite.internal.processors.query.calcite.type;
import java.io.Serializable;
-
+import java.math.BigDecimal;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
+import org.apache.calcite.sql.type.BasicSqlType;
+import org.apache.calcite.sql.type.SqlTypeName;
/**
* Ignite type system.
@@ -38,4 +42,63 @@ public class IgniteTypeSystem extends RelDataTypeSystemImpl
implements Serializa
@Override public int getMaxNumericPrecision() {
return Short.MAX_VALUE;
}
+
+ /** {@inheritDoc} */
+ @Override public RelDataType deriveSumType(RelDataTypeFactory typeFactory,
RelDataType argumentType) {
+ RelDataType sumType;
+ if (argumentType instanceof BasicSqlType) {
+ switch (argumentType.getSqlTypeName()) {
+ case INTEGER:
+ case TINYINT:
+ case SMALLINT:
+ sumType = typeFactory.createSqlType(SqlTypeName.BIGINT);
+
+ break;
+
+ case BIGINT:
+ case DECIMAL:
+ sumType = typeFactory.createSqlType(SqlTypeName.DECIMAL);
+
+ break;
+
+ case REAL:
+ case FLOAT:
+ case DOUBLE:
+ sumType = typeFactory.createSqlType(SqlTypeName.DOUBLE);
+
+ break;
+
+ default:
+ return super.deriveSumType(typeFactory, argumentType);
+ }
+ }
+ else {
+ switch (argumentType.getSqlTypeName()) {
+ case INTEGER:
+ case TINYINT:
+ case SMALLINT:
+ sumType = typeFactory.createJavaType(Long.class);
+
+ break;
+
+ case BIGINT:
+ case DECIMAL:
+ sumType = typeFactory.createJavaType(BigDecimal.class);
+
+ break;
+
+ case REAL:
+ case FLOAT:
+ case DOUBLE:
+ sumType = typeFactory.createJavaType(Double.class);
+
+ break;
+
+ default:
+ return super.deriveSumType(typeFactory, argumentType);
+ }
+ }
+
+ return typeFactory.createTypeWithNullability(sumType,
argumentType.isNullable());
+ }
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
index ff427c4..ef6217a 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
@@ -100,7 +100,7 @@ public class CalciteQueryProcessorTest extends
GridCommonAbstractTest {
}
/** {@inheritDoc} */
- @Override protected void afterTest() {
+ @Override protected void afterTest() throws InterruptedException {
for (Ignite ign : G.allGrids()) {
for (String cacheName : ign.cacheNames())
ign.destroyCache(cacheName);
@@ -110,6 +110,8 @@ public class CalciteQueryProcessorTest extends
GridCommonAbstractTest {
qryProc.queryPlanCache().clear();
}
+
+ awaitPartitionMapExchange();
}
/** {@inheritDoc} */
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/BaseAggregateTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/BaseAggregateTest.java
index 260ed27..819dce1e 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/BaseAggregateTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/BaseAggregateTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.query.calcite.exec.rel;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -513,6 +514,98 @@ public abstract class BaseAggregateTest extends
AbstractExecutionTest {
}
/** */
+ @Test
+ public void sumIntegerOverflow() {
+ ExecutionContext<Object[]> ctx = executionContext(F.first(nodes()),
UUID.randomUUID(), 0);
+ IgniteTypeFactory tf = ctx.getTypeFactory();
+ RelDataType rowType = TypeUtils.createRowType(tf, int.class,
int.class);
+ ScanNode<Object[]> scan = new ScanNode<>(ctx, rowType, Arrays.asList(
+ row(0, Integer.MAX_VALUE / 2),
+ row(0, Integer.MAX_VALUE / 2 + 11)
+ ));
+
+ AggregateCall call = AggregateCall.create(
+ SqlStdOperatorTable.SUM,
+ false,
+ false,
+ false,
+ ImmutableIntList.of(1),
+ -1,
+ RelCollations.EMPTY,
+ tf.createJavaType(Long.class),
+ null);
+
+ ImmutableList<ImmutableBitSet> grpSets =
ImmutableList.of(ImmutableBitSet.of(0));
+
+ RelDataType aggRowType = TypeUtils.createRowType(tf, int.class);
+
+ SingleNode<Object[]> aggChain = createAggregateNodesChain(
+ ctx,
+ grpSets,
+ call,
+ rowType,
+ aggRowType,
+ rowFactory(),
+ scan
+ );
+
+ RootNode<Object[]> root = new RootNode<>(ctx, aggRowType);
+ root.register(aggChain);
+
+ assertTrue(root.hasNext());
+
+ Assert.assertArrayEquals(row(0, (long)Integer.MAX_VALUE / 2 +
(long)Integer.MAX_VALUE / 2 + 11L), root.next());
+
+ assertFalse(root.hasNext());
+ }
+
+ /** */
+ @Test
+ public void sumLongOverflow() {
+ ExecutionContext<Object[]> ctx = executionContext(F.first(nodes()),
UUID.randomUUID(), 0);
+ IgniteTypeFactory tf = ctx.getTypeFactory();
+ RelDataType rowType = TypeUtils.createRowType(tf, int.class,
long.class);
+ ScanNode<Object[]> scan = new ScanNode<>(ctx, rowType, Arrays.asList(
+ row(0, Long.MAX_VALUE / 2),
+ row(0, Long.MAX_VALUE / 2 + 11)
+ ));
+
+ AggregateCall call = AggregateCall.create(
+ SqlStdOperatorTable.SUM,
+ false,
+ false,
+ false,
+ ImmutableIntList.of(1),
+ -1,
+ RelCollations.EMPTY,
+ tf.createJavaType(BigDecimal.class),
+ null);
+
+ ImmutableList<ImmutableBitSet> grpSets =
ImmutableList.of(ImmutableBitSet.of(0));
+
+ RelDataType aggRowType = TypeUtils.createRowType(tf, int.class);
+
+ SingleNode<Object[]> aggChain = createAggregateNodesChain(
+ ctx,
+ grpSets,
+ call,
+ rowType,
+ aggRowType,
+ rowFactory(),
+ scan
+ );
+
+ RootNode<Object[]> root = new RootNode<>(ctx, aggRowType);
+ root.register(aggChain);
+
+ assertTrue(root.hasNext());
+
+ Assert.assertArrayEquals(row(0, new BigDecimal(Long.MAX_VALUE).add(new
BigDecimal(10))), root.next());
+
+ assertFalse(root.hasNext());
+ }
+
+ /** */
protected SingleNode<Object[]> createAggregateNodesChain(
ExecutionContext<Object[]> ctx,
ImmutableList<ImmutableBitSet> grpSets,
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
index 49fc2ee..8fd187a 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/AggregatesIntegrationTest.java
@@ -71,8 +71,8 @@ public class AggregatesIntegrationTest extends
AbstractBasicIntegrationTest {
.check();
assertQuery("select salary, count(1), sum(1) from person group by
salary order by salary")
- .returns(10d, 3L, 3)
- .returns(15d, 2L, 2)
+ .returns(10d, 3L, 3L)
+ .returns(15d, 2L, 2L)
.check();
assertQuery("select salary, name, count(1), sum(salary) from person
group by salary, name order by salary")
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java
index 1de1a81..17a0a0f 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SortAggregateIntegrationTest.java
@@ -20,7 +20,6 @@ package
org.apache.ignite.internal.processors.query.calcite.integration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.QueryEntity;
@@ -108,10 +107,10 @@ public class SortAggregateIntegrationTest extends
GridCommonAbstractTest {
assertEquals(ROWS / 10, res.size());
res.forEach(r -> {
- Integer s0 = (Integer)r.get(0);
- Integer s1 = (Integer)r.get(1);
+ long s0 = (Long)r.get(0);
+ long s1 = (Long)r.get(1);
- assertEquals(s0 * 2, (int)s1);
+ assertEquals(s0 * 2, s1);
});
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
index 96e9e8d..b184826 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AggregatePlannerTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.query.calcite.planner;
+import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -25,6 +26,8 @@ import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.sql.fun.SqlAvgAggFunction;
import org.apache.calcite.util.ImmutableIntList;
@@ -270,6 +273,116 @@ public class AggregatePlannerTest extends
AbstractAggregatePlannerTest {
);
}
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void singleSumTypes() throws Exception {
+ IgniteSchema schema = createSchema(
+ createTable(
+ "TEST", IgniteDistributions.broadcast(),
+ "ID", Integer.class,
+ "GRP", Integer.class,
+ "VAL_TINYINT", Byte.class,
+ "VAL_SMALLINT", Short.class,
+ "VAL_INT", Integer.class,
+ "VAL_BIGINT", Long.class,
+ "VAL_DECIMAL", BigDecimal.class,
+ "VAL_FLOAT", Float.class,
+ "VAL_DOUBLE", Double.class
+ )
+ );
+
+ String sql = "SELECT " +
+ "SUM(VAL_TINYINT), " +
+ "SUM(VAL_SMALLINT), " +
+ "SUM(VAL_INT), " +
+ "SUM(VAL_BIGINT), " +
+ "SUM(VAL_DECIMAL), " +
+ "SUM(VAL_FLOAT), " +
+ "SUM(VAL_DOUBLE) " +
+ "FROM test GROUP BY grp";
+
+ IgniteRel phys = physicalPlan(
+ sql,
+ schema,
+ algo.rulesToDisable
+ );
+
+ checkSplitAndSerialization(phys, schema);
+
+ IgniteSingleAggregateBase agg = findFirstNode(phys,
byClass(algo.single));
+
+ assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), agg);
+
+ RelDataType rowTypes = agg.getRowType();
+
+ RelDataTypeFactory tf = phys.getCluster().getTypeFactory();
+
+ assertEquals(tf.createJavaType(Long.class),
rowTypes.getFieldList().get(1).getType());
+ assertEquals(tf.createJavaType(Long.class),
rowTypes.getFieldList().get(2).getType());
+ assertEquals(tf.createJavaType(Long.class),
rowTypes.getFieldList().get(3).getType());
+ assertEquals(tf.createJavaType(BigDecimal.class),
rowTypes.getFieldList().get(4).getType());
+ assertEquals(tf.createJavaType(BigDecimal.class),
rowTypes.getFieldList().get(5).getType());
+ assertEquals(tf.createJavaType(Double.class),
rowTypes.getFieldList().get(6).getType());
+ assertEquals(tf.createJavaType(Double.class),
rowTypes.getFieldList().get(7).getType());
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void mapReduceSumTypes() throws Exception {
+ IgniteSchema schema = createSchema(
+ createTable(
+ "TEST", IgniteDistributions.random(),
+ "ID", Integer.class,
+ "GRP", Integer.class,
+ "VAL_TINYINT", Byte.class,
+ "VAL_SMALLINT", Short.class,
+ "VAL_INT", Integer.class,
+ "VAL_BIGINT", Long.class,
+ "VAL_DECIMAL", BigDecimal.class,
+ "VAL_FLOAT", Float.class,
+ "VAL_DOUBLE", Double.class
+ )
+ );
+
+ String sql = "SELECT " +
+ "SUM(VAL_TINYINT), " +
+ "SUM(VAL_SMALLINT), " +
+ "SUM(VAL_INT), " +
+ "SUM(VAL_BIGINT), " +
+ "SUM(VAL_DECIMAL), " +
+ "SUM(VAL_FLOAT), " +
+ "SUM(VAL_DOUBLE) " +
+ "FROM test GROUP BY grp";
+
+ IgniteRel phys = physicalPlan(
+ sql,
+ schema,
+ algo.rulesToDisable
+ );
+
+ checkSplitAndSerialization(phys, schema);
+
+ IgniteReduceAggregateBase agg = findFirstNode(phys,
byClass(algo.reduce));
+
+ assertNotNull("Invalid plan\n" + RelOptUtil.toString(phys), agg);
+
+ RelDataType rowTypes = agg.getRowType();
+
+ RelDataTypeFactory tf = phys.getCluster().getTypeFactory();
+
+ assertEquals(tf.createJavaType(Long.class),
rowTypes.getFieldList().get(1).getType());
+ assertEquals(tf.createJavaType(Long.class),
rowTypes.getFieldList().get(2).getType());
+ assertEquals(tf.createJavaType(Long.class),
rowTypes.getFieldList().get(3).getType());
+ assertEquals(tf.createJavaType(BigDecimal.class),
rowTypes.getFieldList().get(4).getType());
+ assertEquals(tf.createJavaType(BigDecimal.class),
rowTypes.getFieldList().get(5).getType());
+ assertEquals(tf.createJavaType(Double.class),
rowTypes.getFieldList().get(6).getType());
+ assertEquals(tf.createJavaType(Double.class),
rowTypes.getFieldList().get(7).getType());
+ }
+
/** */
enum AggregateAlgorithm {
/** */
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/ScriptTestSuite.java
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/ScriptTestSuite.java
index d5a872b..e22a2c7 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/ScriptTestSuite.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/ScriptTestSuite.java
@@ -25,7 +25,7 @@ import org.junit.runner.RunWith;
/**
* Test suite to run SQL test scripts.
*
- * By default only "*.test" and "*.test_slow" scripts are run.
+ * By default, only "*.test" and "*.test_slow" scripts are run.
* Other files are ignored.
*
* Use {@link ScriptRunnerTestsEnvironment#regex()} property to specify
regular expression for filter
diff --git
a/modules/calcite/src/test/sql/aggregate/aggregates/test_aggr_string.test_ignore
b/modules/calcite/src/test/sql/aggregate/aggregates/test_aggr_string.test_ignore
deleted file mode 100644
index 6753827..0000000
---
a/modules/calcite/src/test/sql/aggregate/aggregates/test_aggr_string.test_ignore
+++ /dev/null
@@ -1,81 +0,0 @@
-# name: test/sql/aggregate/aggregates/test_aggr_string.test
-# description: Test aggregations on strings
-# group: [aggregates]
-
-query TTTTI
-SELECT NULL as a, NULL as b, NULL as c, NULL as d, 1 as id UNION SELECT
'Кирилл' as a, 'Müller' as b, '我是谁' as c, 'ASCII' as d, 2 as id ORDER BY 1
NULLS FIRST
-----
-NULL NULL NULL NULL 1
-Кирилл Müller 我是谁 ASCII 2
-
-statement ok
-CREATE TABLE test (a INTEGER, s VARCHAR);
-
-statement ok
-INSERT INTO test VALUES (11, 'hello'), (12, 'world'), (11, NULL)
-
-# scalar aggregation on string
-query II
-SELECT COUNT(*), COUNT(s) FROM test;
-----
-3 2
-
-# grouped aggregation on string
-query III
-SELECT a, COUNT(*), COUNT(s) FROM test GROUP BY a ORDER BY a;
-----
-11 2 1
-12 1 1
-
-# group by the strings
-query TR
-SELECT s, SUM(a) FROM test GROUP BY s ORDER BY s;
-----
-hello 11.000000
-world 12.000000
-NULL 11.000000
-
-# distinct aggregations on string
-statement ok
-INSERT INTO test VALUES (11, 'hello'), (12, 'world')
-
-# scalar distinct
-query III
-SELECT COUNT(*), COUNT(s), COUNT(DISTINCT s) FROM test;
-----
-5 4 2
-
-# grouped distinct
-query IIII
-SELECT a, COUNT(*), COUNT(s), COUNT(DISTINCT s) FROM test GROUP BY a ORDER BY
a;
-----
-11 3 2 1
-12 2 2 1
-
-# now with WHERE clause
-query IIII
-SELECT a, COUNT(*), COUNT(s), COUNT(DISTINCT s) FROM test WHERE s IS NOT NULL
GROUP BY a ORDER BY a;
-----
-11 2 2 1
-12 2 2 1
-
-# string min/max with long strings
-statement ok
-CREATE TABLE test_strings(s VARCHAR);
-INSERT INTO test_strings VALUES ('aaaaaaaahello'),
('bbbbbbbbbbbbbbbbbbbbhello'), ('ccccccccccccccchello'),
('aaaaaaaaaaaaaaaaaaaaaaaahello')
-
-query II
-SELECT MIN(s), MAX(s) FROM test_strings;
-----
-aaaaaaaaaaaaaaaaaaaaaaaahello ccccccccccccccchello
-
-# string min/max with long strings 2
-statement ok
-CREATE TABLE test_strings2(s VARCHAR);
-INSERT INTO test_strings2 VALUES ('a'), ('aa'), ('A'), ('AA'), ('D')
-
-query II
-SELECT MAX(s), MIN(s) FROM test_strings2;
-----
-aa A
-
diff --git
a/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
b/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
index 48b56b9..eb590af 100644
---
a/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
+++
b/modules/calcite/src/test/sql/aggregate/aggregates/test_perfect_ht.test_ignore
@@ -20,10 +20,10 @@ INSERT INTO timeseries VALUES (1996, 10), (1997, 12),
(1996, 20), (2001, 30), (N
query IIII
SELECT year, SUM(val), COUNT(val), COUNT(*) FROM timeseries GROUP BY year
ORDER BY year;
----
+NULL 1 1 1
1996 30 2 3
1997 12 1 1
2001 30 1 1
-NULL 1 1 1
# use aggregates with destructors
query III
diff --git
a/modules/calcite/src/test/sql/aggregate/aggregates/test_scalar_aggr.test_ignore
b/modules/calcite/src/test/sql/aggregate/aggregates/test_scalar_aggr.test_ignore
index 7c2a8a4..641295e 100644
---
a/modules/calcite/src/test/sql/aggregate/aggregates/test_scalar_aggr.test_ignore
+++
b/modules/calcite/src/test/sql/aggregate/aggregates/test_scalar_aggr.test_ignore
@@ -50,10 +50,9 @@ hello,hello,hello
query IIIIIT
SELECT COUNT(NULL), MIN(NULL), ANY_VALUE(NULL), MAX(NULL), SUM(NULL),
STRING_AGG(NULL, NULL) FROM integers
----
-0
-NULL
NULL
NULL
NULL
NULL
+0
diff --git a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
index 0593e6a..8a0b889 100644
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
+++ b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test
@@ -66,10 +66,16 @@ INSERT INTO bigints SELECT * FROM
table(system_range(4611686018427387904, 461168
# sum them up
query I
-SELECT SUM(b::DECIMAL) FROM bigints
+SELECT SUM(b) FROM bigints
----
4611686018427388403500
+# sum them up
+query T
+SELECT typeof(SUM(b)) FROM bigints
+----
+DECIMAL(32767, 0)
+
# this is too big for a bigint
statement error
SELECT SUM(b)::BIGINT FROM bigints
diff --git
a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
b/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
deleted file mode 100644
index c72e6f1..0000000
--- a/modules/calcite/src/test/sql/aggregate/aggregates/test_sum.test_ignore
+++ /dev/null
@@ -1,76 +0,0 @@
-# name: test/sql/aggregate/aggregates/test_sum.test
-# description: Test sum aggregate
-# group: [aggregates]
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-14681
-
-statement ok
-CREATE TABLE integers(i INTEGER);
-
-statement ok
-INSERT INTO integers SELECT * FROM table(system_range(0, 999, 1));
-
-# positive numbers
-query I
-SELECT SUM(i) FROM integers;
-----
-499500
-
-# negative numbers
-statement ok
-INSERT INTO integers SELECT * FROM table(system_range(0, -999, -1));
-
-query I
-SELECT SUM(i) FROM integers;
-----
-0
-
-# more negative numbers
-statement ok
-INSERT INTO integers SELECT * FROM table(system_range(0, -999, -1));
-
-query I
-SELECT SUM(i) FROM integers;
-----
--499500
-
-# now perform sum of a constant
-query I
-SELECT SUM(1) FROM integers;
-----
-3000
-
-# negative constant
-query I
-SELECT SUM(-1) FROM integers;
-----
--3000
-
-# negative constant with a low amount of values
-query I
-SELECT SUM(-1) FROM integers WHERE i=-1;
-----
--2
-
-# no values
-query I
-SELECT SUM(-1) FROM integers WHERE i>10000;
-----
-NULL
-
-# bigint sum
-statement ok
-CREATE TABLE bigints(b BIGINT);
-
-# a bunch of huge values
-statement ok
-INSERT INTO bigints SELECT * FROM table(system_range(4611686018427387904,
4611686018427388903, 1));
-
-# sum them up
-query I
-SELECT SUM(b) FROM bigints
-----
-4611686018427388403500
-
-# this is too big for a bigint
-statement error
-SELECT SUM(b)::BIGINT FROM bigints