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]

Reply via email to