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

Reply via email to