This is an automated email from the ASF dual-hosted git repository.
morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.1 by this push:
new 2fe43929580 branch-3.1: [fix](nereids)scalar subquery should not show
error message when there are multiple agg functions in top-level agg node
#52667 (#52969)
2fe43929580 is described below
commit 2fe439295801b5afb44a0ab7d8513fb81a88354a
Author: starocean999 <[email protected]>
AuthorDate: Wed Jul 9 10:49:52 2025 +0800
branch-3.1: [fix](nereids)scalar subquery should not show error message
when there are multiple agg functions in top-level agg node #52667 (#52969)
pick from master #52667
---
.../nereids/rules/analysis/SubqueryToApply.java | 211 ++++++++++++---------
.../rewrite/UnCorrelatedApplyAggregateFilter.java | 3 +-
.../rules/rewrite/UnCorrelatedApplyFilter.java | 2 +-
.../rewrite/UnCorrelatedApplyProjectFilter.java | 2 +-
.../trees/copier/LogicalPlanDeepCopier.java | 2 +-
.../nereids/trees/expressions/ScalarSubquery.java | 20 +-
.../nereids/trees/plans/logical/LogicalApply.java | 22 +--
.../rules/analysis/SubqueryToApplyTest.java | 67 +++++++
.../rules/rewrite/ExistsApplyToJoinTest.java | 8 +-
.../subquery/correlated_scalar_subquery.out | Bin 736 -> 866 bytes
.../subquery/correlated_scalar_subquery.groovy | 5 +
11 files changed, 215 insertions(+), 127 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SubqueryToApply.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SubqueryToApply.java
index c6cf687819a..4165a9d0516 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SubqueryToApply.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/SubqueryToApply.java
@@ -27,7 +27,6 @@ import
org.apache.doris.nereids.rules.expression.ExpressionRewriteContext;
import
org.apache.doris.nereids.rules.expression.rules.TrySimplifyPredicateWithMarkJoinSlot;
import org.apache.doris.nereids.trees.TreeNode;
import org.apache.doris.nereids.trees.expressions.Alias;
-import org.apache.doris.nereids.trees.expressions.And;
import org.apache.doris.nereids.trees.expressions.CompoundPredicate;
import org.apache.doris.nereids.trees.expressions.Exists;
import org.apache.doris.nereids.trees.expressions.Expression;
@@ -37,7 +36,6 @@ import
org.apache.doris.nereids.trees.expressions.LessThanEqual;
import org.apache.doris.nereids.trees.expressions.ListQuery;
import org.apache.doris.nereids.trees.expressions.MarkJoinSlotReference;
import org.apache.doris.nereids.trees.expressions.NamedExpression;
-import org.apache.doris.nereids.trees.expressions.Not;
import org.apache.doris.nereids.trees.expressions.Or;
import org.apache.doris.nereids.trees.expressions.ScalarSubquery;
import org.apache.doris.nereids.trees.expressions.Slot;
@@ -64,6 +62,7 @@ import
org.apache.doris.nereids.trees.plans.logical.LogicalSort;
import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.nereids.util.Utils;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -141,7 +140,7 @@ public class SubqueryToApply implements AnalysisRuleFactory
{
.collect(ImmutableList.toImmutableList()),
tmpPlan,
context.getSubqueryToMarkJoinSlot(),
ctx.cascadesContext,
- Optional.of(conjunct), false,
isMarkSlotNotNull);
+ Optional.of(conjunct), isMarkSlotNotNull);
applyPlan = result.first;
tmpPlan = applyPlan;
newConjuncts.add(result.second.isPresent() ?
result.second.get() : conjunct);
@@ -183,7 +182,7 @@ public class SubqueryToApply implements AnalysisRuleFactory
{
Pair<LogicalPlan, Optional<Expression>> result =
subqueryToApply(Utils.fastToImmutableList(subqueryExprs), childPlan,
context.getSubqueryToMarkJoinSlot(),
ctx.cascadesContext,
- Optional.of(newProject), true, false);
+ Optional.of(newProject), false);
applyPlan = result.first;
childPlan = applyPlan;
newProjects.add(
@@ -267,7 +266,7 @@ public class SubqueryToApply implements AnalysisRuleFactory
{
subqueryExprs.stream().collect(ImmutableList.toImmutableList()),
relatedInfoList.get(i) ==
RelatedInfo.RelatedToLeft ? leftChildPlan : rightChildPlan,
context.getSubqueryToMarkJoinSlot(),
- ctx.cascadesContext, Optional.of(conjunct),
false, isMarkSlotNotNull);
+ ctx.cascadesContext, Optional.of(conjunct),
isMarkSlotNotNull);
applyPlan = result.first;
if (relatedInfoList.get(i) ==
RelatedInfo.RelatedToLeft) {
leftChildPlan = applyPlan;
@@ -369,22 +368,20 @@ public class SubqueryToApply implements
AnalysisRuleFactory {
private Pair<LogicalPlan, Optional<Expression>> subqueryToApply(
List<SubqueryExpr> subqueryExprs, LogicalPlan childPlan,
Map<SubqueryExpr, Optional<MarkJoinSlotReference>>
subqueryToMarkJoinSlot,
- CascadesContext ctx, Optional<Expression> conjunct, boolean
isProject,
- boolean isMarkJoinSlotNotNull) {
- Pair<LogicalPlan, Optional<Expression>> tmpPlan = Pair.of(childPlan,
conjunct);
+ CascadesContext ctx, Optional<Expression> correlatedOuterExpr,
boolean isMarkJoinSlotNotNull) {
+ Pair<LogicalPlan, Optional<Expression>> tmpPlan = Pair.of(childPlan,
correlatedOuterExpr);
for (int i = 0; i < subqueryExprs.size(); ++i) {
SubqueryExpr subqueryExpr = subqueryExprs.get(i);
if (subqueryExpr instanceof Exists &&
hasTopLevelScalarAgg(subqueryExpr.getQueryPlan())) {
// because top level scalar agg always returns a value or
null(for empty input)
- // so Exists and Not Exists conjunct are always evaluated to
True and false literals respectively
- // we don't create apply node for it
+ // so Exists and Not Exists correlatedOuterExpr are always
evaluated to
+ // True and false literals respectively, we don't create apply
node for it
continue;
}
if (!ctx.subqueryIsAnalyzed(subqueryExpr)) {
tmpPlan = addApply(subqueryExpr, tmpPlan.first,
- subqueryToMarkJoinSlot, ctx, tmpPlan.second,
- isProject, subqueryExprs.size() == 1,
isMarkJoinSlotNotNull);
+ subqueryToMarkJoinSlot, ctx, tmpPlan.second,
isMarkJoinSlotNotNull);
}
}
return tmpPlan;
@@ -399,30 +396,27 @@ public class SubqueryToApply implements
AnalysisRuleFactory {
return false;
}
- private Pair<LogicalPlan, Optional<Expression>> addApply(SubqueryExpr
subquery,
- LogicalPlan childPlan,
+ private Pair<LogicalPlan, Optional<Expression>> addApply(SubqueryExpr
subquery, LogicalPlan childPlan,
Map<SubqueryExpr, Optional<MarkJoinSlotReference>>
subqueryToMarkJoinSlot,
- CascadesContext ctx, Optional<Expression> conjunct, boolean
isProject,
- boolean singleSubquery, boolean isMarkJoinSlotNotNull) {
+ CascadesContext ctx, Optional<Expression> correlatedOuterExpr,
boolean isMarkJoinSlotNotNull) {
ctx.setSubqueryExprIsAnalyzed(subquery, true);
Optional<MarkJoinSlotReference> markJoinSlot =
subqueryToMarkJoinSlot.get(subquery);
- boolean needAddScalarSubqueryOutputToProjects =
isConjunctContainsScalarSubqueryOutput(
- subquery, conjunct, isProject, singleSubquery);
+ boolean needAddScalarSubqueryOutputToProjects =
isScalarSubqueryOutputUsedInOuterScope(
+ subquery, correlatedOuterExpr);
// for scalar subquery, we need ensure it output at most 1 row
// by doing that, we add an aggregate function any_value() to the
project list
// we use needRuntimeAnyValue to indicate if any_value() is needed
// if needRuntimeAnyValue is true, we will add it to the project list
boolean needRuntimeAnyValue = false;
- NamedExpression oldSubqueryOutput =
subquery.getQueryPlan().getOutput().get(0);
+ NamedExpression subqueryOutput =
subquery.getQueryPlan().getOutput().get(0);
if (subquery instanceof ScalarSubquery) {
// scalar sub query may adjust output slot's nullable.
- oldSubqueryOutput = ((ScalarSubquery)
subquery).getOutputSlotAdjustNullable();
+ subqueryOutput = ((ScalarSubquery)
subquery).getOutputSlotAdjustNullable();
}
Slot countSlot = null;
Slot anyValueSlot = null;
- Optional<Expression> newConjunct = conjunct;
- if (needAddScalarSubqueryOutputToProjects && subquery instanceof
ScalarSubquery
- && !subquery.getCorrelateSlots().isEmpty()) {
+ Optional<Expression> newCorrelatedOuterExpr = correlatedOuterExpr;
+ if (needAddScalarSubqueryOutputToProjects &&
!subquery.getCorrelateSlots().isEmpty()) {
if (((ScalarSubquery) subquery).hasTopLevelScalarAgg()) {
// consider sql: SELECT * FROM t1 WHERE t1.a <= (SELECT
COUNT(t2.a) FROM t2 WHERE (t1.b = t2.b));
// when unnest correlated subquery, we create a left join node.
@@ -430,44 +424,21 @@ public class SubqueryToApply implements
AnalysisRuleFactory {
// if there is no match, the row from right table is filled
with nulls
// but COUNT function is always not nullable.
// so wrap COUNT with Nvl to ensure its result is 0 instead of
null to get the correct result
- if (conjunct.isPresent()) {
- NamedExpression agg =
ScalarSubquery.getTopLevelScalarAggFunction(
- subquery.getQueryPlan(),
subquery.getCorrelateSlots()).get();
- if (agg instanceof Alias) {
- Map<Expression, Expression> replaceMap = new
HashMap<>();
- if (((Alias) agg).child() instanceof
NotNullableAggregateFunction) {
- NotNullableAggregateFunction notNullableAggFunc =
- (NotNullableAggregateFunction) ((Alias)
agg).child();
- if (subquery.getQueryPlan() instanceof
LogicalProject) {
- LogicalProject logicalProject =
- (LogicalProject)
subquery.getQueryPlan();
-
Preconditions.checkState(logicalProject.getOutputs().size() == 1,
- "Scalar subuqery's should only output
1 column");
- Slot aggSlot = agg.toSlot();
- replaceMap.put(aggSlot, new Alias(new
Nvl(aggSlot,
-
notNullableAggFunc.resultForEmptyInput())));
- NamedExpression newOutput = (NamedExpression)
ExpressionUtils
- .replace((NamedExpression)
logicalProject.getProjects().get(0), replaceMap);
- replaceMap.clear();
- replaceMap.put(oldSubqueryOutput,
newOutput.toSlot());
- oldSubqueryOutput = newOutput;
- subquery = subquery.withSubquery((LogicalPlan)
logicalProject.child());
- } else {
- replaceMap.put(oldSubqueryOutput, new
Nvl(oldSubqueryOutput,
-
notNullableAggFunc.resultForEmptyInput()));
- }
- if (!replaceMap.isEmpty()) {
- newConjunct =
Optional.of(ExpressionUtils.replace(conjunct.get(), replaceMap));
- }
- }
- }
+ if (correlatedOuterExpr.isPresent()) {
+ List<NamedExpression> aggFunctions =
ScalarSubquery.getTopLevelScalarAggFunctions(
+ subquery.getQueryPlan(),
subquery.getCorrelateSlots());
+ SubQueryRewriteResult result =
addNvlForScalarSubqueryOutput(aggFunctions, subqueryOutput,
+ subquery, correlatedOuterExpr);
+ subqueryOutput = result.subqueryOutput;
+ subquery = result.subquery;
+ newCorrelatedOuterExpr = result.correlatedOuterExpr;
}
} else {
// if scalar subquery doesn't have top level scalar agg we
will create one, for example
// select (select t2.c1 from t2 where t2.c2 = t1.c2) from t1;
// the original output of the correlate subquery is t2.c1,
after adding a scalar agg, it will be
// select (select count(*), any_value(t2.c1) from t2 where
t2.c2 = t1.c2) from t1;
- Alias anyValueAlias = new Alias(new
AnyValue(oldSubqueryOutput));
+ Alias anyValueAlias = new Alias(new AnyValue(subqueryOutput));
LogicalAggregate<Plan> aggregate;
if (((ScalarSubquery) subquery).limitOneIsEliminated()) {
aggregate = new LogicalAggregate<>(ImmutableList.of(),
@@ -480,10 +451,11 @@ public class SubqueryToApply implements
AnalysisRuleFactory {
}
anyValueSlot = anyValueAlias.toSlot();
subquery = subquery.withSubquery(aggregate);
- if (conjunct.isPresent()) {
+ if (correlatedOuterExpr.isPresent()) {
Map<Expression, Expression> replaceMap = new HashMap<>();
- replaceMap.put(oldSubqueryOutput, anyValueSlot);
- newConjunct =
Optional.of(ExpressionUtils.replace(conjunct.get(), replaceMap));
+ replaceMap.put(subqueryOutput, anyValueSlot);
+ newCorrelatedOuterExpr =
Optional.of(ExpressionUtils.replace(correlatedOuterExpr.get(),
+ replaceMap));
}
needRuntimeAnyValue = true;
}
@@ -507,7 +479,7 @@ public class SubqueryToApply implements AnalysisRuleFactory
{
subquery.getCorrelateSlots(),
subQueryType, isNot, compareExpr,
subquery.getTypeCoercionExpr(), Optional.empty(),
markJoinSlot,
- needAddScalarSubqueryOutputToProjects, isProject,
isMarkJoinSlotNotNull,
+ needAddScalarSubqueryOutputToProjects, isMarkJoinSlotNotNull,
childPlan, subquery.getQueryPlan());
ImmutableList.Builder<NamedExpression> projects =
@@ -530,27 +502,108 @@ public class SubqueryToApply implements
AnalysisRuleFactory {
new LessThanEqual(countSlot, new
IntegerLiteral(1))),
new VarcharLiteral("correlate scalar subquery must
return only 1 row"))));
logicalProject = new LogicalProject(projects.build(),
newApply);
- logicalProject = new LogicalProject(upperProjects,
logicalProject);
} else {
logicalProject = new LogicalProject(projects.build(),
newApply);
}
} else {
- projects.add(oldSubqueryOutput);
+ projects.add(subqueryOutput);
logicalProject = new LogicalProject(projects.build(),
newApply);
}
} else {
logicalProject = new LogicalProject(projects.build(), newApply);
}
- return Pair.of(logicalProject, newConjunct);
+ return Pair.of(logicalProject, newCorrelatedOuterExpr);
+ }
+
+ /**
+ * SubQueryRewriteResult
+ */
+ @VisibleForTesting
+ protected class SubQueryRewriteResult {
+ public SubqueryExpr subquery;
+ public NamedExpression subqueryOutput;
+ public Optional<Expression> correlatedOuterExpr;
+
+ public SubQueryRewriteResult(SubqueryExpr subquery, NamedExpression
subqueryOutput,
+ Optional<Expression> correlatedOuterExpr)
{
+ this.subquery = subquery;
+ this.subqueryOutput = subqueryOutput;
+ this.correlatedOuterExpr = correlatedOuterExpr;
+ }
+ }
+
+ /**
+ * for correlated scalar subquery like select c1, (select count(c1) from
t2 where t1.c2 = t2.c2) as c from t1
+ * if we don't add extra nvl for not nullable agg functions, the plan will
be like bellow:
+ * +--LogicalProject(projects=[c1#0, count(c1)#4 AS `c`#5])
+ * +--LogicalJoin(type=LEFT_OUTER_JOIN, hashJoinConjuncts=[(c2#1 =
c2#3)])
+ * |--LogicalOlapScan (t1)
+ * +--LogicalAggregate[108] (groupByExpr=[c2#3], outputExpr=[c2#3,
count(c1#2) AS `count(c1)`#4])
+ * +--LogicalOlapScan (t2)
+ *
+ * the count(c1)#4 may be null because of unmatched row of left outer
join, but count is not nullable agg function,
+ * it should never be null, we need use nvl to wrap it and change the plan
like bellow:
+ * +--LogicalProject(projects=[c1#0, ifnull(count(c1)#4, 0) AS `c`#5])
+ * +--LogicalJoin(type=LEFT_OUTER_JOIN, hashJoinConjuncts=[(c2#1 =
c2#3)])
+ * |--LogicalOlapScan (t1)
+ * +--LogicalAggregate[108] (groupByExpr=[c2#3], outputExpr=[c2#3,
count(c1#2) AS `count(c1)`#4])
+ * +--LogicalOlapScan (t2)
+ *
+ * in order to do that, we need change subquery's output and replace the
correlated outer expr
+ */
+ @VisibleForTesting
+ protected SubQueryRewriteResult
addNvlForScalarSubqueryOutput(List<NamedExpression> aggFunctions,
+
NamedExpression subqueryOutput,
+ SubqueryExpr
subquery,
+
Optional<Expression> correlatedOuterExpr) {
+ SubQueryRewriteResult result = new SubQueryRewriteResult(subquery,
subqueryOutput, correlatedOuterExpr);
+ Map<Expression, Expression> replaceMapForSubqueryProject = new
HashMap<>();
+ Map<Expression, Expression> replaceMapForCorrelatedOuterExpr = new
HashMap<>();
+ for (NamedExpression agg : aggFunctions) {
+ if (agg instanceof Alias && ((Alias) agg).child() instanceof
NotNullableAggregateFunction) {
+ NotNullableAggregateFunction notNullableAggFunc =
+ (NotNullableAggregateFunction) ((Alias) agg).child();
+ if (subquery.getQueryPlan() instanceof LogicalProject) {
+ // if the top node of subquery is LogicalProject, we need
replace the agg slot in
+ // project list by nvl(agg), and this project will be
placed above LogicalApply node
+ Slot aggSlot = agg.toSlot();
+ replaceMapForSubqueryProject.put(aggSlot, new Alias(new
Nvl(aggSlot,
+ notNullableAggFunc.resultForEmptyInput())));
+ } else {
+ replaceMapForCorrelatedOuterExpr.put(subqueryOutput, new
Nvl(subqueryOutput,
+ notNullableAggFunc.resultForEmptyInput()));
+ }
+ }
+ }
+ if (!replaceMapForSubqueryProject.isEmpty()) {
+ Preconditions.checkState(subquery.getQueryPlan() instanceof
LogicalProject,
+ "Scalar subquery's top plan node should be
LogicalProject");
+ LogicalProject logicalProject =
+ (LogicalProject) subquery.getQueryPlan();
+ Preconditions.checkState(logicalProject.getOutputs().size() == 1,
+ "Scalar subuqery's should only output 1 column");
+ NamedExpression newOutput = (NamedExpression) ExpressionUtils
+ .replace((NamedExpression)
logicalProject.getProjects().get(0),
+ replaceMapForSubqueryProject);
+ replaceMapForCorrelatedOuterExpr.put(subqueryOutput,
newOutput.toSlot());
+ result.subqueryOutput = newOutput;
+ // logicalProject will be placed above LogicalApply later, so we
remove it from subquery
+ result.subquery = subquery.withSubquery((LogicalPlan)
logicalProject.child());
+ }
+ if (!replaceMapForCorrelatedOuterExpr.isEmpty()) {
+ result.correlatedOuterExpr =
Optional.of(ExpressionUtils.replace(correlatedOuterExpr.get(),
+ replaceMapForCorrelatedOuterExpr));
+ }
+ return result;
}
- private boolean isConjunctContainsScalarSubqueryOutput(
- SubqueryExpr subqueryExpr, Optional<Expression> conjunct, boolean
isProject, boolean singleSubquery) {
+ private boolean isScalarSubqueryOutputUsedInOuterScope(
+ SubqueryExpr subqueryExpr, Optional<Expression>
correlatedOuterExpr) {
return subqueryExpr instanceof ScalarSubquery
- && ((conjunct.isPresent() && ((ImmutableSet)
conjunct.get().collect(SlotReference.class::isInstance))
- .contains(subqueryExpr.getQueryPlan().getOutput().get(0)))
- || isProject);
+ && ((correlatedOuterExpr.isPresent()
+ && ((ImmutableSet)
correlatedOuterExpr.get().collect(SlotReference.class::isInstance))
+
.contains(subqueryExpr.getQueryPlan().getOutput().get(0))));
}
/**
@@ -683,30 +736,6 @@ public class SubqueryToApply implements
AnalysisRuleFactory {
}
- private enum SearchState {
- SearchNot,
- SearchAnd,
- SearchExistsOrInSubquery
- }
-
- private boolean shouldOutputMarkJoinSlot(Expression expr, SearchState
searchState) {
- if (searchState == SearchState.SearchNot && expr instanceof Not) {
- if (shouldOutputMarkJoinSlot(((Not) expr).child(),
SearchState.SearchAnd)) {
- return true;
- }
- } else if (searchState == SearchState.SearchAnd && expr instanceof
And) {
- for (Expression child : expr.children()) {
- if (shouldOutputMarkJoinSlot(child,
SearchState.SearchExistsOrInSubquery)) {
- return true;
- }
- }
- } else if (searchState == SearchState.SearchExistsOrInSubquery
- && (expr instanceof InSubquery || expr instanceof Exists)) {
- return true;
- }
- return false;
- }
-
private List<Boolean> shouldOutputMarkJoinSlot(Collection<Expression>
conjuncts) {
ImmutableList.Builder<Boolean> result =
ImmutableList.builderWithExpectedSize(conjuncts.size());
for (Expression expr : conjuncts) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyAggregateFilter.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyAggregateFilter.java
index 258698b1f7f..ddc6a17b171 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyAggregateFilter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyAggregateFilter.java
@@ -118,8 +118,7 @@ public class UnCorrelatedApplyAggregateFilter implements
RewriteRuleFactory {
return new LogicalApply<>(apply.getCorrelationSlot(),
apply.getSubqueryType(), apply.isNot(),
apply.getCompareExpr(), apply.getTypeCoercionExpr(),
ExpressionUtils.optionalAnd(correlatedPredicate),
apply.getMarkJoinSlotReference(),
- apply.isNeedAddSubOutputToProjects(), apply.isInProject(),
- apply.isMarkJoinSlotNotNull(), apply.left(),
+ apply.isNeedAddSubOutputToProjects(),
apply.isMarkJoinSlotNotNull(), apply.left(),
isRightChildAgg ? newAgg : apply.right().withChildren(newAgg));
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyFilter.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyFilter.java
index b5732a604ca..10bdf2f0046 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyFilter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyFilter.java
@@ -70,7 +70,7 @@ public class UnCorrelatedApplyFilter extends
OneRewriteRuleFactory {
apply.getCompareExpr(), apply.getTypeCoercionExpr(),
ExpressionUtils.optionalAnd(correlatedPredicate),
apply.getMarkJoinSlotReference(),
apply.isNeedAddSubOutputToProjects(),
- apply.isInProject(), apply.isMarkJoinSlotNotNull(),
apply.left(), child);
+ apply.isMarkJoinSlotNotNull(), apply.left(), child);
}).toRule(RuleType.UN_CORRELATED_APPLY_FILTER);
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyProjectFilter.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyProjectFilter.java
index 4f31d672a16..ceced5b6143 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyProjectFilter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyProjectFilter.java
@@ -91,7 +91,7 @@ public class UnCorrelatedApplyProjectFilter extends
OneRewriteRuleFactory {
apply.getCompareExpr(),
apply.getTypeCoercionExpr(),
ExpressionUtils.optionalAnd(correlatedPredicate),
apply.getMarkJoinSlotReference(),
apply.isNeedAddSubOutputToProjects(),
- apply.isInProject(),
apply.isMarkJoinSlotNotNull(), apply.left(), newProject);
+ apply.isMarkJoinSlotNotNull(), apply.left(),
newProject);
}).toRule(RuleType.UN_CORRELATED_APPLY_PROJECT_FILTER);
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/copier/LogicalPlanDeepCopier.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/copier/LogicalPlanDeepCopier.java
index 2dd581e38d7..8ae323cfb41 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/copier/LogicalPlanDeepCopier.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/copier/LogicalPlanDeepCopier.java
@@ -138,7 +138,7 @@ public class LogicalPlanDeepCopier extends
DefaultPlanRewriter<DeepCopierContext
.map(m -> (MarkJoinSlotReference)
ExpressionDeepCopier.INSTANCE.deepCopy(m, context));
return new LogicalApply<>(correlationSlot, apply.getSubqueryType(),
apply.isNot(),
compareExpr, typeCoercionExpr, correlationFilter,
- markJoinSlotReference, apply.isNeedAddSubOutputToProjects(),
apply.isInProject(),
+ markJoinSlotReference, apply.isNeedAddSubOutputToProjects(),
apply.isMarkJoinSlotNotNull(), left, right);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
index 4ded2447731..ed608135254 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ScalarSubquery.java
@@ -34,6 +34,7 @@ import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -79,18 +80,15 @@ public class ScalarSubquery extends SubqueryExpr implements
LeafExpression {
}
/**
- * getTopLevelScalarAggFunction
- */
- public static Optional<NamedExpression> getTopLevelScalarAggFunction(Plan
queryPlan,
+ * get Top Level ScalarAgg Functions
+ */
+ public static List<NamedExpression> getTopLevelScalarAggFunctions(Plan
queryPlan,
List<Slot> correlateSlots) {
LogicalAggregate<?> aggregate = findTopLevelScalarAgg(queryPlan,
ImmutableSet.copyOf(correlateSlots));
if (aggregate != null) {
- Preconditions.checkState(aggregate.getAggregateFunctions().size()
== 1,
- "in scalar subquery, should only return 1 column 1 row, "
- + "but we found multiple columns ",
aggregate.getOutputExpressions());
- return Optional.of((NamedExpression)
aggregate.getOutputExpressions().get(0));
+ return aggregate.getOutputExpressions();
} else {
- return Optional.empty();
+ return new ArrayList<>();
}
}
@@ -148,9 +146,9 @@ public class ScalarSubquery extends SubqueryExpr implements
LeafExpression {
public static Slot getScalarQueryOutputAdjustNullable(Plan queryPlan,
List<Slot> correlateSlots) {
Slot output = queryPlan.getOutput().get(0);
boolean nullable = true;
- Optional<NamedExpression> aggOpt =
getTopLevelScalarAggFunction(queryPlan, correlateSlots);
- if (aggOpt.isPresent()) {
- NamedExpression agg = aggOpt.get();
+ List<NamedExpression> aggOpts =
getTopLevelScalarAggFunctions(queryPlan, correlateSlots);
+ if (aggOpts.size() == 1) {
+ NamedExpression agg = aggOpts.get(0);
if (agg.getExprId().equals(output.getExprId())
&& agg instanceof Alias
&& ((Alias) agg).child() instanceof
NotNullableAggregateFunction) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
index 59d72f51c95..5d21b1156f2 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalApply.java
@@ -68,9 +68,6 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
// The slot replaced by the subquery in MarkJoin
private final Optional<MarkJoinSlotReference> markJoinSlotReference;
- // Whether the subquery is in logicalProject
- private final boolean inProject;
-
// Whether adding the subquery's output to projects
private final boolean needAddSubOutputToProjects;
@@ -89,7 +86,6 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
Optional<Expression> correlationFilter,
Optional<MarkJoinSlotReference> markJoinSlotReference,
boolean needAddSubOutputToProjects,
- boolean inProject,
boolean isMarkJoinSlotNotNull,
LEFT_CHILD_TYPE leftChild, RIGHT_CHILD_TYPE rightChild) {
super(PlanType.LOGICAL_APPLY, groupExpression, logicalProperties,
leftChild, rightChild);
@@ -104,17 +100,16 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
this.correlationFilter = correlationFilter;
this.markJoinSlotReference = markJoinSlotReference;
this.needAddSubOutputToProjects = needAddSubOutputToProjects;
- this.inProject = inProject;
this.isMarkJoinSlotNotNull = isMarkJoinSlotNotNull;
}
public LogicalApply(List<Slot> correlationSlot, SubQueryType subqueryType,
boolean isNot,
Optional<Expression> compareExpr, Optional<Expression>
typeCoercionExpr,
Optional<Expression> correlationFilter,
Optional<MarkJoinSlotReference> markJoinSlotReference,
- boolean needAddSubOutputToProjects, boolean inProject, boolean
isMarkJoinSlotNotNull,
+ boolean needAddSubOutputToProjects, boolean isMarkJoinSlotNotNull,
LEFT_CHILD_TYPE input, RIGHT_CHILD_TYPE subquery) {
this(Optional.empty(), Optional.empty(), correlationSlot,
subqueryType, isNot, compareExpr, typeCoercionExpr,
- correlationFilter, markJoinSlotReference,
needAddSubOutputToProjects, inProject, isMarkJoinSlotNotNull,
+ correlationFilter, markJoinSlotReference,
needAddSubOutputToProjects, isMarkJoinSlotNotNull,
input, subquery);
}
@@ -178,10 +173,6 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
return needAddSubOutputToProjects;
}
- public boolean isInProject() {
- return inProject;
- }
-
public boolean isMarkJoinSlotNotNull() {
return isMarkJoinSlotNotNull;
}
@@ -229,7 +220,6 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
&& Objects.equals(correlationFilter,
that.getCorrelationFilter())
&& Objects.equals(markJoinSlotReference,
that.getMarkJoinSlotReference())
&& needAddSubOutputToProjects ==
that.needAddSubOutputToProjects
- && inProject == that.inProject
&& isMarkJoinSlotNotNull == that.isMarkJoinSlotNotNull
&& isNot == that.isNot;
}
@@ -238,7 +228,7 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
public int hashCode() {
return Objects.hash(
correlationSlot, subqueryType, compareExpr, typeCoercionExpr,
correlationFilter,
- markJoinSlotReference, needAddSubOutputToProjects, inProject,
isMarkJoinSlotNotNull, isNot);
+ markJoinSlotReference, needAddSubOutputToProjects,
isMarkJoinSlotNotNull, isNot);
}
@Override
@@ -264,7 +254,7 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
public LogicalApply<Plan, Plan> withChildren(List<Plan> children) {
Preconditions.checkArgument(children.size() == 2);
return new LogicalApply<>(correlationSlot, subqueryType, isNot,
compareExpr, typeCoercionExpr,
- correlationFilter, markJoinSlotReference,
needAddSubOutputToProjects, inProject, isMarkJoinSlotNotNull,
+ correlationFilter, markJoinSlotReference,
needAddSubOutputToProjects, isMarkJoinSlotNotNull,
children.get(0), children.get(1));
}
@@ -272,7 +262,7 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
public Plan withGroupExpression(Optional<GroupExpression> groupExpression)
{
return new LogicalApply<>(groupExpression,
Optional.of(getLogicalProperties()),
correlationSlot, subqueryType, isNot, compareExpr,
typeCoercionExpr, correlationFilter,
- markJoinSlotReference, needAddSubOutputToProjects, inProject,
isMarkJoinSlotNotNull, left(), right());
+ markJoinSlotReference, needAddSubOutputToProjects,
isMarkJoinSlotNotNull, left(), right());
}
@Override
@@ -281,6 +271,6 @@ public class LogicalApply<LEFT_CHILD_TYPE extends Plan,
RIGHT_CHILD_TYPE extends
Preconditions.checkArgument(children.size() == 2);
return new LogicalApply<>(groupExpression, logicalProperties,
correlationSlot, subqueryType, isNot,
compareExpr, typeCoercionExpr, correlationFilter,
markJoinSlotReference,
- needAddSubOutputToProjects, inProject, isMarkJoinSlotNotNull,
children.get(0), children.get(1));
+ needAddSubOutputToProjects, isMarkJoinSlotNotNull,
children.get(0), children.get(1));
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/SubqueryToApplyTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/SubqueryToApplyTest.java
new file mode 100644
index 00000000000..47127307995
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/SubqueryToApplyTest.java
@@ -0,0 +1,67 @@
+// 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.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.EqualTo;
+import org.apache.doris.nereids.trees.expressions.ExprId;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.ScalarSubquery;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.functions.agg.Count;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Nvl;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+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.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.util.LogicalPlanBuilder;
+import org.apache.doris.nereids.util.PlanConstructor;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+public class SubqueryToApplyTest {
+
+ @Test
+ void testAddNvlAggregate() {
+ SlotReference slotReference = new SlotReference("col1",
IntegerType.INSTANCE);
+ NamedExpression aggregateFunction = new Alias(new ExprId(12345), new
Count(slotReference), "count");
+ LogicalOlapScan logicalOlapScan =
PlanConstructor.newLogicalOlapScan(0, "t1", 0);
+ LogicalAggregate<Plan> logicalAggregate = new LogicalAggregate<>(
+ ImmutableList.of(), ImmutableList.of(aggregateFunction),
logicalOlapScan);
+ LogicalPlanBuilder planBuilder = new
LogicalPlanBuilder(logicalAggregate);
+ LogicalPlan plan = planBuilder.projectAll().build();
+ Optional<Expression> correlatedOuterExpr = Optional
+ .of(new EqualTo(plan.getOutput().get(0), new
BigIntLiteral(1)));
+ ScalarSubquery subquery = new ScalarSubquery(plan);
+ SubqueryToApply subqueryToApply = new SubqueryToApply();
+ SubqueryToApply.SubQueryRewriteResult rewriteResult =
subqueryToApply.addNvlForScalarSubqueryOutput(
+ ImmutableList.of(aggregateFunction), plan.getOutput().get(0),
subquery, correlatedOuterExpr);
+ Assertions.assertInstanceOf(EqualTo.class,
rewriteResult.correlatedOuterExpr.get());
+ Assertions.assertInstanceOf(Alias.class, rewriteResult.subqueryOutput);
+ Assertions.assertInstanceOf(Nvl.class,
rewriteResult.subqueryOutput.child(0));
+ Assertions.assertEquals(rewriteResult.subqueryOutput.toSlot(),
+ rewriteResult.correlatedOuterExpr.get().child(0));
+ }
+}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ExistsApplyToJoinTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ExistsApplyToJoinTest.java
index 597eadf7a55..6290178754a 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ExistsApplyToJoinTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ExistsApplyToJoinTest.java
@@ -47,7 +47,7 @@ class ExistsApplyToJoinTest implements
MemoPatternMatchSupported {
new LogicalApply<>(ImmutableList.of(leftSlots.get(0),
rightSlots.get(0)),
LogicalApply.SubQueryType.EXITS_SUBQUERY, false,
Optional.empty(), Optional.empty(),
Optional.of(equalTo), Optional.empty(),
- false, false, false, left, right);
+ false, false, left, right);
PlanChecker.from(MemoTestUtils.createConnectContext(), apply)
.applyTopDown(new ExistsApplyToJoin())
.matchesFromRoot(logicalJoin(
@@ -67,7 +67,7 @@ class ExistsApplyToJoinTest implements
MemoPatternMatchSupported {
new LogicalApply<>(Collections.emptyList(),
LogicalApply.SubQueryType.EXITS_SUBQUERY, false,
Optional.empty(), Optional.empty(),
Optional.of(equalTo), Optional.empty(),
- false, false, false, left, right);
+ false, false, left, right);
PlanChecker.from(MemoTestUtils.createConnectContext(), apply)
.applyTopDown(new ExistsApplyToJoin())
.matchesFromRoot(logicalJoin(
@@ -87,7 +87,7 @@ class ExistsApplyToJoinTest implements
MemoPatternMatchSupported {
new LogicalApply<>(Collections.emptyList(),
LogicalApply.SubQueryType.EXITS_SUBQUERY, true,
Optional.empty(), Optional.empty(),
Optional.of(equalTo), Optional.empty(),
- false, false, false, left, right);
+ false, false, left, right);
PlanChecker.from(MemoTestUtils.createConnectContext(), apply)
.applyTopDown(new ExistsApplyToJoin())
.matchesFromRoot(logicalFilter(logicalJoin(
@@ -108,7 +108,7 @@ class ExistsApplyToJoinTest implements
MemoPatternMatchSupported {
new LogicalApply<>(ImmutableList.of(leftSlots.get(0),
rightSlots.get(0)),
LogicalApply.SubQueryType.EXITS_SUBQUERY, true,
Optional.empty(), Optional.empty(),
Optional.of(equalTo), Optional.empty(),
- false, false, false, left, right);
+ false, false, left, right);
PlanChecker.from(MemoTestUtils.createConnectContext(), apply)
.applyTopDown(new ExistsApplyToJoin())
.matchesFromRoot(logicalJoin(
diff --git
a/regression-test/data/nereids_p0/subquery/correlated_scalar_subquery.out
b/regression-test/data/nereids_p0/subquery/correlated_scalar_subquery.out
index 6811af2c187..b95797f5fbc 100644
Binary files
a/regression-test/data/nereids_p0/subquery/correlated_scalar_subquery.out and
b/regression-test/data/nereids_p0/subquery/correlated_scalar_subquery.out differ
diff --git
a/regression-test/suites/nereids_p0/subquery/correlated_scalar_subquery.groovy
b/regression-test/suites/nereids_p0/subquery/correlated_scalar_subquery.groovy
index d77ac769122..60860383083 100644
---
a/regression-test/suites/nereids_p0/subquery/correlated_scalar_subquery.groovy
+++
b/regression-test/suites/nereids_p0/subquery/correlated_scalar_subquery.groovy
@@ -243,4 +243,9 @@ suite("correlated_scalar_subquery") {
"""
exception "access outer query column"
}
+
+ qt_select_agg_project1 """select c2 from correlated_scalar_t1 where
correlated_scalar_t1.c2 > (select if(count(c1) = 0, 2, 100) from
correlated_scalar_t2 where correlated_scalar_t1.c1 = correlated_scalar_t2.c1)
order by c2;"""
+ qt_select_agg_project2 """select c2 from correlated_scalar_t1 where
correlated_scalar_t1.c2 = (select if(sum(c1) is null, 2, 100) from
correlated_scalar_t2 where correlated_scalar_t1.c1 = correlated_scalar_t2.c1)
order by c2;"""
+ qt_select_2_aggs """select c2 from correlated_scalar_t1 where
correlated_scalar_t1.c2 > (select count(c1) - min(c1) from correlated_scalar_t2
where correlated_scalar_t1.c1 = correlated_scalar_t2.c1) order by c2;"""
+ qt_select_3_aggs """select c2 from correlated_scalar_t1 where
correlated_scalar_t1.c2 > (select if(sum(c1) is null, count(c1), max(c2)) from
correlated_scalar_t2 where correlated_scalar_t1.c1 = correlated_scalar_t2.c1)
order by c2;"""
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]