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 c017770d3cb [fix](analysis) Hide internal HAVING helper outputs 
(#67812)
c017770d3cb is described below

commit c017770d3cbc0ee1aa68529a31a35960fa2d4328
Author: morrySnow <[email protected]>
AuthorDate: Sat Sep 12 01:12:12 2026 +0800

    [fix](analysis) Hide internal HAVING helper outputs (#67812)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    
    A scalar subquery that exposes one expression can be rejected as
    returning two columns when an aggregate is used only by its `HAVING`
    clause.
    
    For example:
    
    ```sql
    CREATE TABLE t (id SMALLINT) DISTRIBUTED BY RANDOM PROPERTIES 
('replication_num' = '1');
    INSERT INTO t VALUES (1);
    SELECT (SELECT 1 FROM t HAVING SUM(id) > 0);
    ```
    
    The fill-up analysis phase adds aggregate and missing-slot expressions
    to an internal project so that `HAVING` and `ORDER BY` can resolve them.
    In the aggregate path, it did not restore the original project
    afterwards. The helper aggregate therefore leaked into the public
    output, and scalar-subquery validation reported `Found 2`.
    
    The rule now always adds a final project with the original output after
    `HAVING` and `ORDER BY` consume their helper slots. This keeps internal
    dependencies available during analysis while preserving the query's
    public output contract.
    
    Tests cover the valid scalar subquery, an empty result caused by
    `HAVING`, and a genuine two-column scalar subquery that must still be
    rejected.
    
    ### Release note
    
    Fix false multi-column errors for scalar subqueries whose aggregate
    appears only in `HAVING`.
    
    ### Check List (For Author)
    
    - Test: Unit Test and Regression Test
    - Behavior changed: Yes. Internal helper slots no longer appear in query
    output.
    - Does this need documentation: No
---
 .../nereids/rules/analysis/FillUpMissingSlots.java |  8 ++---
 .../rules/analysis/FillUpMissingSlotsTest.java     | 18 +++++++++++
 .../rules/analysis/NormalizeAggregateTest.java     | 37 ++++++++++++----------
 .../test_having_with_aggregate_function.out        |  5 +++
 .../test_having_with_aggregate_function.groovy     | 15 +++++++++
 5 files changed, 62 insertions(+), 21 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 30440b83ffe..6f7dc1de191 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
@@ -504,11 +504,9 @@ public class FillUpMissingSlots implements 
AnalysisRuleFactory {
             }
             result = oldSort.get().withOrderKeysAndChild(newOrderKeys, result);
         }
-        if (!hasAggregateFunc.get()) {
-            // handle for miss slots case, add a top project
-            result = new 
LogicalProject<>(ImmutableList.copyOf(oldProject.getOutput()), result);
-        }
-        return result;
+        // The outputs appended for HAVING and ORDER BY are implementation 
details. Restore the
+        // original projection contract after those operators have consumed 
their helper slots.
+        return new 
LogicalProject<>(ImmutableList.copyOf(oldProject.getOutput()), result);
     }
 
     private void collectNotExistsSlotAndAggFunc(Expression expression, 
Set<Slot> oldProjectSlots,
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 d3fab2e4856..1baf69c0555 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
@@ -319,6 +319,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 37aa9d4913a..d65c8b82129 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 fcf06b6b3bf..64fd7bfad76 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
@@ -10,3 +10,8 @@
 -- !having_array_lambda_local_slots --
 1      2
 
+-- !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 04a2ec51039..2cd7d0e8484 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
@@ -68,4 +68,19 @@ suite("test_having_project") {
         sql "SELECT 1 AS c1 FROM t HAVING count(1) > 0 OR c1 IS NOT NULL"
         exception "HAVING expression 'c1' must appear in the GROUP BY clause 
or be used in an aggregate function"
     }
+
+    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