This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 1aa13de013a branch-4.1: [fix](analysis) Hide internal HAVING helper
outputs #67812 (#68075)
1aa13de013a is described below
commit 1aa13de013a2d13119d63ab4c51655fe5ea10dbc
Author: morrySnow <[email protected]>
AuthorDate: Sun Sep 20 09:56:05 2026 +0800
branch-4.1: [fix](analysis) Hide internal HAVING helper outputs #67812
(#68075)
### What problem does this PR solve?
Related PR: #67812
Problem Summary:
Backport the fix that prevents internal aggregate slots added for HAVING
analysis from leaking into the query output. Without the final
projection, a scalar subquery such as `SELECT (SELECT 1 FROM t HAVING
SUM(id) > 0)` is incorrectly treated as returning two columns.
The implementation is adapted to branch-4.1's older
`FILL_UP_HAVING_PROJECT` rule by restoring the original project output
after HAVING consumes the helper aggregate slot, without bringing in
unrelated later analysis-rule refactoring.
### Release note
Fix false multi-column errors for scalar subqueries whose aggregate
appears only in HAVING.
### Check List (For Author)
- Test
- [x] Unit Test
- [x] Regression test coverage included
- Behavior changed:
- [x] Yes. Internal HAVING helper outputs are no longer exposed as query
outputs.
- Does this need documentation?
- [x] No.
Test details:
- `FillUpMissingSlotsTest` and `NormalizeAggregateTest`: 30 tests
passed.
- Full FE Maven reactor: `BUILD SUCCESS`.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.../nereids/rules/analysis/FillUpMissingSlots.java | 4 ++-
.../rules/analysis/FillUpMissingSlotsTest.java | 20 ++++++++++++
.../rules/analysis/NormalizeAggregateTest.java | 37 ++++++++++++----------
.../test_having_with_aggregate_function.out | 4 +++
.../test_having_with_aggregate_function.groovy | 15 +++++++++
5 files changed, 63 insertions(+), 17 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlots.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlots.java
index 3f92f612152..709ed3b6297 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlots.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlots.java
@@ -232,7 +232,9 @@ public class FillUpMissingSlots implements
AnalysisRuleFactory {
adjustAggNullableConjuncts,
resolver.getSubstitution());
ImmutableList.Builder<NamedExpression> projects =
ImmutableList.builder();
projects.addAll(project.getOutputs()).addAll(agg.getOutput());
- return new LogicalHaving<>(newConjuncts, new
LogicalProject<>(projects.build(), agg));
+ Plan child = new LogicalHaving<>(
+ newConjuncts, new
LogicalProject<>(projects.build(), agg));
+ return new
LogicalProject<>(ImmutableList.copyOf(project.getOutput()), child);
} else {
LogicalProject<Plan> project = having.child();
Set<Slot> projectOutputSet =
project.getOutputSet();
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlotsTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlotsTest.java
index b329eed2753..56ebd5c7459 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlotsTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/FillUpMissingSlotsTest.java
@@ -37,6 +37,7 @@ import
org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
+import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.types.BigIntType;
import org.apache.doris.nereids.types.IntegerType;
import org.apache.doris.nereids.types.SmallIntType;
@@ -48,6 +49,7 @@ import org.apache.doris.nereids.util.PlanChecker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class FillUpMissingSlotsTest extends AnalyzeCheckTestBase implements
MemoPatternMatchSupported {
@@ -285,6 +287,24 @@ public class FillUpMissingSlotsTest extends
AnalyzeCheckTestBase implements Memo
).when(FieldChecker.check("projects",
Lists.newArrayList(a1.toSlot()))));
}
+ @Test
+ void testHavingAggregateFunctionDoesNotLeakHelperOutput() {
+ Plan plan = PlanChecker.from(connectContext)
+ .analyze("SELECT 1 FROM t1 HAVING SUM(a1) > 0")
+ .getPlan();
+ Assertions.assertEquals(1, plan.getOutput().size());
+
+ PlanChecker.from(connectContext)
+ .analyze("SELECT (SELECT 1 FROM t1 HAVING SUM(a1) > 0)");
+
+ ExceptionChecker.expectThrowsWithMsg(
+ AnalysisException.class,
+ "Multiple columns returned by subquery are not yet supported.
Found 2",
+ () -> PlanChecker.from(connectContext).analyze(
+ "SELECT (SELECT 1, 2 FROM t1 HAVING SUM(a1) > 0)"
+ ));
+ }
+
@Test
void testJoinWithHaving() {
String sql = "SELECT a1, sum(a2) FROM t1, t2 WHERE t1.pk = t2.pk GROUP
BY a1 HAVING a1 > sum(b1)";
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/NormalizeAggregateTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/NormalizeAggregateTest.java
index 78e66da8638..e1c22e2a624 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/NormalizeAggregateTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/NormalizeAggregateTest.java
@@ -492,31 +492,36 @@ public class NormalizeAggregateTest extends
TestWithFeService implements MemoPat
.analyze("select 1 from t1 having sum(id) > 10")
.matchesFromRoot(
logicalResultSink(
- logicalFilter(
- logicalProject(
+ logicalProject(
+ logicalFilter(
logicalProject(
-
logicalAggregate().when(agg -> {
- List<Slot> output
= agg.getOutput();
-
checkExprsToSql(output, "sum(id)");
-
Assertions.assertTrue(output.get(0).nullable());
+ logicalProject(
+
logicalAggregate().when(agg -> {
+ List<Slot>
output = agg.getOutput();
+
checkExprsToSql(output, "sum(id)");
+
Assertions.assertTrue(output.get(0).nullable());
+ return
true;
+ })
+ ).when(project -> {
+
List<NamedExpression> projects = project.getProjects();
+
checkExprsToSql(projects, "sum(id)");
+
Assertions.assertTrue(projects.get(0).nullable());
return true;
})
).when(project -> {
List<NamedExpression>
projects = project.getProjects();
- checkExprsToSql(projects,
"sum(id)");
-
Assertions.assertTrue(projects.get(0).nullable());
+ checkExprsToSql(projects,
"1 AS `1`", "sum(id)");
+
Assertions.assertTrue(projects.get(1).nullable());
return true;
})
- ).when(project -> {
- List<NamedExpression> projects =
project.getProjects();
- checkExprsToSql(projects, "1 AS
`1`", "sum(id)");
-
Assertions.assertTrue(projects.get(1).nullable());
+ ).when(filter -> {
+ List<Expression> conjuncts =
filter.getExpressions();
+ checkExprsToSql(conjuncts,
"(sum(id) > 10)");
+
Assertions.assertTrue(conjuncts.get(0).child(0).nullable());
return true;
})
- ).when(filter -> {
- List<Expression> conjuncts =
filter.getExpressions();
- checkExprsToSql(conjuncts, "(sum(id) >
10)");
-
Assertions.assertTrue(conjuncts.get(0).child(0).nullable());
+ ).when(project -> {
+ checkExprsToSql(project.getProjects(),
"1");
return true;
})
)
diff --git
a/regression-test/data/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.out
b/regression-test/data/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.out
index 2bef87ab2b7..a00e2503dcc 100644
---
a/regression-test/data/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.out
+++
b/regression-test/data/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.out
@@ -2,3 +2,7 @@
-- !having_project_with_having_count_1_and_slot_from_project --
1
+-- !scalar_subquery_having_true --
+1
+
+-- !scalar_subquery_having_false --
diff --git
a/regression-test/suites/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.groovy
b/regression-test/suites/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.groovy
index 9047a7c85bf..fc6a9bfc522 100644
---
a/regression-test/suites/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.groovy
+++
b/regression-test/suites/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.groovy
@@ -29,4 +29,19 @@ suite("test_having_project") {
qt_having_project_with_having_count_1_and_slot_from_project """
SELECT 1 AS c1 FROM t HAVING count(1) > 0 OR c1 IS NOT NULL
"""
+
+ sql "INSERT INTO t VALUES (1)"
+
+ qt_scalar_subquery_having_true """
+ SELECT (SELECT 1 FROM t HAVING SUM(id) > 0) AS scalar_value
+ """
+
+ qt_scalar_subquery_having_false """
+ SELECT (SELECT 1 FROM t HAVING SUM(id) < 0) AS scalar_value
+ """
+
+ test {
+ sql "SELECT (SELECT 1, 2 FROM t HAVING SUM(id) > 0)"
+ exception "Multiple columns returned by subquery are not yet
supported. Found 2"
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]