Code review comment: looks like sql "SELECT id, v1-2 as v, sum(v2) v2 FROM test_having_alias_tb GROUP BY id,v having(v2>1);" still report the same error. Please double check if it's expected behavior of this pr

Code before review:
private void analyzeAggregation(Analyzer analyzer) throws AnalysisException {
if (havingClause != null) {
Expr ambiguousAlias = getFirstAmbiguousAlias(havingClause);
if (ambiguousAlias != null) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_NON_UNIQ_ERROR, ambiguousAlias.toColumnLabel());
}
/*
* The having clause need to be substitute by aliasSMap.
* And it is analyzed after substitute.
* For example:
* Query: select k1 a, sum(k2) b from table group by k1 having a > 1;
* Having clause: a > 1
* aliasSMap: <a, table.k1> <b, sum(table.k2)>
* After substitute: a > 1 changed to table.k1 > 1
* Analyzer: check column and other subquery in having clause
* having predicate: table.k1 > 1
*/
/*
* TODO(ml): support substitute outer column in correlated subquery
* For example: select k1 key, sum(k1) sum_k1 from table a group by k1
*              having k1 >
*                     (select min(k1) from table b where a.key=b.k2);
* TODO: the a.key should be replaced by a.k1 instead of unknown column 'key' in 'a'
*/
/* according to mysql (https:
* "For GROUP BY or HAVING clauses, it searches the FROM clause before searching in the
* select_expr values. (For GROUP BY and HAVING, this differs from the pre-MySQL 5.0 behavior
* that used the same rules as for ORDER BY.)"
* case1: having clause use column name table.v1, because it searches the FROM clause firstly
*     select id, sum(v1) v1 from table group by id,v1 having(v1>1);
* case2: having clause used in aggregate functions, such as sum(v2) here
*     select id, sum(v1) v1, sum(v2) v2 from table group by id,v1 having(v1>1 AND sum(v2)>1);
* case3: having clause use alias name v, because table do not have column name v
*     select id, floor(v1) v, sum(v2) v2 from table group by id,v having(v>1 AND v2>1);
* case4: having clause use alias name vsum, because table do not have column name vsum
*     select id, floor(v1) v, sum(v2) vsum from table group by id,v having(v>1 AND vsum>1);
*/
if (groupByClause != null) {
ExprSubstitutionMap excludeAliasSMap = aliasSMap.clone();
List<Expr> havingSlots = Lists.newArrayList();
havingClause.collect(SlotRef.class, havingSlots);
for (Expr expr : havingSlots) {
if (excludeAliasSMap.get(expr) == null) {
continue;
}
try {
expr.clone().analyze(analyzer);
excludeAliasSMap.removeByLhsExpr(expr);
} catch (AnalysisException ex) {
}
}
havingClauseAfterAnaylzed = havingClause.substitute(excludeAliasSMap, analyzer, false);
} else {
havingClauseAfterAnaylzed = havingClause.substitute(aliasSMap, analyzer, false);
}
havingClauseAfterAnaylzed = rewriteQueryExprByMvColumnExpr(havingClauseAfterAnaylzed, analyzer);
havingClauseAfterAnaylzed.checkReturnsBool("HAVING clause", true);
if (groupingInfo != null) {
groupingInfo.substituteGroupingFn(Arrays.asList(havingClauseAfterAnaylzed), analyzer);
}
Expr analyticExpr = havingClauseAfterAnaylzed.findFirstOf(AnalyticExpr.class);
if (analyticExpr != null) {
throw new AnalysisException(
"HAVING clause must not contain analytic expressions: "
+ analyticExpr.toSql());
}
if (isContainInBitmap(havingClauseAfterAnaylzed)) {
throw new AnalysisException(
"HAVING clause dose not support in bitmap syntax: " + havingClauseAfterAnaylzed.toSql());
}
}
if (groupByClause == null && !selectList.isDistinct()
&& !TreeNode.contains(resultExprs, Expr.isAggregatePredicate())
&& (havingClauseAfterAnaylzed == null || !havingClauseAfterAnaylzed.contains(
Expr.isAggregatePredicate()))
&& (sortInfo == null || !TreeNode.contains(sortInfo.getOrderingExprs(),
Expr.isAggregatePredicate()))) {
if (havingClauseAfterAnaylzed != null) {
if (havingClauseAfterAnaylzed.contains(Subquery.class)) {
throw new AnalysisException("Only constant expr could be supported in having clause "
+ "when no aggregation in stmt");
}
analyzer.registerConjuncts(havingClauseAfterAnaylzed, true);
}
return;
}
if (fromClause.size() == 0) {
throw new AnalysisException("Aggregation without a FROM clause is not allowed");
}
if (selectList.isDistinct() && groupByClause == null) {
List<Expr> aggregateExpr = Lists.newArrayList();
TreeNode.collect(resultExprs, Expr.isAggregatePredicate(), aggregateExpr);
if (aggregateExpr.size() == resultExprs.size()) {
selectList.setIsDistinct(false);
}
}
if (selectList.isDistinct()
&& (groupByClause != null
|| TreeNode.contains(resultExprs, Expr.isAggregatePredicate())
|| (havingClauseAfterAnaylzed != null && havingClauseAfterAnaylzed.contains(
Expr.isAggregatePredicate())))) {
throw new AnalysisException("cannot combine SELECT DISTINCT with aggregate functions or GROUP BY");
}
if (groupByClause != null || TreeNode.contains(resultExprs, Expr.isAggregatePredicate())) {
for (SelectListItem item : selectList.getItems()) {
if (item.isStar()) {
throw new AnalysisException(
"cannot combine '*' in select list with GROUP BY: " + item.toSql());
}
}
}
ArrayList<FunctionCallExpr> aggExprs = Lists.newArrayList();
TreeNode.collect(resultExprs, Expr.isAggregatePredicate(), aggExprs);
if (havingClauseAfterAnaylzed != null) {
havingClauseAfterAnaylzed.collect(Expr.isAggregatePredicate(), aggExprs);
}
if (sortInfo != null) {
TreeNode.collect(sortInfo.getOrderingExprs(), Expr.isAggregatePredicate(), aggExprs);
}
ExprSubstitutionMap countAllMap = createCountAllMap(aggExprs, analyzer);
final ExprSubstitutionMap multiCountOrSumDistinctMap =
createSumOrCountMultiDistinctSMap(aggExprs, analyzer);
countAllMap = ExprSubstitutionMap.compose(multiCountOrSumDistinctMap, countAllMap, analyzer);
List<Expr> substitutedAggs =
Expr.substituteList(aggExprs, countAllMap, analyzer, false);
resultExprs = Expr.substituteList(resultExprs, countAllMap, analyzer, false);
aggExprs.clear();
TreeNode.collect(substitutedAggs, Expr.isAggregatePredicate(), aggExprs);
List<TupleId> groupingByTupleIds = new ArrayList<>();
if (groupByClause != null) {
groupByClause.genGroupingExprs();
ArrayList<Expr> groupingExprs = groupByClause.getGroupingExprs();
if (groupingInfo != null) {
groupingInfo.buildRepeat(groupingExprs, groupByClause.getGroupingSetList());
}
substituteOrdinalsAliases(groupingExprs, "GROUP BY", analyzer, false);
if (!groupByClause.isGroupByExtension() && !groupingExprs.isEmpty()) {
ArrayList<Expr> tempExprs = new ArrayList<>(groupingExprs);
groupingExprs.removeIf(Expr::isConstant);
if (groupingExprs.isEmpty() && aggExprs.isEmpty()) {
groupingExprs.add(tempExprs.get(0));
}
}
for (int i = 0; i < groupingExprs.size(); i++) {
groupingExprs.set(i, rewriteQueryExprByMvColumnExpr(groupingExprs.get(i), analyzer));
}
if (groupingInfo != null) {
groupingInfo.genOutputTupleDescAndSMap(analyzer, groupingExprs, aggExprs);
groupingByTupleIds.add(groupingInfo.getOutputTupleDesc().getId());
}
groupByClause.analyze(analyzer);
createAggInfo(groupingExprs, aggExprs, analyzer);
} else {
createAggInfo(new ArrayList<>(), aggExprs, analyzer);
}
if (aggInfo == null) {
return;
}
AggregateInfo finalAggInfo =
aggInfo.getSecondPhaseDistinctAggInfo() != null
? aggInfo.getSecondPhaseDistinctAggInfo()
: aggInfo;
groupingByTupleIds.add(finalAggInfo.getOutputTupleId());
ExprSubstitutionMap combinedSmap = ExprSubstitutionMap.compose(
countAllMap, finalAggInfo.getOutputSmap(), analyzer);
if (LOG.isDebugEnabled()) {
LOG.debug("combined smap: " + combinedSmap.debugString());
LOG.debug("desctbl: " + analyzer.getDescTbl().debugString());
LOG.debug("resultexprs: " + Expr.debugString(resultExprs));
}
if (havingClauseAfterAnaylzed != null) {
List<Subquery> subqueryInHaving = Lists.newArrayList();
havingClauseAfterAnaylzed.collect(Subquery.class, subqueryInHaving);
for (Subquery subquery : subqueryInHaving) {
if (subquery.isCorrelatedPredicate(getTableRefIds())) {
throw new AnalysisException("The correlated having clause is not supported");
}
}
}
/*
* All of columns of result and having clause are replaced by new slot ref
* which is bound by top tuple of agg info.
* For example:
* ResultExprs: SlotRef(k1), FunctionCall(sum(SlotRef(k2)))
* Having predicate: FunctionCall(sum(SlotRef(k2))) > subquery
* CombinedSMap: <SlotRef(k1) tuple 0, SlotRef(k1) of tuple 3>,
*               <FunctionCall(SlotRef(k2)) tuple 0, SlotRef(sum(k2)) of tuple 3>
*
* After rewritten:
* ResultExprs: SlotRef(k1) of tuple 3, SlotRef(sum(k2)) of tuple 3
* Having predicate: SlotRef(sum(k2)) of tuple 3 > subquery
*/
resultExprs = Expr.substituteList(resultExprs, combinedSmap, analyzer, false);
if (LOG.isDebugEnabled()) {
LOG.debug("post-agg selectListExprs: " + Expr.debugString(resultExprs));
}
if (havingClauseAfterAnaylzed != null) {
havingPred = havingClauseAfterAnaylzed.substitute(combinedSmap, analyzer, false);
analyzer.registerConjuncts(havingPred, true, finalAggInfo.getOutputTupleId().asList());
if (LOG.isDebugEnabled()) {
LOG.debug("post-agg havingPred: " + havingPred.debugString());
}
}
if (sortInfo != null) {
sortInfo.substituteOrderingExprs(combinedSmap, analyzer);
if (LOG.isDebugEnabled()) {
LOG.debug("post-agg orderingExprs: "
+ Expr.debugString(sortInfo.getOrderingExprs()));
}
}
for (int i = 0; i < selectList.getItems().size(); ++i) {
if (!resultExprs.get(i).isBoundByTupleIds(groupingByTupleIds)) {
throw new AnalysisException(
"select list expression not produced by aggregation output " + "(missing from "
+ "GROUP BY clause?): " + selectList.getItems().get(i).getExpr().toSql());
}
}
if (orderByElements != null) {
for (int i = 0; i < orderByElements.size(); ++i) {
if (!sortInfo.getOrderingExprs().get(i).isBoundByTupleIds(groupingByTupleIds)) {
throw new AnalysisException(
"ORDER BY expression not produced by aggregation output " + "(missing from "
+ "GROUP BY clause?): " + orderByElements.get(i).getExpr().toSql());
}
if (sortInfo.getOrderingExprs().get(i).type.isObjectStored()) {
throw new AnalysisException("ORDER BY expression could not contain object-stored columnx.");
}
}
}
if (havingPred != null) {
if (!havingPred.isBoundByTupleIds(groupingByTupleIds)) {
throw new AnalysisException(
"HAVING clause not produced by aggregation output " + "(missing from GROUP BY "
+ "clause?): " + havingClause.toSql());
}
}
}

Line under review: if (excludeAliasSMap.get(expr) == null) {

Code after addressing the review:
private void analyzeAggregation(Analyzer analyzer) throws AnalysisException {
if (havingClause != null) {
Expr ambiguousAlias = getFirstAmbiguousAlias(havingClause);
if (ambiguousAlias != null) {
ErrorReport.reportAnalysisException(ErrorCode.ERR_NON_UNIQ_ERROR, ambiguousAlias.toColumnLabel());
}
/*
* The having clause need to be substitute by aliasSMap.
* And it is analyzed after substitute.
* For example:
* Query: select k1 a, sum(k2) b from table group by k1 having a > 1;
* Having clause: a > 1
* aliasSMap: <a, table.k1> <b, sum(table.k2)>
* After substitute: a > 1 changed to table.k1 > 1
* Analyzer: check column and other subquery in having clause
* having predicate: table.k1 > 1
*/
/*
* TODO(ml): support substitute outer column in correlated subquery
* For example: select k1 key, sum(k1) sum_k1 from table a group by k1
*              having k1 >
*                     (select min(k1) from table b where a.key=b.k2);
* TODO: the a.key should be replaced by a.k1 instead of unknown column 'key' in 'a'
*/
/* according to mysql (https:
* "For GROUP BY or HAVING clauses, it searches the FROM clause before searching in the
* select_expr values. (For GROUP BY and HAVING, this differs from the pre-MySQL 5.0 behavior
* that used the same rules as for ORDER BY.)"
* case1: having clause use column name table.v1, because it searches the FROM clause firstly
*     select id, sum(v1) v1 from table group by id,v1 having(v1>1);
* case2: having clause used in aggregate functions, such as sum(v2) here
*     select id, sum(v1) v1, sum(v2) v2 from table group by id,v1 having(v1>1 AND sum(v2)>1);
* case3: having clause use alias name v, because table do not have column name v
*     select id, floor(v1) v, sum(v2) v2 from table group by id,v having(v>1 AND v2>1);
* case4: having clause use alias name vsum, because table do not have column name vsum
*     select id, floor(v1) v, sum(v2) vsum from table group by id,v having(v>1 AND vsum>1);
*/
if (groupByClause != null) {
ExprSubstitutionMap excludeAliasSMap = aliasSMap.clone();
List<Expr> havingSlots = Lists.newArrayList();
havingClause.collect(SlotRef.class, havingSlots);
for (Expr expr : havingSlots) {
if (excludeAliasSMap.get(expr) == null) {
continue;
}
try {
expr.clone().analyze(analyzer);
excludeAliasSMap.removeByLhsExpr(expr);
} catch (AnalysisException ex) {
}
}
havingClauseAfterAnaylzed = havingClause.substitute(excludeAliasSMap, analyzer, false);
} else {
havingClauseAfterAnaylzed = havingClause.substitute(aliasSMap, analyzer, false);
}
havingClauseAfterAnaylzed = rewriteQueryExprByMvColumnExpr(havingClauseAfterAnaylzed, analyzer);
havingClauseAfterAnaylzed.checkReturnsBool("HAVING clause", true);
if (groupingInfo != null) {
groupingInfo.substituteGroupingFn(Arrays.asList(havingClauseAfterAnaylzed), analyzer);
}
Expr analyticExpr = havingClauseAfterAnaylzed.findFirstOf(AnalyticExpr.class);
if (analyticExpr != null) {
throw new AnalysisException(
"HAVING clause must not contain analytic expressions: "
+ analyticExpr.toSql());
}
if (isContainInBitmap(havingClauseAfterAnaylzed)) {
throw new AnalysisException(
"HAVING clause dose not support in bitmap syntax: " + havingClauseAfterAnaylzed.toSql());
}
}
if (groupByClause == null && !selectList.isDistinct()
&& !TreeNode.contains(resultExprs, Expr.isAggregatePredicate())
&& (havingClauseAfterAnaylzed == null || !havingClauseAfterAnaylzed.contains(
Expr.isAggregatePredicate()))
&& (sortInfo == null || !TreeNode.contains(sortInfo.getOrderingExprs(),
Expr.isAggregatePredicate()))) {
if (havingClauseAfterAnaylzed != null) {
if (havingClauseAfterAnaylzed.contains(Subquery.class)) {
throw new AnalysisException("Only constant expr could be supported in having clause "
+ "when no aggregation in stmt");
}
analyzer.registerConjuncts(havingClauseAfterAnaylzed, true);
}
return;
}
if (fromClause.size() == 0) {
throw new AnalysisException("Aggregation without a FROM clause is not allowed");
}
if (selectList.isDistinct() && groupByClause == null) {
List<Expr> aggregateExpr = Lists.newArrayList();
TreeNode.collect(resultExprs, Expr.isAggregatePredicate(), aggregateExpr);
if (aggregateExpr.size() == resultExprs.size()) {
selectList.setIsDistinct(false);
}
}
if (selectList.isDistinct()
&& (groupByClause != null
|| TreeNode.contains(resultExprs, Expr.isAggregatePredicate())
|| (havingClauseAfterAnaylzed != null && havingClauseAfterAnaylzed.contains(
Expr.isAggregatePredicate())))) {
throw new AnalysisException("cannot combine SELECT DISTINCT with aggregate functions or GROUP BY");
}
if (groupByClause != null || TreeNode.contains(resultExprs, Expr.isAggregatePredicate())) {
for (SelectListItem item : selectList.getItems()) {
if (item.isStar()) {
throw new AnalysisException(
"cannot combine '*' in select list with GROUP BY: " + item.toSql());
}
}
}
ArrayList<FunctionCallExpr> aggExprs = Lists.newArrayList();
TreeNode.collect(resultExprs, Expr.isAggregatePredicate(), aggExprs);
if (havingClauseAfterAnaylzed != null) {
havingClauseAfterAnaylzed.collect(Expr.isAggregatePredicate(), aggExprs);
}
if (sortInfo != null) {
TreeNode.collect(sortInfo.getOrderingExprs(), Expr.isAggregatePredicate(), aggExprs);
}
ExprSubstitutionMap countAllMap = createCountAllMap(aggExprs, analyzer);
final ExprSubstitutionMap multiCountOrSumDistinctMap =
createSumOrCountMultiDistinctSMap(aggExprs, analyzer);
countAllMap = ExprSubstitutionMap.compose(multiCountOrSumDistinctMap, countAllMap, analyzer);
List<Expr> substitutedAggs =
Expr.substituteList(aggExprs, countAllMap, analyzer, false);
resultExprs = Expr.substituteList(resultExprs, countAllMap, analyzer, false);
aggExprs.clear();
TreeNode.collect(substitutedAggs, Expr.isAggregatePredicate(), aggExprs);
List<TupleId> groupingByTupleIds = new ArrayList<>();
if (groupByClause != null) {
groupByClause.genGroupingExprs();
ArrayList<Expr> groupingExprs = groupByClause.getGroupingExprs();
if (groupingInfo != null) {
groupingInfo.buildRepeat(groupingExprs, groupByClause.getGroupingSetList());
}
substituteOrdinalsAliases(groupingExprs, "GROUP BY", analyzer, false);
if (!groupByClause.isGroupByExtension() && !groupingExprs.isEmpty()) {
ArrayList<Expr> tempExprs = new ArrayList<>(groupingExprs);
groupingExprs.removeIf(Expr::isConstant);
if (groupingExprs.isEmpty() && aggExprs.isEmpty()) {
groupingExprs.add(tempExprs.get(0));
}
}
for (int i = 0; i < groupingExprs.size(); i++) {
groupingExprs.set(i, rewriteQueryExprByMvColumnExpr(groupingExprs.get(i), analyzer));
}
if (groupingInfo != null) {
groupingInfo.genOutputTupleDescAndSMap(analyzer, groupingExprs, aggExprs);
groupingByTupleIds.add(groupingInfo.getOutputTupleDesc().getId());
}
groupByClause.analyze(analyzer);
createAggInfo(groupingExprs, aggExprs, analyzer);
} else {
createAggInfo(new ArrayList<>(), aggExprs, analyzer);
}
if (aggInfo == null) {
return;
}
AggregateInfo finalAggInfo =
aggInfo.getSecondPhaseDistinctAggInfo() != null
? aggInfo.getSecondPhaseDistinctAggInfo()
: aggInfo;
groupingByTupleIds.add(finalAggInfo.getOutputTupleId());
ExprSubstitutionMap combinedSmap = ExprSubstitutionMap.compose(
countAllMap, finalAggInfo.getOutputSmap(), analyzer);
if (LOG.isDebugEnabled()) {
LOG.debug("combined smap: " + combinedSmap.debugString());
LOG.debug("desctbl: " + analyzer.getDescTbl().debugString());
LOG.debug("resultexprs: " + Expr.debugString(resultExprs));
}
if (havingClauseAfterAnaylzed != null) {
List<Subquery> subqueryInHaving = Lists.newArrayList();
havingClauseAfterAnaylzed.collect(Subquery.class, subqueryInHaving);
for (Subquery subquery : subqueryInHaving) {
if (subquery.isCorrelatedPredicate(getTableRefIds())) {
throw new AnalysisException("The correlated having clause is not supported");
}
}
}
/*
* All of columns of result and having clause are replaced by new slot ref
* which is bound by top tuple of agg info.
* For example:
* ResultExprs: SlotRef(k1), FunctionCall(sum(SlotRef(k2)))
* Having predicate: FunctionCall(sum(SlotRef(k2))) > subquery
* CombinedSMap: <SlotRef(k1) tuple 0, SlotRef(k1) of tuple 3>,
*               <FunctionCall(SlotRef(k2)) tuple 0, SlotRef(sum(k2)) of tuple 3>
*
* After rewritten:
* ResultExprs: SlotRef(k1) of tuple 3, SlotRef(sum(k2)) of tuple 3
* Having predicate: SlotRef(sum(k2)) of tuple 3 > subquery
*/
resultExprs = Expr.substituteList(resultExprs, combinedSmap, analyzer, false);
if (LOG.isDebugEnabled()) {
LOG.debug("post-agg selectListExprs: " + Expr.debugString(resultExprs));
}
if (havingClauseAfterAnaylzed != null) {
havingPred = havingClauseAfterAnaylzed.substitute(combinedSmap, analyzer, false);
analyzer.registerConjuncts(havingPred, true, finalAggInfo.getOutputTupleId().asList());
if (LOG.isDebugEnabled()) {
LOG.debug("post-agg havingPred: " + havingPred.debugString());
}
}
if (sortInfo != null) {
sortInfo.substituteOrderingExprs(combinedSmap, analyzer);
if (LOG.isDebugEnabled()) {
LOG.debug("post-agg orderingExprs: "
+ Expr.debugString(sortInfo.getOrderingExprs()));
}
}
for (int i = 0; i < selectList.getItems().size(); ++i) {
if (!resultExprs.get(i).isBoundByTupleIds(groupingByTupleIds)) {
throw new AnalysisException(
"select list expression not produced by aggregation output " + "(missing from "
+ "GROUP BY clause?): " + selectList.getItems().get(i).getExpr().toSql());
}
}
if (orderByElements != null) {
for (int i = 0; i < orderByElements.size(); ++i) {
if (!sortInfo.getOrderingExprs().get(i).isBoundByTupleIds(groupingByTupleIds)) {
throw new AnalysisException(
"ORDER BY expression not produced by aggregation output " + "(missing from "
+ "GROUP BY clause?): " + orderByElements.get(i).getExpr().toSql());
}
if (sortInfo.getOrderingExprs().get(i).type.isObjectStored()) {
throw new AnalysisException("ORDER BY expression could not contain object-stored columnx.");
}
}
}
if (havingPred != null) {
if (!havingPred.isBoundByTupleIds(groupingByTupleIds)) {
throw new AnalysisException(
"HAVING clause not produced by aggregation output " + "(missing from GROUP BY "
+ "clause?): " + havingClause.toSql());
}
}
}