diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java index 5552b3d1d5c7e5..56016ad071a5ef 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java @@ -53,14 +53,14 @@ public class FunctionRegistry { // to record the global alias function and other udf. private static final String GLOBAL_FUNCTION = "__GLOBAL_FUNCTION__"; - private final Map> name2InternalBuiltinBuilders; + private final Map> name2BuiltinBuilders; private final Map>> name2UdfBuilders; public FunctionRegistry() { - name2InternalBuiltinBuilders = new ConcurrentHashMap<>(); + name2BuiltinBuilders = new ConcurrentHashMap<>(); name2UdfBuilders = new ConcurrentHashMap<>(); - registerBuiltinFunctions(name2InternalBuiltinBuilders); - afterRegisterBuiltinFunctions(name2InternalBuiltinBuilders); + registerBuiltinFunctions(name2BuiltinBuilders); + afterRegisterBuiltinFunctions(name2BuiltinBuilders); } // this function is used to test. @@ -78,12 +78,33 @@ public FunctionBuilder findFunctionBuilder(String name, Object argument) { } public Optional> tryGetBuiltinBuilders(String name) { - List builders = name2InternalBuiltinBuilders.get(name); - return name2InternalBuiltinBuilders.get(name) == null + List builders = name2BuiltinBuilders.get(name); + return name2BuiltinBuilders.get(name) == null ? Optional.empty() : Optional.of(ImmutableList.copyOf(builders)); } + public boolean isAggregateFunction(String dbName, String name) { + name = name.toLowerCase(); + Class aggClass = org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction.class; + if (StringUtils.isEmpty(dbName)) { + List functionBuilders = name2BuiltinBuilders.get(name); + for (FunctionBuilder functionBuilder : functionBuilders) { + if (aggClass.isAssignableFrom(functionBuilder.functionClass())) { + return true; + } + } + } + + List udfBuilders = findUdfBuilder(dbName, name); + for (FunctionBuilder udfBuilder : udfBuilders) { + if (aggClass.isAssignableFrom(udfBuilder.functionClass())) { + return true; + } + } + return false; + } + // currently we only find function by name and arity and args' types. public FunctionBuilder findFunctionBuilder(String dbName, String name, List arguments) { List functionBuilders = null; @@ -92,11 +113,11 @@ public FunctionBuilder findFunctionBuilder(String dbName, String name, List a if (StringUtils.isEmpty(dbName)) { // search internal function only if dbName is empty - functionBuilders = name2InternalBuiltinBuilders.get(name.toLowerCase()); + functionBuilders = name2BuiltinBuilders.get(name.toLowerCase()); if (CollectionUtils.isEmpty(functionBuilders) && AggCombinerFunctionBuilder.isAggStateCombinator(name)) { String nestedName = AggCombinerFunctionBuilder.getNestedName(name); String combinatorSuffix = AggCombinerFunctionBuilder.getCombinatorSuffix(name); - functionBuilders = name2InternalBuiltinBuilders.get(nestedName.toLowerCase()); + functionBuilders = name2BuiltinBuilders.get(nestedName.toLowerCase()); if (functionBuilders != null) { List candidateBuilders = Lists.newArrayListWithCapacity(functionBuilders.size()); for (FunctionBuilder functionBuilder : functionBuilders) { @@ -199,8 +220,8 @@ public void dropUdf(String dbName, String name, List argTypes) { } synchronized (name2UdfBuilders) { Map> builders = name2UdfBuilders.getOrDefault(dbName, ImmutableMap.of()); - builders.getOrDefault(name, Lists.newArrayList()).removeIf(builder -> ((UdfBuilder) builder).getArgTypes() - .equals(argTypes)); + builders.getOrDefault(name, Lists.newArrayList()) + .removeIf(builder -> ((UdfBuilder) builder).getArgTypes().equals(argTypes)); } } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java index a561507fb947a8..2017006870f110 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java @@ -19,11 +19,13 @@ import org.apache.doris.catalog.Env; import org.apache.doris.catalog.FunctionRegistry; +import org.apache.doris.common.Pair; import org.apache.doris.nereids.CascadesContext; import org.apache.doris.nereids.NereidsPlanner; import org.apache.doris.nereids.StatementContext; import org.apache.doris.nereids.analyzer.MappingSlot; import org.apache.doris.nereids.analyzer.Scope; +import org.apache.doris.nereids.analyzer.UnboundFunction; import org.apache.doris.nereids.analyzer.UnboundOneRowRelation; import org.apache.doris.nereids.analyzer.UnboundResultSink; import org.apache.doris.nereids.analyzer.UnboundSlot; @@ -351,12 +353,12 @@ private LogicalHaving bindHaving(MatchingContext> ctx) CascadesContext cascadesContext = ctx.cascadesContext; // bind slot by child.output first - Scope defaultScope = toScope(cascadesContext, childPlan.getOutput()); + Scope childOutput = toScope(cascadesContext, childPlan.getOutput()); // then bind slot by child.children.output - Supplier backupScope = Suppliers.memoize(() -> + Supplier childChildrenOutput = Suppliers.memoize(() -> toScope(cascadesContext, PlanUtils.fastGetChildrenOutputs(childPlan.children())) ); - return bindHavingByScopes(having, cascadesContext, defaultScope, backupScope); + return bindHavingByScopes(having, cascadesContext, childOutput, childChildrenOutput); } private LogicalHaving bindHavingAggregate( @@ -365,13 +367,114 @@ private LogicalHaving bindHavingAggregate( Aggregate aggregate = having.child(); CascadesContext cascadesContext = ctx.cascadesContext; - // having(aggregate) should bind slot by aggregate.child.output first - Scope defaultScope = toScope(cascadesContext, PlanUtils.fastGetChildrenOutputs(aggregate.children())); - // then bind slot by aggregate.output - Supplier backupScope = Suppliers.memoize(() -> - toScope(cascadesContext, aggregate.getOutput()) - ); - return bindHavingByScopes(ctx.root, ctx.cascadesContext, defaultScope, backupScope); + // keep same behavior as mysql + Supplier bindByAggChild = Suppliers.memoize(() -> { + Scope aggChildOutputScope + = toScope(cascadesContext, PlanUtils.fastGetChildrenOutputs(aggregate.children())); + return (analyzer, unboundSlot) -> analyzer.bindSlotByScope(unboundSlot, aggChildOutputScope); + }); + + Scope aggOutputScope = toScope(cascadesContext, aggregate.getOutput()); + Supplier bindByGroupByThenAggOutputThenAggChild = Suppliers.memoize(() -> { + List groupByExprs = aggregate.getGroupByExpressions(); + ImmutableList.Builder groupBySlots + = ImmutableList.builderWithExpectedSize(groupByExprs.size()); + for (Expression groupBy : groupByExprs) { + if (groupBy instanceof Slot) { + groupBySlots.add((Slot) groupBy); + } + } + Scope groupBySlotsScope = toScope(cascadesContext, groupBySlots.build()); + + Supplier> separateAggOutputScopes = Suppliers.memoize(() -> { + ImmutableList.Builder groupByOutputs = ImmutableList.builderWithExpectedSize( + aggregate.getOutputExpressions().size()); + ImmutableList.Builder aggFunOutputs = ImmutableList.builderWithExpectedSize( + aggregate.getOutputExpressions().size()); + for (NamedExpression outputExpression : aggregate.getOutputExpressions()) { + if (outputExpression.anyMatch(AggregateFunction.class::isInstance)) { + aggFunOutputs.add(outputExpression.toSlot()); + } else { + groupByOutputs.add(outputExpression.toSlot()); + } + } + Scope nonAggFunSlotsScope = toScope(cascadesContext, groupByOutputs.build()); + Scope aggFuncSlotsScope = toScope(cascadesContext, aggFunOutputs.build()); + return Pair.of(nonAggFunSlotsScope, aggFuncSlotsScope); + }); + + return (analyzer, unboundSlot) -> { + List boundInGroupBy = analyzer.bindSlotByScope(unboundSlot, groupBySlotsScope); + if (boundInGroupBy.size() == 1) { + return boundInGroupBy; + } + + Pair separateAggOutputScope = separateAggOutputScopes.get(); + List boundInNonAggFuncs = analyzer.bindSlotByScope(unboundSlot, separateAggOutputScope.first); + if (boundInNonAggFuncs.size() == 1) { + return boundInNonAggFuncs; + } + + List boundInAggFuncs = analyzer.bindSlotByScope(unboundSlot, separateAggOutputScope.second); + if (boundInAggFuncs.size() == 1) { + return boundInAggFuncs; + } + return analyzer.bindSlotByScope(unboundSlot, aggOutputScope); + }; + }); + + FunctionRegistry functionRegistry = cascadesContext.getConnectContext().getEnv().getFunctionRegistry(); + ExpressionAnalyzer havingAnalyzer = new ExpressionAnalyzer(having, aggOutputScope, cascadesContext, + false, true) { + private boolean currentIsInAggregateFunction; + + @Override + public Expression visitAggregateFunction(AggregateFunction aggregateFunction, + ExpressionRewriteContext context) { + if (!currentIsInAggregateFunction) { + currentIsInAggregateFunction = true; + try { + return super.visitAggregateFunction(aggregateFunction, context); + } finally { + currentIsInAggregateFunction = false; + } + } else { + return super.visitAggregateFunction(aggregateFunction, context); + } + } + + @Override + public Expression visitUnboundFunction(UnboundFunction unboundFunction, ExpressionRewriteContext context) { + if (!currentIsInAggregateFunction && isAggregateFunction(unboundFunction, functionRegistry)) { + currentIsInAggregateFunction = true; + try { + return super.visitUnboundFunction(unboundFunction, context); + } finally { + currentIsInAggregateFunction = false; + } + } else { + return super.visitUnboundFunction(unboundFunction, context); + } + } + + @Override + protected List bindSlotByThisScope(UnboundSlot unboundSlot) { + if (currentIsInAggregateFunction) { + return bindByAggChild.get().bindSlot(this, unboundSlot); + } else { + return bindByGroupByThenAggOutputThenAggChild.get().bindSlot(this, unboundSlot); + } + } + }; + + Set havingExprs = having.getConjuncts(); + ImmutableSet.Builder analyzedHaving = ImmutableSet.builderWithExpectedSize(havingExprs.size()); + ExpressionRewriteContext rewriteContext = new ExpressionRewriteContext(cascadesContext); + for (Expression expression : havingExprs) { + analyzedHaving.add(havingAnalyzer.analyze(expression, rewriteContext)); + } + + return new LogicalHaving<>(analyzedHaving.build(), having.child()); } private LogicalHaving bindHavingByScopes( @@ -764,6 +867,11 @@ private void checkIfOutputAliasNameDuplicatedForGroupBy(Collection e } } + private boolean isAggregateFunction(UnboundFunction unboundFunction, FunctionRegistry functionRegistry) { + return functionRegistry.isAggregateFunction( + unboundFunction.getDbName(), unboundFunction.getName()); + } + private E checkBoundExceptLambda(E expression, Plan plan) { if (expression instanceof Lambda) { return expression; @@ -797,6 +905,12 @@ private SimpleExprAnalyzer buildSimpleExprAnalyzer( boolean enableExactMatch, boolean bindSlotInOuterScope) { List childrenOutputs = PlanUtils.fastGetChildrenOutputs(children); Scope scope = toScope(cascadesContext, childrenOutputs); + return buildSimpleExprAnalyzer(currentPlan, cascadesContext, scope, enableExactMatch, bindSlotInOuterScope); + } + + private SimpleExprAnalyzer buildSimpleExprAnalyzer( + Plan currentPlan, CascadesContext cascadesContext, Scope scope, + boolean enableExactMatch, boolean bindSlotInOuterScope) { ExpressionRewriteContext rewriteContext = new ExpressionRewriteContext(cascadesContext); ExpressionAnalyzer expressionAnalyzer = new ExpressionAnalyzer(currentPlan, scope, cascadesContext, enableExactMatch, bindSlotInOuterScope); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/AggCombinerFunctionBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/AggCombinerFunctionBuilder.java index 3c514475eedde2..4ef642890cae15 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/AggCombinerFunctionBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/AggCombinerFunctionBuilder.java @@ -56,6 +56,11 @@ public AggCombinerFunctionBuilder(String combinatorSuffix, FunctionBuilder neste this.nestedBuilder = Objects.requireNonNull(nestedBuilder, "nestedBuilder can not be null"); } + @Override + public Class functionClass() { + return nestedBuilder.functionClass(); + } + @Override public boolean canApply(List arguments) { if (combinatorSuffix.equals(STATE) || combinatorSuffix.equals(FOREACH)) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java index 74c4a918cf0aa0..e2dab713332fd6 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/BuiltinFunctionBuilder.java @@ -42,13 +42,21 @@ public class BuiltinFunctionBuilder extends FunctionBuilder { // Concrete BoundFunction's constructor private final Constructor builderMethod; + private final Class functionClass; - public BuiltinFunctionBuilder(Constructor builderMethod) { + public BuiltinFunctionBuilder( + Class functionClass, Constructor builderMethod) { + this.functionClass = Objects.requireNonNull(functionClass, "functionClass can not be null"); this.builderMethod = Objects.requireNonNull(builderMethod, "builderMethod can not be null"); this.arity = builderMethod.getParameterCount(); this.isVariableLength = arity > 0 && builderMethod.getParameterTypes()[arity - 1].isArray(); } + @Override + public Class functionClass() { + return functionClass; + } + @Override public boolean canApply(List arguments) { if (isVariableLength && arity > arguments.size() + 1) { @@ -133,7 +141,7 @@ public static List resolve(Class funct + functionClass.getSimpleName()); return Arrays.stream(functionClass.getConstructors()) .filter(constructor -> Modifier.isPublic(constructor.getModifiers())) - .map(constructor -> new BuiltinFunctionBuilder((Constructor) constructor)) + .map(constructor -> new BuiltinFunctionBuilder(functionClass, (Constructor) constructor)) .collect(ImmutableList.toImmutableList()); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java index 0b3a50a239b8e0..d1e69d3e307d6f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/FunctionBuilder.java @@ -27,6 +27,8 @@ * This class used to build BoundFunction(Builtin or Combinator) by a list of Expressions. */ public abstract class FunctionBuilder { + public abstract Class functionClass(); + /** check whether arguments can apply to the constructor */ public abstract boolean canApply(List arguments); diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java index 197cb8b396df5d..733bd5fcae1164 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdfBuilder.java @@ -50,6 +50,11 @@ public List getArgTypes() { return aliasUdf.getArgTypes(); } + @Override + public Class functionClass() { + return AliasUdf.class; + } + @Override public boolean canApply(List arguments) { if (arguments.size() != aliasUdf.arity()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java index d0c0b067e944d2..89073b4eb77e4d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java @@ -49,6 +49,11 @@ public List getArgTypes() { .collect(Collectors.toList())).get(); } + @Override + public Class functionClass() { + return JavaUdaf.class; + } + @Override public boolean canApply(List arguments) { if ((isVarArgs && arity > arguments.size() + 1) || (!isVarArgs && arguments.size() != arity)) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdfBuilder.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdfBuilder.java index efbcbf9f4835f3..a78f8cfe4b46ae 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdfBuilder.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdfBuilder.java @@ -51,6 +51,11 @@ public List getArgTypes() { .collect(Collectors.toList())).get(); } + @Override + public Class functionClass() { + return JavaUdf.class; + } + @Override public boolean canApply(List arguments) { if ((isVarArgs && arity > arguments.size() + 1) || (!isVarArgs && arguments.size() != arity)) { diff --git a/regression-test/data/nereids_syntax_p0/bind_priority.out b/regression-test/data/nereids_syntax_p0/bind_priority.out index 7cd1e3ea5696e4..fec4313d09eed1 100644 --- a/regression-test/data/nereids_syntax_p0/bind_priority.out +++ b/regression-test/data/nereids_syntax_p0/bind_priority.out @@ -36,3 +36,34 @@ all 2 4 5 6 6 +-- !having_bind_child -- +1 10 + +-- !having_bind_child2 -- +2 10 + +-- !having_bind_child3 -- +2 10 + +-- !having_bind_project -- +2 10 + +-- !having_bind_project2 -- + +-- !having_bind_project3 -- + +-- !having_bind_project4 -- +2 11 + +-- !having_bind_child4 -- +2 11 + +-- !having_bind_child5 -- +2 11 + +-- !having_bind_agg_fun -- + +-- !having_bind_agg_fun -- +2 4 +3 3 + diff --git a/regression-test/suites/nereids_syntax_p0/bind_priority.groovy b/regression-test/suites/nereids_syntax_p0/bind_priority.groovy index 072587ed28eee4..4e1740061b6375 100644 --- a/regression-test/suites/nereids_syntax_p0/bind_priority.groovy +++ b/regression-test/suites/nereids_syntax_p0/bind_priority.groovy @@ -31,7 +31,7 @@ suite("bind_priority") { sql """ insert into bind_priority_tbl values(1, 2),(3, 4) """ - + sql "SET enable_nereids_planner=true" sql "SET enable_fallback_to_original_planner=false" @@ -100,17 +100,17 @@ suite("bind_priority") { ); """ sql "insert into bind_priority_tbl2 values(3,5),(2, 6),(1,4);" - + qt_bind_order_to_project_alias """ select bind_priority_tbl.b b, bind_priority_tbl2.b - from bind_priority_tbl join bind_priority_tbl2 on bind_priority_tbl.a=bind_priority_tbl2.a + from bind_priority_tbl join bind_priority_tbl2 on bind_priority_tbl.a=bind_priority_tbl2.a order by b; """ qt_bind_order_to_project_alias """ select bind_priority_tbl.b, bind_priority_tbl2.b b - from bind_priority_tbl join bind_priority_tbl2 on bind_priority_tbl.a=bind_priority_tbl2.a + from bind_priority_tbl join bind_priority_tbl2 on bind_priority_tbl.a=bind_priority_tbl2.a order by b; """ @@ -148,11 +148,144 @@ suite("bind_priority") { ) a ), tb2 as ( - select * from tb1 + select * from tb1 ) select * from tb2 order by id; """) result([[1], [2], [3]]) } + + def testBindHaving = { + sql "drop table if exists test_bind_having_slots" + + sql """create table test_bind_having_slots + (id int, age int) + distributed by hash(id) + properties('replication_num'='1'); + """ + sql "insert into test_bind_having_slots values(1, 10), (2, 20), (3, 30);" + + order_qt_having_bind_child """ + select id, sum(age) + from test_bind_having_slots s + group by id + having id = 1; -- bind id from group by + """ + + order_qt_having_bind_child2 """ + select id + 1 as id, sum(age) + from test_bind_having_slots s + group by id + having id = 1; -- bind id from group by + """ + + order_qt_having_bind_child3 """ + select id + 1 as id, sum(age) + from test_bind_having_slots s + group by id + having id + 1 = 2; -- bind id from group by + """ + + order_qt_having_bind_project """ + select id + 1 as id, sum(age) + from test_bind_having_slots s + group by id + 1 + having id = 2; -- bind id from project + """ + + order_qt_having_bind_project2 """ + select id + 1 as id, sum(age) + from test_bind_having_slots s + group by id + 1 + having id + 1 = 2; -- bind id from project + """ + + order_qt_having_bind_project3 """ + select id + 1 as id, sum(age + 1) as age + from test_bind_having_slots s + group by id + having age = 10; -- bind age from project + """ + + order_qt_having_bind_project4 """ + select id + 1 as id, sum(age + 1) as age + from test_bind_having_slots s + group by id + having age = 11; -- bind age from project + """ + + order_qt_having_bind_child4 """ + select id + 1 as id, sum(age + 1) as age + from test_bind_having_slots s + group by id + having sum(age) = 10; -- bind age from s + """ + + order_qt_having_bind_child5 """ + select id + 1 as id, sum(age + 1) as age + from test_bind_having_slots s + group by id + having sum(age + 1) = 11 -- bind age from s + """ + + + + + sql "drop table if exists test_bind_having_slots2" + sql """create table test_bind_having_slots2 + (id int) + distributed by hash(id) + properties('replication_num'='1'); + """ + sql "insert into test_bind_having_slots2 values(1), (2), (3), (2);" + + order_qt_having_bind_agg_fun """ + select id, abs(sum(id)) as id + from test_bind_having_slots2 + group by id + having sum(id) + id >= 7 + """ + + order_qt_having_bind_agg_fun """ + select id, abs(sum(id)) as id + from test_bind_having_slots2 + group by id + having sum(id) + id >= 6 + """ + + + + + + sql "drop table if exists test_bind_having_slots3" + + sql """CREATE TABLE `test_bind_having_slots3`(pk int, pk2 int) + DUPLICATE KEY(`pk`) + DISTRIBUTED BY HASH(`pk`) BUCKETS 10 + properties('replication_num'='1'); + """ + sql "insert into test_bind_having_slots3 values(1, 1), (2, 2), (2, 2), (3, 3), (3, 3), (3, 3);" + + order_qt_having_bind_group_by """ + SELECT pk + 6 as ps, COUNT(pk ) * 3 as pk + FROM test_bind_having_slots3 tbl_alias1 + GROUP by pk + HAVING pk = 1 + """ + + order_qt_having_bind_group_by """ + SELECT pk + 6 as pk, COUNT(pk ) * 3 as pk + FROM test_bind_having_slots3 tbl_alias1 + GROUP by pk + 6 + HAVING pk = 7 + """ + + order_qt_having_bind_group_by """ + SELECT pk + 6, COUNT(pk ) * 3 as pk + FROM test_bind_having_slots3 tbl_alias1 + GROUP by pk + 6 + HAVING pk = 3 + """ + }() }