This is an automated email from the ASF dual-hosted git repository.
morrysnow pushed a commit to branch branch-2.0
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.0 by this push:
new e7b19b34512 [fix](Nereids) support aggregate function only in having
statement (#34086) (#34338)
e7b19b34512 is described below
commit e7b19b3451231fd4f52d7e44826b10e1d00d0fba
Author: morrySnow <[email protected]>
AuthorDate: Tue Apr 30 15:35:51 2024 +0800
[fix](Nereids) support aggregate function only in having statement (#34086)
(#34338)
pick from master
PR #34086
commit id dfe30f542d89f74c008f05d1b7241c8d33c94d6c
SQL like
> SELECT 1 AS c1 FROM t HAVING count(1) > 0 OR c1 IS NOT NULL
---
.../nereids/rules/analysis/FillUpMissingSlots.java | 75 +++++++++++++++++-----
.../rules/analysis/FillUpMissingSlotsTest.java | 2 +-
.../test_having_with_aggregate_function.out | 4 ++
.../test_having_with_aggregate_function.groovy | 32 +++++++++
4 files changed, 95 insertions(+), 18 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 1a59ba00adf..0a70ccaee92 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
@@ -30,6 +30,7 @@ import
org.apache.doris.nereids.trees.expressions.SlotReference;
import
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.algebra.Aggregate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
import org.apache.doris.nereids.trees.plans.logical.LogicalHaving;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
@@ -157,20 +158,52 @@ public class FillUpMissingSlots implements
AnalysisRuleFactory {
),
RuleType.FILL_UP_HAVING_PROJECT.build(
logicalHaving(logicalProject()).then(having -> {
- LogicalProject<Plan> project = having.child();
- Set<Slot> projectOutputSet = project.getOutputSet();
- Set<Slot> notExistedInProject =
having.getExpressions().stream()
- .map(Expression::getInputSlots)
- .flatMap(Set::stream)
- .filter(s -> !projectOutputSet.contains(s))
- .collect(Collectors.toSet());
- if (notExistedInProject.size() == 0) {
- return null;
+ if (having.getExpressions().stream().anyMatch(e ->
e.containsType(AggregateFunction.class))) {
+ // This is very weird pattern.
+ // There are some aggregate functions in having,
but its child is project.
+ // There are some slot from project in having too.
+ // Having should execute after project.
+ // But aggregate function should execute before
project.
+ // Since no aggregate here, we should add an empty
aggregate before project.
+ // We should push aggregate function into
aggregate node first.
+ // Then put aggregate result slots and original
project slots into new project.
+ // The final plan should be
+ // Having
+ // +-- Project
+ // +-- Aggregate
+ // Since aggregate node have no group by key.
+ // So project should not contain any slot from its
original child.
+ // Or otherwise slot cannot find will be thrown.
+ LogicalProject<Plan> project = having.child();
+ // new an empty agg here
+ LogicalAggregate<Plan> agg = new
LogicalAggregate<>(
+ ImmutableList.of(), ImmutableList.of(),
project.child());
+ // avoid throw exception even if having have slot
from its child.
+ // because we will add a project between having
and project.
+ Resolver resolver = new Resolver(agg, false);
+ having.getConjuncts().forEach(resolver::resolve);
+ agg =
agg.withAggOutput(resolver.getNewOutputSlots());
+ Set<Expression> newConjuncts =
ExpressionUtils.replace(
+ having.getConjuncts(),
resolver.getSubstitution());
+ ImmutableList.Builder<NamedExpression> projects =
ImmutableList.builder();
+
projects.addAll(project.getOutputs()).addAll(agg.getOutput());
+ return new LogicalHaving<>(newConjuncts, new
LogicalProject<>(projects.build(), agg));
+ } else {
+ LogicalProject<Plan> project = having.child();
+ Set<Slot> projectOutputSet =
project.getOutputSet();
+ Set<Slot> notExistedInProject =
having.getExpressions().stream()
+ .map(Expression::getInputSlots)
+ .flatMap(Set::stream)
+ .filter(s -> !projectOutputSet.contains(s))
+ .collect(Collectors.toSet());
+ if (notExistedInProject.isEmpty()) {
+ return null;
+ }
+ List<NamedExpression> projects =
ImmutableList.<NamedExpression>builder()
+
.addAll(project.getProjects()).addAll(notExistedInProject).build();
+ return new
LogicalProject<>(ImmutableList.copyOf(project.getOutput()),
+ having.withChildren(new
LogicalProject<>(projects, project.child())));
}
- List<NamedExpression> projects =
ImmutableList.<NamedExpression>builder()
-
.addAll(project.getProjects()).addAll(notExistedInProject).build();
- return new
LogicalProject<>(ImmutableList.copyOf(project.getOutput()),
- having.withChildren(new
LogicalProject<>(projects, project.child())));
})
)
);
@@ -183,13 +216,19 @@ public class FillUpMissingSlots implements
AnalysisRuleFactory {
private final Map<Expression, Slot> substitution = Maps.newHashMap();
private final List<NamedExpression> newOutputSlots =
Lists.newArrayList();
private final Map<Slot, Expression> outputSubstitutionMap;
+ private final boolean checkSlot;
- Resolver(Aggregate aggregate) {
+ Resolver(Aggregate<?> aggregate, boolean checkSlot) {
outputExpressions = aggregate.getOutputExpressions();
groupByExpressions = aggregate.getGroupByExpressions();
outputSubstitutionMap =
outputExpressions.stream().filter(Alias.class::isInstance)
- .collect(Collectors.toMap(alias -> alias.toSlot(), alias
-> alias.child(0),
- (k1, k2) -> k1));
+ .collect(Collectors.toMap(NamedExpression::toSlot, alias
-> alias.child(0),
+ (k1, k2) -> k1));
+ this.checkSlot = checkSlot;
+ }
+
+ Resolver(Aggregate<?> aggregate) {
+ this(aggregate, true);
}
public void resolve(Expression expression) {
@@ -217,7 +256,9 @@ public class FillUpMissingSlots implements
AnalysisRuleFactory {
// We couldn't find the equivalent expression in output
expressions and group-by expressions,
// so we should check whether the expression is valid.
if (expression instanceof SlotReference) {
- throw new AnalysisException(expression.toSql() + " in
having clause should be grouped by.");
+ if (checkSlot) {
+ throw new AnalysisException(expression.toSql() + "
should be grouped by.");
+ }
} else if (expression instanceof AggregateFunction) {
if
(checkWhetherNestedAggregateFunctionsExist((AggregateFunction) expression)) {
throw new AnalysisException("Aggregate functions in
having clause can't be nested: "
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 534833754bd..7e6c6b0c794 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
@@ -306,7 +306,7 @@ public class FillUpMissingSlotsTest extends
AnalyzeCheckTestBase implements Memo
void testInvalidHaving() {
ExceptionChecker.expectThrowsWithMsg(
AnalysisException.class,
- "a2 in having clause should be grouped by.",
+ "a2 should be grouped by",
() -> PlanChecker.from(connectContext).analyze(
"SELECT a1 FROM t1 GROUP BY a1 HAVING a2 > 0"
));
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
new file mode 100644
index 00000000000..2bef87ab2b7
--- /dev/null
+++
b/regression-test/data/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.out
@@ -0,0 +1,4 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !having_project_with_having_count_1_and_slot_from_project --
+1
+
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
new file mode 100644
index 00000000000..9047a7c85bf
--- /dev/null
+++
b/regression-test/suites/nereids_rules_p0/fill_up_missing_slots/test_having_with_aggregate_function.groovy
@@ -0,0 +1,32 @@
+// 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.
+
+suite("test_having_project") {
+ sql "SET enable_nereids_planner=true"
+ sql "SET enable_fallback_to_original_planner=false"
+
+ sql """
+ DROP TABLE IF EXISTS t
+ """
+ sql """
+ create table t(id smallint) distributed by random
properties('replication_num'='1');
+ """
+
+ 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
+ """
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]