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 a465549f5e [feature](Nereids)support parse and analyze having clause 
(#12129)
a465549f5e is described below

commit a465549f5ed145ca072daa7d7a444276947235ff
Author: Adonis Ling <[email protected]>
AuthorDate: Wed Sep 7 09:47:03 2022 +0800

    [feature](Nereids)support parse and analyze having clause (#12129)
    
    Implement the having clause for Nereids Planner.
    
    NOTE:
    
    This PR aims at making Nereids Planner generate the correct logical plan 
and physical plan only. The runtime correctness is not the goal in this PR due 
to GROUP BY is not ready in Nereids Planner.
---
 .../doris/nereids/jobs/batch/AnalyzeRulesJob.java  |   2 +
 .../doris/nereids/parser/LogicalPlanBuilder.java   |  21 +-
 .../org/apache/doris/nereids/rules/RuleType.java   |   3 +
 .../doris/nereids/rules/analysis/BindFunction.java |   7 +
 .../nereids/rules/analysis/BindSlotReference.java  |  18 +-
 .../nereids/rules/analysis/ResolveHaving.java      | 189 ++++++++++
 .../expressions/visitor/ExpressionReplacer.java    |   4 +-
 .../apache/doris/nereids/trees/plans/PlanType.java |   1 +
 .../nereids/trees/plans/logical/LogicalHaving.java | 112 ++++++
 .../nereids/trees/plans/visitor/PlanVisitor.java   |   5 +
 .../doris/nereids/parser/HavingClauseTest.java     | 386 +++++++++++++++++++++
 .../apache/doris/utframe/TestWithFeService.java    |   2 +
 regression-test/data/nereids_syntax_p0/having.out  |  56 +++
 .../suites/nereids_syntax_p0/having.groovy         |  58 ++++
 14 files changed, 857 insertions(+), 7 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/AnalyzeRulesJob.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/AnalyzeRulesJob.java
index 0f7e3a0032..87c50a109e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/AnalyzeRulesJob.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/batch/AnalyzeRulesJob.java
@@ -22,6 +22,7 @@ import org.apache.doris.nereids.rules.analysis.BindFunction;
 import org.apache.doris.nereids.rules.analysis.BindRelation;
 import org.apache.doris.nereids.rules.analysis.BindSlotReference;
 import org.apache.doris.nereids.rules.analysis.ProjectToGlobalAggregate;
+import org.apache.doris.nereids.rules.analysis.ResolveHaving;
 import org.apache.doris.nereids.rules.analysis.Scope;
 
 import com.google.common.collect.ImmutableList;
@@ -45,6 +46,7 @@ public class AnalyzeRulesJob extends BatchRulesJob {
                         new BindRelation(),
                         new BindSlotReference(scope),
                         new BindFunction(),
+                        new ResolveHaving(),
                         new ProjectToGlobalAggregate())
                 )));
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index fdb2756145..5aa750e34c 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -31,6 +31,7 @@ import 
org.apache.doris.nereids.DorisParser.DereferenceContext;
 import org.apache.doris.nereids.DorisParser.ExistContext;
 import org.apache.doris.nereids.DorisParser.ExplainContext;
 import org.apache.doris.nereids.DorisParser.FromClauseContext;
+import org.apache.doris.nereids.DorisParser.HavingClauseContext;
 import org.apache.doris.nereids.DorisParser.HintAssignmentContext;
 import org.apache.doris.nereids.DorisParser.HintStatementContext;
 import org.apache.doris.nereids.DorisParser.IdentifierListContext;
@@ -123,6 +124,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.ExplainCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.ExplainCommand.ExplainLevel;
 import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalHaving;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
@@ -153,7 +155,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
- * Build an logical plan tree with unbounded nodes.
+ * Build a logical plan tree with unbounded nodes.
  */
 public class LogicalPlanBuilder extends DorisParserBaseVisitor<Object> {
 
@@ -224,7 +226,8 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
                 ctx, relation,
                 ctx.selectClause(),
                 Optional.ofNullable(ctx.whereClause()),
-                Optional.ofNullable(ctx.aggClause())
+                Optional.ofNullable(ctx.aggClause()),
+                Optional.ofNullable(ctx.havingClause())
             );
         });
     }
@@ -758,7 +761,8 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
             LogicalPlan inputRelation,
             SelectClauseContext selectClause,
             Optional<WhereClauseContext> whereClause,
-            Optional<AggClauseContext> aggClause) {
+            Optional<AggClauseContext> aggClause,
+            Optional<HavingClauseContext> havingClause) {
         return ParserUtils.withOrigin(ctx, () -> {
             // TODO: add lateral views
 
@@ -767,7 +771,7 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
             LogicalPlan filter = withFilter(inputRelation, whereClause);
             LogicalPlan aggregate = withAggregate(filter, selectClause, 
aggClause);
             // TODO: replace and process having at this position
-            LogicalPlan having = aggregate; // LogicalPlan having = 
withFilter(aggregate, havingClause);
+            LogicalPlan having = withHaving(aggregate, havingClause);
             LogicalPlan projection = withProjection(having, selectClause, 
aggClause);
             return withSelectHint(projection, selectClause.selectHint());
         });
@@ -872,6 +876,15 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         });
     }
 
+    private LogicalPlan withHaving(LogicalPlan input, 
Optional<HavingClauseContext> havingCtx) {
+        return input.optionalMap(havingCtx, () -> {
+            if (!(input instanceof LogicalAggregate)) {
+                throw new ParseException("Having clause should be applied 
against an aggregation.", havingCtx.get());
+            }
+            return new 
LogicalHaving<>(getExpression((havingCtx.get().booleanExpression())), input);
+        });
+    }
+
     /**
      * match predicate type and generate different predicates.
      *
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
index d545013506..20c3a9fb79 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/RuleType.java
@@ -41,6 +41,9 @@ public enum RuleType {
     BINDING_AGGREGATE_FUNCTION(RuleTypeClass.REWRITE),
     BINDING_SUBQUERY_ALIAS_SLOT(RuleTypeClass.REWRITE),
     BINDING_FILTER_FUNCTION(RuleTypeClass.REWRITE),
+    BINDING_HAVING_SLOT(RuleTypeClass.REWRITE),
+    BINDING_HAVING_FUNCTION(RuleTypeClass.REWRITE),
+    RESOLVE_HAVING(RuleTypeClass.REWRITE),
 
     RESOLVE_PROJECT_ALIAS(RuleTypeClass.REWRITE),
     RESOLVE_AGGREGATE_ALIAS(RuleTypeClass.REWRITE),
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java
index 2e45e4c4cb..567d2169ff 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindFunction.java
@@ -34,6 +34,7 @@ import 
org.apache.doris.nereids.trees.expressions.functions.WeekOfYear;
 import org.apache.doris.nereids.trees.expressions.functions.Year;
 import 
org.apache.doris.nereids.trees.expressions.visitor.DefaultExpressionRewriter;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalHaving;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
 import org.apache.doris.nereids.types.DateTimeType;
 import org.apache.doris.nereids.types.DateType;
@@ -69,6 +70,12 @@ public class BindFunction implements AnalysisRuleFactory {
                    List<Expression> predicates = bind(filter.getExpressions());
                    return new LogicalFilter<>(predicates.get(0), 
filter.child());
                })
+            ),
+            RuleType.BINDING_HAVING_FUNCTION.build(
+                logicalHaving(logicalAggregate()).then(filter -> {
+                    List<Expression> predicates = 
bind(filter.getExpressions());
+                    return new LogicalHaving<>(predicates.get(0), 
filter.child());
+                })
             )
         );
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSlotReference.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSlotReference.java
index fcb2bd2eda..c5c7503c4d 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSlotReference.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSlotReference.java
@@ -42,6 +42,7 @@ import org.apache.doris.nereids.trees.plans.LeafPlan;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalHaving;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
@@ -56,6 +57,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -132,7 +134,21 @@ public class BindSlotReference implements 
AnalysisRuleFactory {
                     return new LogicalSort<>(sortItemList, sort.child());
                 })
             ),
-
+            RuleType.BINDING_HAVING_SLOT.build(
+                logicalHaving(logicalAggregate()).thenApply(ctx -> {
+                    LogicalHaving<LogicalAggregate<GroupPlan>> having = 
ctx.root;
+                    LogicalAggregate<GroupPlan> aggregate = having.child();
+                    // We should deduplicate the slots, otherwise the binding 
process will fail due to the
+                    // ambiguous slots exist.
+                    Set<Slot> boundSlots = Stream.concat(Stream.of(aggregate), 
aggregate.children().stream())
+                            .flatMap(plan -> plan.getOutput().stream())
+                            .collect(Collectors.toSet());
+                    Expression boundPredicates = new SlotBinder(
+                            toScope(new ArrayList<>(boundSlots)), having, 
ctx.cascadesContext
+                    ).bind(having.getPredicates());
+                    return new LogicalHaving<>(boundPredicates, 
having.child());
+                })
+            ),
             RuleType.BINDING_NON_LEAF_LOGICAL_PLAN.build(
                 logicalPlan()
                         .when(plan -> plan.canBind() && !(plan instanceof 
LeafPlan))
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ResolveHaving.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ResolveHaving.java
new file mode 100644
index 0000000000..c42cc868e9
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ResolveHaving.java
@@ -0,0 +1,189 @@
+// 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.
+
+package org.apache.doris.nereids.rules.analysis;
+
+import org.apache.doris.common.Pair;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.rules.Rule;
+import org.apache.doris.nereids.rules.RuleType;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.AggregateFunction;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionReplacer;
+import org.apache.doris.nereids.trees.plans.GroupPlan;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalHaving;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Streams;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Resolve having clause to the aggregation.
+ */
+public class ResolveHaving extends OneAnalysisRuleFactory {
+    @Override
+    public Rule build() {
+        return RuleType.RESOLVE_HAVING.build(
+            logicalHaving(logicalAggregate()).thenApply(ctx -> {
+                LogicalHaving<LogicalAggregate<GroupPlan>> having = ctx.root;
+                LogicalAggregate<GroupPlan> aggregate = having.child();
+                Resolver resolver = new Resolver(aggregate);
+                resolver.resolve(having.getPredicates());
+                return createPlan(having, resolver);
+            })
+        );
+    }
+
+    static class Resolver {
+
+        private final List<NamedExpression> outputExpressions;
+        private final List<Expression> groupByExpressions;
+        private final Map<Expression, Slot> substitution = Maps.newHashMap();
+        private final List<NamedExpression> newOutputSlots = 
Lists.newArrayList();
+
+        Resolver(LogicalAggregate<? extends Plan> aggregate) {
+            outputExpressions = aggregate.getOutputExpressions();
+            groupByExpressions = aggregate.getGroupByExpressions();
+        }
+
+        public void resolve(Expression expression) {
+            Pair<Optional<Expression>, Boolean> result = lookUp(expression);
+            Optional<Expression> found = result.first;
+            boolean isFoundInOutputExpressions = result.second;
+
+            if (found.isPresent()) {
+                // If we found the equivalent slot or alias in the output 
expressions or group-by expressions,
+                // We should replace the expression in having clause with the 
one in aggregation.
+                if (found.get() instanceof NamedExpression) {
+                    substitution.put(expression, ((NamedExpression) 
found.get()).toSlot());
+                    if (!isFoundInOutputExpressions) {
+                        // If the equivalent expression wasn't found in the 
output expressions, we should
+                        // push it down to the aggregation.
+                        newOutputSlots.add(((NamedExpression) 
found.get()).toSlot());
+                    }
+                } else {
+                    // If the equivalent expression (neither slot nor alias) 
was found in group-by expressions (
+                    // E.g. group by (a + 1) having (a + 1)), we should 
generate an alias for it and
+                    // push it down to the aggregation.
+                    generateAliasForNewOutputSlots(expression);
+                }
+            } else {
+                // 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.");
+                } else if (expression instanceof AggregateFunction) {
+                    if 
(checkWhetherNestedAggregateFunctionsExist((AggregateFunction) expression)) {
+                        throw new AnalysisException("Aggregate functions in 
having clause can't be nested: "
+                                + expression.toSql() + ".");
+                    }
+                    generateAliasForNewOutputSlots(expression);
+                } else {
+                    // Try to resolve the children.
+                    for (Expression child : expression.children()) {
+                        resolve(child);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Look up the expression in aggregation.
+         * @param expression Expression in predicates of having clause.
+         * @return {@code Pair<Optional<Expression>, Boolean>}
+         *     first: the expression in aggregation which is equivalent to 
input expression.
+         *     second: whether the expression is found in output expressions 
of aggregation.
+         */
+        private Pair<Optional<Expression>, Boolean> lookUp(Expression 
expression) {
+            Optional<Expression> found = outputExpressions.stream()
+                    .filter(source -> isEquivalent(source, expression))
+                    .map(source -> (Expression) source)
+                    .findFirst();
+            if (found.isPresent()) {
+                return Pair.of(found, true);
+            }
+
+            found = groupByExpressions.stream().filter(source -> 
isEquivalent(source, expression)).findFirst();
+            return Pair.of(found, false);
+        }
+
+        /**
+         * Check whether the two expressions are equivalent.
+         * @param source The expression in aggregation.
+         * @param expression The expression used to compared to the one in 
aggregation.
+         * @return true if the expressions are equivalent.
+         */
+        private boolean isEquivalent(Expression source, Expression expression) 
{
+            if (source.equals(expression)) {
+                return true;
+            } else if (source instanceof Alias) {
+                Alias alias = (Alias) source;
+                return alias.toSlot().equals(expression) || 
alias.child().equals(expression);
+            }
+            return false;
+        }
+
+        private boolean 
checkWhetherNestedAggregateFunctionsExist(AggregateFunction function) {
+            return function.children().stream().anyMatch(child -> 
child.anyMatch(AggregateFunction.class::isInstance));
+        }
+
+        private void generateAliasForNewOutputSlots(Expression expression) {
+            Alias alias = new Alias(expression, expression.toSql());
+            newOutputSlots.add(alias);
+            substitution.put(expression, alias.toSlot());
+        }
+
+        public Map<Expression, Slot> getSubstitution() {
+            return substitution;
+        }
+
+        public List<NamedExpression> getNewOutputSlots() {
+            return newOutputSlots;
+        }
+    }
+
+    private Plan createPlan(LogicalHaving<LogicalAggregate<GroupPlan>> having, 
Resolver resolver) {
+        LogicalAggregate<GroupPlan> aggregate = having.child();
+        Expression newPredicates = ExpressionReplacer.INSTANCE
+                .visit(having.getPredicates(), resolver.getSubstitution());
+        List<NamedExpression> newOutputExpressions = Streams.concat(
+                aggregate.getOutputExpressions().stream(), 
resolver.getNewOutputSlots().stream()
+        ).collect(Collectors.toList());
+        LogicalFilter<LogicalAggregate<Plan>> filter = new 
LogicalFilter<>(newPredicates,
+                
aggregate.withGroupByAndOutput(aggregate.getGroupByExpressions(), 
newOutputExpressions));
+        return resolver.getNewOutputSlots().isEmpty()
+                ? filter
+                : new LogicalProject<>(
+                        
aggregate.getOutputExpressions().stream().map(NamedExpression::toSlot)
+                                .collect(Collectors.toList()),
+                        filter
+                );
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionReplacer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionReplacer.java
index 2caca9e25d..f3fd35d695 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionReplacer.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ExpressionReplacer.java
@@ -25,11 +25,11 @@ import java.util.Map;
  * replace expr nodes by substitutionMap
  */
 public class ExpressionReplacer
-        extends DefaultExpressionRewriter<Map<Expression, Expression>> {
+        extends DefaultExpressionRewriter<Map<? extends Expression, ? extends 
Expression>> {
     public static final ExpressionReplacer INSTANCE = new ExpressionReplacer();
 
     @Override
-    public Expression visit(Expression expr, Map<Expression, Expression> 
substitutionMap) {
+    public Expression visit(Expression expr, Map<? extends Expression, ? 
extends Expression> substitutionMap) {
         if (substitutionMap.containsKey(expr)) {
             return substitutionMap.get(expr);
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
index 7d24020095..68aae991db 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
@@ -38,6 +38,7 @@ public enum PlanType {
     LOGICAL_APPLY,
     LOGICAL_SELECT_HINT,
     LOGICAL_ASSERT_NUM_ROWS,
+    LOGICAL_HAVING,
     GROUP_PLAN,
 
     // physical plan
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
new file mode 100644
index 0000000000..4e49c5330b
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalHaving.java
@@ -0,0 +1,112 @@
+// 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.
+
+package org.apache.doris.nereids.trees.plans.logical;
+
+import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.LogicalProperties;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.algebra.Filter;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.nereids.util.Utils;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Logical Having plan
+ * @param <CHILD_TYPE> Types which inherit from {@link Plan}
+ */
+public class LogicalHaving<CHILD_TYPE extends Plan> extends 
LogicalUnary<CHILD_TYPE> implements Filter {
+
+    private final Expression predicates;
+
+    public LogicalHaving(Expression predicates, CHILD_TYPE child) {
+        this(predicates, Optional.empty(), Optional.empty(), child);
+    }
+
+    public LogicalHaving(Expression predicates, Optional<GroupExpression> 
groupExpression,
+            Optional<LogicalProperties> logicalProperties, CHILD_TYPE child) {
+        super(PlanType.LOGICAL_HAVING, groupExpression, logicalProperties, 
child);
+        this.predicates = Objects.requireNonNull(predicates, "predicates can 
not be null");
+    }
+
+    @Override
+    public Expression getPredicates() {
+        return predicates;
+    }
+
+    @Override
+    public Plan withChildren(List<Plan> children) {
+        Preconditions.checkArgument(children.size() == 1);
+        return new LogicalHaving<>(predicates, children.get(0));
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitLogicalHaving((LogicalHaving<Plan>) this, context);
+    }
+
+    @Override
+    public List<Expression> getExpressions() {
+        return ImmutableList.of(predicates);
+    }
+
+    @Override
+    public Plan withGroupExpression(Optional<GroupExpression> groupExpression) 
{
+        return new LogicalHaving<>(predicates, groupExpression, 
Optional.of(logicalProperties), child());
+    }
+
+    @Override
+    public Plan withLogicalProperties(Optional<LogicalProperties> 
logicalProperties) {
+        return new LogicalHaving<>(predicates, Optional.empty(), 
logicalProperties, child());
+    }
+
+    @Override
+    public List<Slot> computeOutput() {
+        return child().getOutput();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(predicates);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LogicalHaving) || getClass() != 
object.getClass()) {
+            return false;
+        }
+        LogicalHaving other = (LogicalHaving) object;
+        return predicates.equals(other.predicates);
+    }
+
+    @Override
+    public String toString() {
+        return Utils.toSqlString("LogicalHaving", "predicates", predicates);
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
index 497a6df19f..e50c81d608 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/PlanVisitor.java
@@ -26,6 +26,7 @@ import 
org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
 import org.apache.doris.nereids.trees.plans.logical.LogicalApply;
 import org.apache.doris.nereids.trees.plans.logical.LogicalAssertNumRows;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalHaving;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalLimit;
 import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
@@ -136,6 +137,10 @@ public abstract class PlanVisitor<R, C> {
         return visit(assertNumRows, context);
     }
 
+    public R visitLogicalHaving(LogicalHaving<Plan> having, C context) {
+        return visit(having, context);
+    }
+
     // *******************************
     // Physical plans
     // *******************************
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/HavingClauseTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/HavingClauseTest.java
new file mode 100644
index 0000000000..c2120ca019
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/HavingClauseTest.java
@@ -0,0 +1,386 @@
+// 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.
+
+package org.apache.doris.nereids.parser;
+
+import org.apache.doris.common.ExceptionChecker;
+import org.apache.doris.nereids.datasets.tpch.AnalyzeCheckTestBase;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Add;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.And;
+import org.apache.doris.nereids.trees.expressions.ExprId;
+import org.apache.doris.nereids.trees.expressions.GreaterThan;
+import org.apache.doris.nereids.trees.expressions.NamedExpressionUtil;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.Count;
+import org.apache.doris.nereids.trees.expressions.functions.Min;
+import org.apache.doris.nereids.trees.expressions.functions.Sum;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.FieldChecker;
+import org.apache.doris.nereids.util.PatternMatchSupported;
+import org.apache.doris.nereids.util.PlanChecker;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.Collectors;
+
+public class HavingClauseTest extends AnalyzeCheckTestBase implements 
PatternMatchSupported {
+
+    @Override
+    protected void runBeforeAll() throws Exception {
+        createDatabase("test_having");
+        connectContext.setDatabase("default_cluster:test_having");
+        createTables(
+                "CREATE TABLE t1 (\n"
+                        + "    pk INT,\n"
+                        + "    a1 INT,\n"
+                        + "    a2 INT\n"
+                        + ")\n"
+                        + "DUPLICATE KEY (pk)\n"
+                        + "DISTRIBUTED BY HASH (pk)\n"
+                        + "PROPERTIES(\n"
+                        + "    'replication_num' = '1'\n"
+                        + ");",
+                "CREATE TABLE t2 (\n"
+                        + "    pk INT,\n"
+                        + "    b1 INT,\n"
+                        + "    b2 INT\n"
+                        + ")\n"
+                        + "DUPLICATE KEY (pk)\n"
+                        + "DISTRIBUTED BY HASH (pk)\n"
+                        + "PROPERTIES(\n"
+                        + "    'replication_num' = '1'\n"
+                        + ");"
+        );
+    }
+
+    @Override
+    protected void runBeforeEach() throws Exception {
+        NamedExpressionUtil.clear();
+    }
+
+    @Test
+    public void testHavingGroupBySlot() throws Exception {
+        String sql = "SELECT a1 FROM t1 GROUP BY a1 HAVING a1 > 0";
+        SlotReference a1 = new SlotReference(
+                new ExprId(1), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(a1, new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1 as value FROM t1 GROUP BY a1 HAVING a1 > 0";
+        a1 = new SlotReference(
+                new ExprId(2), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        Alias value = new Alias(new ExprId(0), a1, "value");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(value)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(value.toSlot(), new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1 as value FROM t1 GROUP BY a1 HAVING value > 0";
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(value)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(value.toSlot(), new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT SUM(a2) FROM t1 GROUP BY a1 HAVING a1 > 0";
+        a1 = new SlotReference(
+                new ExprId(1), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        SlotReference a2 = new SlotReference(
+                new ExprId(2), "a2", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        Alias sumA2 = new Alias(new ExprId(3), new Sum(a2), "SUM(a2)");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalOlapScan()
+                            ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(sumA2, a1)))
+                        ).when(FieldChecker.check("predicates", new 
GreaterThan(a1, new IntegerLiteral(0))))
+                    ).when(FieldChecker.check("projects", 
Lists.newArrayList(sumA2.toSlot()))));
+        NamedExpressionUtil.clear();
+    }
+
+    @Test
+    public void testHavingAggregateFunction() throws Exception {
+        String sql = "SELECT a1 FROM t1 GROUP BY a1 HAVING SUM(a2) > 0";
+        SlotReference a1 = new SlotReference(
+                new ExprId(1), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        SlotReference a2 = new SlotReference(
+                new ExprId(2), "a2", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        Alias sumA2 = new Alias(new ExprId(3), new Sum(a2), "sum(a2)");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalOlapScan()
+                            ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, sumA2)))
+                        ).when(FieldChecker.check("predicates", new 
GreaterThan(sumA2.toSlot(), new IntegerLiteral(0))))
+                    ).when(FieldChecker.check("projects", 
Lists.newArrayList(a1.toSlot()))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1, SUM(a2) FROM t1 GROUP BY a1 HAVING SUM(a2) > 0";
+        sumA2 = new Alias(new ExprId(3), new Sum(a2), "SUM(a2)");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, sumA2)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(sumA2.toSlot(), new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1, SUM(a2) as value FROM t1 GROUP BY a1 HAVING SUM(a2) 
> 0";
+        a1 = new SlotReference(
+                new ExprId(2), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        a2 = new SlotReference(
+                new ExprId(3), "a2", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        Alias value = new Alias(new ExprId(0), new Sum(a2), "value");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, value)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(value.toSlot(), new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1, SUM(a2) as value FROM t1 GROUP BY a1 HAVING value > 
0";
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, value)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(value.toSlot(), new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1, SUM(a2) FROM t1 GROUP BY a1 HAVING MIN(pk) > 0";
+        a1 = new SlotReference(
+                new ExprId(1), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        a2 = new SlotReference(
+                new ExprId(2), "a2", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        sumA2 = new Alias(new ExprId(3), new Sum(a2), "SUM(a2)");
+        SlotReference pk = new SlotReference(
+                new ExprId(0), "pk", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        Alias minPK = new Alias(new ExprId(4), new Min(pk), "min(pk)");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalOlapScan()
+                            ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, sumA2, minPK)))
+                        ).when(FieldChecker.check("predicates", new 
GreaterThan(minPK.toSlot(), new IntegerLiteral(0))))
+                    ).when(FieldChecker.check("projects", 
Lists.newArrayList(a1.toSlot(), sumA2.toSlot()))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1, SUM(a1 + a2) FROM t1 GROUP BY a1 HAVING SUM(a1 + a2) 
> 0";
+        Alias sumA1A2 = new Alias(new ExprId(3), new Sum(new Add(a1, a2)), 
"SUM((a1 + a2))");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalFilter(
+                        logicalAggregate(
+                            logicalOlapScan()
+                        ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, sumA1A2)))
+                    ).when(FieldChecker.check("predicates", new 
GreaterThan(sumA1A2.toSlot(), new IntegerLiteral(0)))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1, SUM(a1 + a2) FROM t1 GROUP BY a1 HAVING SUM(a1 + a2 
+ 3) > 0";
+        Alias sumA1A23 = new Alias(new ExprId(4), new Sum(new Add(new Add(a1, 
a2), new IntegerLiteral(3))),
+                "sum(((a1 + a2) + 3))");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalOlapScan()
+                            ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, sumA1A2, sumA1A23)))
+                        ).when(FieldChecker.check("predicates", new 
GreaterThan(sumA1A23.toSlot(), new IntegerLiteral(0))))
+                    ).when(FieldChecker.check("projects", 
Lists.newArrayList(a1.toSlot(), sumA1A2.toSlot()))));
+        NamedExpressionUtil.clear();
+
+        sql = "SELECT a1 FROM t1 GROUP BY a1 HAVING COUNT(*) > 0";
+        Alias countStar = new Alias(new ExprId(3), new Count(), "count(*)");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalOlapScan()
+                            ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, countStar)))
+                        ).when(FieldChecker.check("predicates", new 
GreaterThan(countStar.toSlot(), new IntegerLiteral(0))))
+                    ).when(FieldChecker.check("projects", 
Lists.newArrayList(a1.toSlot()))));
+        NamedExpressionUtil.clear();
+    }
+
+    @Test
+    void testJoin() throws Exception {
+        String sql = "SELECT a1, sum(a2) FROM t1, t2 WHERE t1.pk = t2.pk GROUP 
BY a1 HAVING a1 > SUM(b1)";
+        SlotReference a1 = new SlotReference(
+                new ExprId(1), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        SlotReference a2 = new SlotReference(
+                new ExprId(2), "a2", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        SlotReference b1 = new SlotReference(
+                new ExprId(4), "b1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t2")
+        );
+        Alias sumA2 = new Alias(new ExprId(6), new Sum(a2), "sum(a2)");
+        Alias sumB1 = new Alias(new ExprId(7), new Sum(b1), "sum(b1)");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalFilter(
+                                    logicalJoin(
+                                        logicalOlapScan(),
+                                        logicalOlapScan()
+                                    )
+                                )
+                            ).when(FieldChecker.check("outputExpressions", 
Lists.newArrayList(a1, sumA2, sumB1)))
+                        ).when(FieldChecker.check("predicates", new 
GreaterThan(a1, sumB1.toSlot())))
+                    ).when(FieldChecker.check("projects", 
Lists.newArrayList(a1.toSlot(), sumA2.toSlot()))));
+        NamedExpressionUtil.clear();
+    }
+
+    @Test
+    void testInvalidHaving() {
+        ExceptionChecker.expectThrowsWithMsg(
+                AnalysisException.class,
+                "a2 in having clause should be grouped by.",
+                () -> PlanChecker.from(connectContext).analyze(
+                        "SELECT a1 FROM t1 GROUP BY a1 HAVING a2 > 0"
+                ));
+
+        ExceptionChecker.expectThrowsWithMsg(
+                AnalysisException.class,
+                "Aggregate functions in having clause can't be nested: sum((a1 
+ avg(a2))).",
+                () -> PlanChecker.from(connectContext).analyze(
+                        "SELECT a1 FROM t1 GROUP BY a1 HAVING SUM(a1 + 
AVG(a2)) > 0"
+                ));
+
+        ExceptionChecker.expectThrowsWithMsg(
+                AnalysisException.class,
+                "Aggregate functions in having clause can't be nested: 
sum(((a1 + a2) + avg(a2))).",
+                () -> PlanChecker.from(connectContext).analyze(
+                        "SELECT a1 FROM t1 GROUP BY a1 HAVING SUM(a1 + a2 + 
AVG(a2)) > 0"
+                ));
+    }
+
+    @Test
+    void testComplexQuery() throws Exception {
+        String sql = "SELECT t1.pk + 1, t1.pk + 1 + 1, t1.pk + 2, SUM(a1), 
COUNT(a1) + 1, SUM(a1 + a2), COUNT(a2) as v1\n"
+                + "FROM t1, t2 WHERE t1.pk = t2.pk GROUP BY t1.pk, t1.pk + 1\n"
+                + "HAVING t1.pk > 0 AND COUNT(a1) + 1 > 0 AND SUM(a1 + a2) + 1 
> 0 AND v1 + 1 > 0 AND v1 > 0";
+        SlotReference pk = new SlotReference(
+                new ExprId(1), "pk", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        SlotReference a1 = new SlotReference(
+                new ExprId(2), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        SlotReference a2 = new SlotReference(
+                new ExprId(3), "a1", IntegerType.INSTANCE, true,
+                ImmutableList.of("default_cluster:test_having", "t1")
+        );
+        Alias pk1 = new Alias(new ExprId(7), new Add(pk, 
IntegerLiteral.of(1)), "(pk + 1)");
+        Alias pk11 = new Alias(new ExprId(8), new Add(new Add(pk, 
IntegerLiteral.of(1)), IntegerLiteral.of(1)), "((pk + 1) + 1)");
+        Alias pk2 = new Alias(new ExprId(9), new Add(pk, 
IntegerLiteral.of(2)), "(pk + 2)");
+        Alias sumA1 = new Alias(new ExprId(10), new Sum(a1), "SUM(a1)");
+        Alias countA11 = new Alias(new ExprId(11), new Add(new Count(a1), 
IntegerLiteral.of(1)), "(COUNT(a1) + 1)");
+        Alias sumA1A2 = new Alias(new ExprId(12), new Sum(new Add(a1, a2)), 
"SUM((a1 + a2))");
+        Alias v1 = new Alias(new ExprId(0), new Count(a2), "v1");
+        PlanChecker.from(connectContext).analyze(sql)
+                .matchesFromRoot(
+                    logicalProject(
+                        logicalFilter(
+                            logicalAggregate(
+                                logicalFilter(
+                                    logicalJoin(
+                                        logicalOlapScan(),
+                                        logicalOlapScan()
+                                    )
+                                )
+                            ).when(FieldChecker.check("outputExpressions",
+                                    Lists.newArrayList(pk1, pk11, pk2, sumA1, 
countA11, sumA1A2, v1, pk)))
+                        ).when(FieldChecker.check("predicates",
+                                new And(
+                                        new And(
+                                                new And(
+                                                        new And(
+                                                                new 
GreaterThan(pk.toSlot(), IntegerLiteral.of(0)),
+                                                                new 
GreaterThan(countA11.toSlot(), IntegerLiteral.of(0))),
+                                                        new GreaterThan(new 
Add(sumA1A2.toSlot(), IntegerLiteral.of(1)), IntegerLiteral.of(0))),
+                                                new GreaterThan(new 
Add(v1.toSlot(), IntegerLiteral.of(1)), IntegerLiteral.of(0))
+                                        ),
+                                        new GreaterThan(v1.toSlot(), 
IntegerLiteral.of(0))
+                                )
+                        ))
+                    ).when(FieldChecker.check(
+                        "projects", Lists.newArrayList(
+                            pk1, pk11, pk2, sumA1, countA11, sumA1A2, 
v1).stream()
+                                    
.map(Alias::toSlot).collect(Collectors.toList()))
+                    ));
+        NamedExpressionUtil.clear();
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java 
b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
index b3e04aa35c..57b68ca4bc 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/TestWithFeService.java
@@ -44,6 +44,7 @@ import org.apache.doris.common.util.SqlParserUtils;
 import org.apache.doris.nereids.CascadesContext;
 import org.apache.doris.nereids.StatementContext;
 import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.expressions.NamedExpressionUtil;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.planner.Planner;
 import org.apache.doris.qe.ConnectContext;
@@ -115,6 +116,7 @@ public abstract class TestWithFeService {
         runAfterAll();
         Env.getCurrentEnv().clear();
         cleanDorisFeDir(runningDir);
+        NamedExpressionUtil.clear();
     }
 
     @BeforeEach
diff --git a/regression-test/data/nereids_syntax_p0/having.out 
b/regression-test/data/nereids_syntax_p0/having.out
new file mode 100644
index 0000000000..858c26cdc3
--- /dev/null
+++ b/regression-test/data/nereids_syntax_p0/having.out
@@ -0,0 +1,56 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !select --
+1
+2
+3
+
+-- !select --
+1
+2
+3
+
+-- !select --
+12
+18
+6
+
+-- !select --
+1
+2
+3
+
+-- !select --
+1      6
+2      12
+3      18
+
+-- !select --
+1      6
+2      12
+3      18
+
+-- !select --
+1      6
+2      12
+3      18
+
+-- !select --
+1      6
+2      12
+3      18
+
+-- !select --
+1      9
+2      18
+3      27
+
+-- !select --
+1      9
+2      18
+3      27
+
+-- !select --
+1
+2
+3
+
diff --git a/regression-test/suites/nereids_syntax_p0/having.groovy 
b/regression-test/suites/nereids_syntax_p0/having.groovy
new file mode 100644
index 0000000000..4f6abf2e61
--- /dev/null
+++ b/regression-test/suites/nereids_syntax_p0/having.groovy
@@ -0,0 +1,58 @@
+// 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_nereids_having") {
+
+    sql "SET enable_nereids_planner=true"
+
+    sql "DROP TABLE IF EXISTS t1"
+
+    sql """
+        CREATE TABLE t1 (
+            pk INT,
+            a1 INT,
+            a2 INT
+        ) DUPLICATE KEY (pk) DISTRIBUTED BY HASH (pk)
+        PROPERTIES ('replication_num' = '1')
+    """
+
+    sql """
+        INSERT INTO t1 VALUES
+            (1, 1, 1),
+            (1, 1, 2),
+            (1, 1, 3),
+            (2, 2, 2),
+            (2, 2, 4),
+            (2, 2, 6),
+            (3, 3, 3),
+            (3, 3, 6),
+            (3, 3, 9)
+    """
+
+
+    order_qt_select "SELECT a1 as value FROM t1 GROUP BY a1 HAVING a1 > 0";
+    order_qt_select "SELECT a1 as value FROM t1 GROUP BY a1 HAVING value > 0";
+    order_qt_select "SELECT SUM(a2) FROM t1 GROUP BY a1 HAVING a1 > 0";
+    order_qt_select "SELECT a1 FROM t1 GROUP BY a1 HAVING SUM(a2) > 0";
+    order_qt_select "SELECT a1, SUM(a2) FROM t1 GROUP BY a1 HAVING SUM(a2) > 
0";
+    order_qt_select "SELECT a1, SUM(a2) as value FROM t1 GROUP BY a1 HAVING 
SUM(a2) > 0";
+    order_qt_select "SELECT a1, SUM(a2) as value FROM t1 GROUP BY a1 HAVING 
value > 0";
+    order_qt_select "SELECT a1, SUM(a2) FROM t1 GROUP BY a1 HAVING MIN(pk) > 
0";
+    order_qt_select "SELECT a1, SUM(a1 + a2) FROM t1 GROUP BY a1 HAVING SUM(a1 
+ a2) > 0";
+    order_qt_select "SELECT a1, SUM(a1 + a2) FROM t1 GROUP BY a1 HAVING SUM(a1 
+ a2 + 3) > 0";
+    order_qt_select "SELECT a1 FROM t1 GROUP BY a1 HAVING COUNT(*) > 0";
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to