This is an automated email from the ASF dual-hosted git repository.
morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 093f6b1b27a [fix](aggregate) Preserve AVG accumulator width in
distinct rewrite (#67740)
093f6b1b27a is described below
commit 093f6b1b27af28bfa5e827060e374fbb73b12292
Author: morrySnow <[email protected]>
AuthorDate: Thu Sep 10 12:18:45 2026 +0800
[fix](aggregate) Preserve AVG accumulator width in distinct rewrite (#67740)
## Problem
When multiple DISTINCT aggregates trigger the AVG decomposition rewrite,
`AVG(DISTINCT BIGINT)` can return an incorrect value. For example,
averaging
`9223372036854775807` and `9223372036854775806` produced `-1.5`; a
predicate
such as `avg_value > 0` could therefore discard a row that should match.
## Root cause
The rewrite decomposed `AVG(DISTINCT BIGINT)` into
`SUM(DISTINCT BIGINT) / COUNT(DISTINCT BIGINT)`. Native AVG uses a
LARGEINT
accumulator for BIGINT input, while SUM keeps a BIGINT accumulator. The
SUM
overflowed before the division result was converted to AVG's return
type.
## Reproduction
```sql
SELECT AVG(DISTINCT x)
FROM (
SELECT CAST(9223372036854775807 AS BIGINT) AS x
UNION ALL
SELECT CAST(9223372036854775806 AS BIGINT) AS x
) t;
```
With the multi-distinct rewrite enabled, the result was `-1.5` instead
of
approximately `9.223372036854776e18`.
## Fix
Losslessly widen a BIGINT AVG argument to LARGEINT before constructing
the
replacement SUM and COUNT. The same widened expression is reused by both
aggregates, preserving the shared DISTINCT argument required by the
multi-distinct rewrite while matching AVG's original accumulator width.
## Tests
- Added a focused rewrite unit test that verifies the SUM uses LARGEINT
and
the generated COUNT shares the same widened argument.
- Added a regression case using the two BIGINT boundary values above
together
with another DISTINCT aggregate and an outer positive-value filter.
- The focused FE unit test passed: 1 test, 0 failures.
- The regression suite passed: 1 suite, 0 failed suites.
- Sandbox verification changed the result from `-1.5`/0 matching rows to
`9.223372036854776e18`/1 matching row.
---
.../rules/analysis/AvgDistinctToSumDivCount.java | 12 +++-
.../analysis/AvgDistinctToSumDivCountTest.java | 72 ++++++++++++++++++++++
.../avg_distinct_to_sum_div_count.out | 2 +
.../avg_distinct_to_sum_div_count.groovy | 13 ++++
4 files changed, 97 insertions(+), 2 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCount.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCount.java
index 2740e826976..581d58c585f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCount.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCount.java
@@ -29,6 +29,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.agg.Count;
import org.apache.doris.nereids.trees.expressions.functions.agg.Sum;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NonNullable;
import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
+import org.apache.doris.nereids.types.LargeIntType;
import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.nereids.util.TypeCoercionUtils;
@@ -52,11 +53,18 @@ public class AvgDistinctToSumDivCount extends
OneRewriteRuleFactory {
.stream()
.filter(function -> function instanceof Avg &&
function.isDistinct())
.collect(ImmutableMap.toImmutableMap(function ->
function, function -> {
+ Expression argument = ((Avg) function).child();
+ // AVG(BIGINT) accumulates in LARGEINT, while
SUM(BIGINT) accumulates
+ // in BIGINT. Preserve AVG's wider accumulator
when decomposing it.
+ if (argument.getDataType().isBigIntType()) {
+ argument =
TypeCoercionUtils.castIfNotSameType(
+ argument, LargeIntType.INSTANCE);
+ }
Sum sum = (Sum)
TypeCoercionUtils.processBoundFunction(
new Sum(true, ((Avg)
function).isAlwaysNullable(), false,
- ((Avg) function).child()));
+ argument));
Count count = (Count)
TypeCoercionUtils.processBoundFunction(
- new Count(true, ((Avg)
function).child()));
+ new Count(true, argument));
Expression divide =
TypeCoercionUtils.castIfNotSameType(TypeCoercionUtils.processDivide(
new Divide(sum, count)),
function.getDataType());
if (!function.nullable() && divide.nullable())
{
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCountTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCountTest.java
new file mode 100644
index 00000000000..6deb4b2aee0
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/AvgDistinctToSumDivCountTest.java
@@ -0,0 +1,72 @@
+// 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.doris.nereids.rules.analysis;
+
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
+import
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Avg;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Sum;
+import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOneRowRelation;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.MemoPatternMatchSupported;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.nereids.util.TypeCoercionUtils;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+public class AvgDistinctToSumDivCountTest implements MemoPatternMatchSupported
{
+ @Test
+ public void testBigIntUsesLargeIntAccumulator() {
+ SlotReference value = new SlotReference("value", BigIntType.INSTANCE,
false);
+ SlotReference other = new SlotReference("other", IntegerType.INSTANCE,
false);
+ LogicalOneRowRelation relation = new LogicalOneRowRelation(
+ StatementScopeIdGenerator.newRelationId(),
ImmutableList.of(value, other));
+
+ Avg avg = (Avg) TypeCoercionUtils.processBoundFunction(new Avg(true,
value));
+ Count count = (Count) TypeCoercionUtils.processBoundFunction(new
Count(true, other));
+ NamedExpression avgOutput = new Alias(avg, "average");
+ NamedExpression countOutput = new Alias(count, "count");
+ LogicalAggregate<LogicalOneRowRelation> aggregate = new
LogicalAggregate<>(
+ ImmutableList.of(), ImmutableList.of(avgOutput, countOutput),
relation);
+
+ PlanChecker.from(MemoTestUtils.createConnectContext(), aggregate)
+ .applyTopDown(new AvgDistinctToSumDivCount())
+ .matches(logicalAggregate().when(rewritten -> {
+ Set<AggregateFunction> functions =
rewritten.getAggregateFunctions();
+ Sum sum = (Sum) functions.stream()
+ .filter(Sum.class::isInstance)
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("rewritten
SUM is missing"));
+ boolean countSharesWidenedArgument = functions.stream()
+ .filter(Count.class::isInstance)
+ .anyMatch(function ->
function.child(0).equals(sum.child(0)));
+ return sum.child(0).getDataType().isLargeIntType()
+ && countSharesWidenedArgument;
+ }));
+ }
+}
diff --git
a/regression-test/data/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.out
b/regression-test/data/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.out
index 38654c9ecca..a0f210574be 100644
---
a/regression-test/data/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.out
+++
b/regression-test/data/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.out
@@ -2,3 +2,5 @@
-- !ctas --
135.55 67.7750
+-- !bigint_avg_distinct_overflow --
+1 2
diff --git
a/regression-test/suites/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.groovy
b/regression-test/suites/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.groovy
index 826bda8fd8b..a57c4ad8884 100644
---
a/regression-test/suites/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.groovy
+++
b/regression-test/suites/nereids_rules_p0/avg_distinct_to_sum_div_count/avg_distinct_to_sum_div_count.groovy
@@ -26,4 +26,17 @@ suite("avg_distinct_to_sum_div_count") {
sql "drop table if exists testctas;"
sql """create table testctas properties("replication_num" = "1") select
sum(distinct c1),avg(distinct c1) from t1;"""
qt_ctas "select * from testctas;"
+
+ qt_bigint_avg_distinct_overflow """
+ SELECT COUNT(*) AS matched_rows, MAX(c) AS distinct_count
+ FROM (
+ SELECT AVG(DISTINCT x) AS a, COUNT(DISTINCT y) AS c
+ FROM (
+ SELECT CAST(9223372036854775807 AS BIGINT) AS x, CAST(1 AS
INT) AS y
+ UNION ALL
+ SELECT CAST(9223372036854775806 AS BIGINT) AS x, CAST(2 AS
INT) AS y
+ ) t
+ ) q
+ WHERE a > 0
+ """
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]