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]