github-actions[bot] commented on code in PR #67940:
URL: https://github.com/apache/doris/pull/67940#discussion_r4090950117


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/UnCorrelatedApplyAggregateFilter.java:
##########
@@ -97,28 +657,2195 @@ public List<Rule> buildRules() {
             return apply;
         }
 
-        // pull up correlated filter into apply node
-        List<NamedExpression> newAggOutput = new 
ArrayList<>(agg.getOutputExpressions());
-        List<Expression> newGroupby =
-                Utils.getUnCorrelatedExprs(correlatedPredicate, 
apply.getCorrelationSlot());
-        newGroupby.addAll(agg.getGroupByExpressions());
+        CorrelatedAggregatePredicates predicates =
+                CorrelatedAggregatePredicates.of(apply, correlatedPredicate,
+                        aggregation.filtersAboveTheAggregation());
+        // A global aggregate above an aggregate which can return no row for a 
correlation key
+        // returns a row for the empty input of that key, and neither rewrite 
can reproduce it (see
+        // observesTheEmptyInputOfAGlobalAggregate): report those subqueries 
instead of dropping the
+        // row and evaluating the subquery to false.
+        if (observesTheEmptyInputOfAGlobalAggregate(apply, aggregation, 
predicates)) {
+            throw new AnalysisException("Unsupported correlated subquery with 
grouping and/or aggregation "
+                    + apply.right());
+        }
+        if (needCorrelatedAggregationOnOuter(apply, aggregation, 
correlatedPredicate, predicates)) {
+            Plan aggregatedOuter = pullUpCorrelatedPredicateByAggregatingOuter(
+                    apply, aggregation, unCorrelatedPredicate, predicates);
+            if (aggregatedOuter != null) {
+                return aggregatedOuter;
+            }
+            // The original rewrite is known to be not equivalent for this 
subquery and the rewrite
+            // above cannot be applied safely: report the subquery as 
unsupported instead of building
+            // a plan whose result is wrong.
+            throw new AnalysisException("Unsupported correlated subquery with 
grouping and/or aggregation "
+                    + apply.right());
+        }
+
+        // pull up correlated filter into apply node: the inner side of every 
correlated predicate
+        // becomes a group by column and an output column of the aggregation 
below the filter, so that
+        // the aggregation of one outer row is the aggregation of the rows of 
its own key, and every
+        // aggregate above that aggregation groups the rows of its child by 
the same keys (a scalar
+        // subquery keeps the rows of its aggregation through an aggregation 
which SubqueryToApply adds
+        // above it, and those rows may not be mixed between two correlation 
keys either)
+        List<Expression> newGroupby = 
Utils.getUnCorrelatedExprs(correlatedPredicate, apply.getCorrelationSlot());
         Map<Expression, Slot> unCorrelatedExprToSlot = Maps.newHashMap();
+        List<NamedExpression> newGroupbyOutputs = 
Lists.newArrayListWithCapacity(newGroupby.size());
         for (Expression expression : newGroupby) {
             if (expression instanceof Slot) {
-                newAggOutput.add((NamedExpression) expression);
+                newGroupbyOutputs.add((NamedExpression) expression);
             } else {
                 Alias alias = new Alias(expression);
                 unCorrelatedExprToSlot.put(expression, alias.toSlot());
-                newAggOutput.add(alias);
+                newGroupbyOutputs.add(alias);
             }
         }
+        // the keys which the aggregates above the deepest one group by: the 
slots the keys have in
+        // the output of the aggregation below them
+        List<NamedExpression> keySlots = newGroupbyOutputs.stream()
+                
.map(NamedExpression::toSlot).collect(ImmutableList.toImmutableList());
         correlatedPredicate = ExpressionUtils.replace(correlatedPredicate, 
unCorrelatedExprToSlot);
-        LogicalAggregate newAgg = new LogicalAggregate<>(newGroupby, 
newAggOutput,
-                
PlanUtils.filterOrSelf(ImmutableSet.copyOf(unCorrelatedPredicate), 
filter.child()));
+        Map<LogicalAggregate<?>, Plan> newAggregations = new 
IdentityHashMap<>();
+        for (LogicalAggregate<?> aggregate : aggregation.aggregationChain()) {
+            boolean isTheAggregationOfTheDomain = aggregate == 
aggregation.domainAggregation();
+            List<Expression> groupBy = Lists.newArrayList(
+                    isTheAggregationOfTheDomain ? newGroupby : keySlots);
+            groupBy.addAll(aggregate.getGroupByExpressions());
+            List<NamedExpression> outputs = 
Lists.newArrayList(aggregate.getOutputExpressions());
+            outputs.addAll(isTheAggregationOfTheDomain ? newGroupbyOutputs : 
keySlots);
+            Plan child = isTheAggregationOfTheDomain
+                    // the projections below it only carry the columns which 
the aggregation needs, so
+                    // the new aggregation reads the rows of the filter 
directly
+                    ? 
PlanUtils.filterOrSelf(ImmutableSet.copyOf(unCorrelatedPredicate),
+                            aggregation.domainFilter().child())
+                    : aggregate.child(0);
+            newAggregations.put(aggregate, new LogicalAggregate<>(groupBy, 
outputs, child));
+        }
+        // the predicates which were already pulled into the apply are the 
predicates of the HAVING
+        // clause of the subquery: they were evaluated on the rows of the old 
aggregate and have to
+        // stay in the filter of the new apply, otherwise the subquery loses 
them
+        List<Expression> newCorrelationFilter = Lists.newArrayList();
+        apply.getCorrelationFilter().map(ExpressionUtils::extractConjunction)
+                .ifPresent(newCorrelationFilter::addAll);
+        newCorrelationFilter.addAll(correlatedPredicate);
+        // the join which unnests the apply reads the inner side of the 
correlation predicates from
+        // the output of the right side, so the projections which wrap the new 
aggregate have to
+        // expose the keys it added: an IN subquery keeps the projections of 
its select list above
+        // the aggregate (for example the outputs [c1] and [c1, c2] which wrap 
an aggregate
+        // computing count(*) as c1, random() as c2), and a projection which 
hides one of the keys
+        // makes the apply unresolvable
+        Set<Slot> keysToExpose = keySlots.stream()
+                
.map(NamedExpression::toSlot).collect(ImmutableSet.toImmutableSet());
+        // The outputs of the top aggregate are exposed by the projections 
above that aggregate
+        // alone, because the projections below it cannot produce them: the 
aggregate which defines
+        // them sits above those projections. The predicates which were pulled 
into the apply read
+        // the outputs of the top aggregate as well (for example the max(c) <= 
t1.c1 of the HAVING
+        // clause), and the projection below the top aggregate has to carry 
the keys alone. For
+        // example the subquery of
+        //
+        //     select t1.c1 from t1 where t1.c1 in (select max(c) from (select 
count(*) as c from t2
+        //         where t2.c1 = t1.c1 group by t2.c2) x having max(c) <= 
t1.c1)
+        //
+        // reaches the rewrite with the plan
+        //
+        //     Apply(correlationFilter=[(max(c) <= t1.c1)])
+        //       |-- t1
+        //       +-- Project([max(c)])                                 [the 
select list]
+        //             +-- Aggregate(group by [], output [max(c) as max(c)])
+        //                   +-- Project([c])                         [the 
projection below the
+        //                         +-- Aggregate(group by [t2.c2],     
aggregate which defines
+        //                               output [t2.c2, count(*) as c]) max(c)]
+        //                               +-- Filter(t2.c1 = t1.c1)
+        //                                     +-- t2
+        //
+        // and appending max(c) to the projection of the count (the projection 
below the aggregate
+        // which defines it) would make that projection read a slot which its 
child cannot produce,
+        // so the plan would be rejected by the slot check of the rewrite.
+        Set<Slot> outputsOfTheTopAggregation = newCorrelationFilter.stream()
+                .flatMap(conjunct -> conjunct.getInputSlots().stream())
+                .filter(slot -> 
newAggregations.get(aggregation.topAggregation()).getOutput().contains(slot))
+                .filter(slot -> !keysToExpose.contains(slot))
+                .collect(ImmutableSet.toImmutableSet());
+        // the predicates of the apply are evaluated on the nodes above the 
aggregation of the
+        // subquery, which produce the outputs of that aggregation themselves, 
so no output of it has
+        // to be appended to the projections below them
         return new LogicalApply<>(apply.getCorrelationSlot(), 
apply.getSubqueryType(), apply.isNot(),
                 apply.getCompareExpr(), apply.getTypeCoercionExpr(),
-                ExpressionUtils.optionalAnd(correlatedPredicate), 
apply.getMarkJoinSlotReference(),
+                ExpressionUtils.optionalAnd(newCorrelationFilter), 
apply.getMarkJoinSlotReference(),
                 apply.isNeedAddSubOutputToProjects(), 
apply.isMarkJoinSlotNotNull(), apply.left(),
-                isRightChildAgg ? newAgg : apply.right().withChildren(newAgg));
+                rebuildTheAggregationChain(apply.right(), aggregation, 
newAggregations, keysToExpose,
+                        outputsOfTheTopAggregation, null, ImmutableSet.of(), 
false, Maps.newHashMap()));
+    }
+
+    /**
+     * The aggregation of the subquery with the keys of the correlation added 
to its group by and to
+     * its output: the rows of one correlation key are the rows of the 
subquery for the outer rows
+     * which own that key, so an aggregation above the aggregation of the 
domain may not mix them.
+     * For example the aggregate of the sum of the example of TheAggregation 
is rewritten into
+     *
+     *     Aggregate(group by [key.c1], output [sum(c) as sum(x.c), key.c1])
+     *
+     * around the rewritten aggregation of the domain, whose rows carry the 
key as well (see
+     * rebuildTheAggregationChain).
+     */
+    private static LogicalAggregate<?> 
withTheKeysInTheGroupBy(LogicalAggregate<?> aggregate,
+            List<? extends Expression> keys, Slot matchMarkerOfTheEmptyDomain,
+            boolean exposesTheMatchMarker, Map<Expression, Expression> 
nullableInnerSlots,
+            boolean ignoresTheKeptRowOfAnEmptyDomain) {
+        List<Expression> groupBy = Lists.newArrayList(keys);
+        for (Expression groupByExpression : aggregate.getGroupByExpressions()) 
{
+            // the group by of an aggregate above the aggregation of the 
domain may read the columns
+            // of the inner side as well, and the left outer join of the 
domain reports them as
+            // nullable (see nullableInnerSlots)
+            groupBy.add(ExpressionUtils.replace(groupByExpression, 
nullableInnerSlots));
+        }
+        if (matchMarkerOfTheEmptyDomain != null && exposesTheMatchMarker) {
+            // The marker of the row which is kept for an empty domain is read 
by the guard of the
+            // aggregates above the aggregation of the domain and by the 
filters between the
+            // aggregates (see rebuildTheAggregationChain), so those 
aggregates expose it. The marker
+            // is null for the row which is kept for an empty domain, so the 
grouping of the rows of a
+            // correlation key does not change. The top aggregate does not 
expose it: the rows which it
+            // produces are the rows of the subquery, and the marker belongs 
to the rows below it (the
+            // aggregates above the aggregation of the domain read it from 
their own input).
+            groupBy.add(matchMarkerOfTheEmptyDomain);
+        }
+        List<NamedExpression> outputs = Lists.newArrayList();
+        if (!ignoresTheKeptRowOfAnEmptyDomain) {
+            // The row which the rewrite keeps for an empty correlated domain 
is the row which the
+            // original subquery computes out of the empty input of the 
aggregation of the domain (its
+            // own guard makes that aggregation return the value of an empty 
input, see
+            // guardAggregateArguments), so the aggregates above it read the 
value which that row
+            // carries (the count 0 of
+            // select (select count(*) from t2 where t2.c1 = t1.c1 having 
count(*) = 0) from t1, for
+            // example). They read the columns of the inner side of the join 
through the nullable slots.
+            for (NamedExpression output : aggregate.getOutputExpressions()) {
+                outputs.add((NamedExpression) ExpressionUtils.replace(output, 
nullableInnerSlots));
+            }
+        } else {
+            // The subquery produces no row for an empty correlated domain (a 
grouped aggregation of
+            // the domain reports an empty domain as "no row", and a HAVING 
clause which does not hold
+            // for the row of a global aggregation of the domain removes that 
row), so the aggregates
+            // above the aggregation of the domain ignore the row which the 
rewrite keeps for that
+            // domain: they return the value of their empty input for it (see 
guardAggregateArguments),
+            // which is the value the original subquery computes for the empty 
domain as well.
+            Set<AggregateFunction> aggregates = Sets.newLinkedHashSet();
+            for (NamedExpression output : aggregate.getOutputExpressions()) {
+                
aggregates.addAll(output.collect(AggregateFunction.class::isInstance));
+            }
+            Map<Expression, Expression> compensated = 
guardAggregateArguments(aggregates,
+                    matchMarkerOfTheEmptyDomain);
+            if (compensated == null) {
+                // an aggregate of the aggregation cannot be guarded, so the 
row which is kept for an
+                // empty input cannot be told apart from a row of the rows 
below the aggregation
+                return null;
+            }
+            for (NamedExpression output : aggregate.getOutputExpressions()) {
+                outputs.add((NamedExpression) ExpressionUtils.replace(
+                        (NamedExpression) ExpressionUtils.replace(output, 
compensated), nullableInnerSlots));
+            }
+        }
+        keys.forEach(key -> outputs.add((NamedExpression) key));
+        if (matchMarkerOfTheEmptyDomain != null && exposesTheMatchMarker
+                && !outputs.contains(matchMarkerOfTheEmptyDomain)) {
+            // The aggregates above this one and the filters between them read 
the marker from their
+            // own input, and the outputs of an aggregate decide on the rows 
which it produces: the
+            // marker is a group key of this aggregate (see above), so it has 
to be one of its outputs
+            // as well, otherwise the nodes above it cannot read it.
+            outputs.add(matchMarkerOfTheEmptyDomain);
+        }
+        return new LogicalAggregate<>(groupBy, outputs, aggregate.child(0));
+    }
+
+    /**
+     * Whether the rewrite of the outer side has to keep one row for the 
correlation keys whose rows
+     * below the aggregation of the domain are missing, although that 
aggregation returns no row of its
+     * own for them: every aggregate above it is global, so the aggregation of 
the original subquery
+     * produces one row for the empty input of such a key, and the aggregates 
which the rewrite builds
+     * above that aggregation can be guarded with the marker of the row which 
is kept for it (see
+     * guardAggregateArguments and withTheKeysInTheGroupBy). The value which 
that row exposes is then
+     * the value which the original subquery exposes for the key. For example 
the subquery of
+     *
+     *     select o.k from o where o.k in (
+     *         select coalesce(max(c), 0) from
+     *             (select count(*) as c from i where i.k = o.k group by i.g) 
x)
+     *
+     * returns one row whose value is 0 for the outer rows whose correlated 
domain is empty (the max of
+     * the empty derived table is null and the coalesce turns that null into 
the 0), so the outer row
+     * of the value 0 matches the subquery: the rewrite keeps a row for such a 
key, the max above it
+     * ignores that row and returns the null of its empty input, and the 
coalesce of the plan of the
+     * subquery turns that null into the 0 as well.
+     *
+     * An EXISTS subquery reads whether the row of such a key exists instead 
of the value it exposes,
+     * so the row has to be kept when the HAVING clause of the subquery keeps 
the row of the empty
+     * input (see the EXISTS branch below).
+     */
+    private static boolean keepsTheRowOfAnEmptyDomain(LogicalApply<?, ?> 
apply, TheAggregation aggregation,
+            CorrelatedAggregatePredicates predicates) {
+        List<LogicalAggregate<?>> chain = aggregation.aggregationChain();
+        List<LogicalAggregate<?>> aboveTheDomain = chain.subList(0, 
chain.size() - 1);
+        if (aboveTheDomain.isEmpty()) {
+            // the aggregation of the domain is the only aggregation of the 
subquery: the rewrite of the
+            // outer side keeps a row of its own for an empty domain when that 
aggregation is global,
+            // and no aggregate above it observes such a row
+            return false;
+        }
+        if (aboveTheDomain.stream().anyMatch(aggregate -> 
!aggregate.getGroupByExpressions().isEmpty())) {
+            // an aggregate above the aggregation of the domain groups the 
rows which it reads, so the
+            // row which is kept for an empty domain builds a group of its own 
in that aggregate, while
+            // the aggregation of the original subquery produces no row at all 
for such an empty input
+            return false;
+        }
+        if (!chain.stream()
+                .flatMap(aggregate -> 
aggregate.getOutputExpressions().stream())
+                .flatMap(output -> 
output.collect(AggregateFunction.class::isInstance).stream())
+                .allMatch(function -> function instanceof 
NullIgnoringAggregateFunction)) {
+            // only the aggregates which ignore null arguments can be guarded, 
so that the row which is
+            // kept for an empty domain does not contribute to them (see 
guardAggregateArguments)
+            return false;
+        }
+        if (apply.isExist()) {
+            // The row which the aggregation of an empty correlated domain 
produces exists for the
+            // subquery when the HAVING clause of the aggregation above the 
one of the domain holds for
+            // the values of that empty input (see 
havingMayHoldWithEmptyInput): the EXISTS of the
+            // subquery of
+            //
+            //     select t1.c1 from t1 where exists (select max(c) from 
(select count(*) as c from t2
+            //         where t2.c1 = t1.c1 group by t2.c2) x having max(c) is 
null)
+            //
+            // is true for the outer rows whose correlated domain is empty 
(the max of the empty
+            // derived table is null and the HAVING clause keeps that row). 
The rewrite keeps the row of
+            // such a key and lets the aggregates above the aggregation of the 
domain return the values
+            // of an empty input for it, so that the nodes above the 
aggregation decide on the row the
+            // way the original subquery does (see rebuildTheAggregationChain 
and
+            // guardAggregateArguments). A HAVING clause which rejects the row 
of the empty input
+            // (having max(c) > 0, for example) drops it, and the nodes above 
the aggregation reject
+            // the row which the rewrite keeps for such a key as well.
+            List<Expression> havingConjuncts = predicates.havingPredicates();
+            return aboveTheDomain.stream()
+                    .filter(aggregate -> 
aggregate.getGroupByExpressions().isEmpty())
+                    .anyMatch(aggregate -> 
havingMayHoldWithEmptyInput(aggregate,
+                            Sets.newLinkedHashSet(havingConjuncts)));
+        }
+        return true;
+    }
+
+    /**
+     * Whether the aggregation of the subquery holds a global aggregate above 
an aggregate which can
+     * return no row for a correlation key, and the subquery observes the row 
which that global
+     * aggregate returns for the empty input.
+     *
+     * The rewrite adds the correlation keys to the group by of every 
aggregate of the chain (see
+     * pullUpCorrelatedFilter and withTheKeysInTheGroupBy), so a global 
aggregate above the
+     * aggregation of the domain produces no row at all for a key whose rows 
below it are missing,
+     * while the aggregation of the original subquery returns one row for that 
empty input. The
+     * subquery of
+     *
+     *     select t1.c1 from t1 where exists (select max(c) from (select 
count(*) as c from t2
+     *         where t2.c1 = t1.c1 group by t2.c2) x having max(c) is null)
+     *
+     * is true for the outer rows whose correlated domain is empty, because 
the max of the empty
+     * derived table is null and the HAVING clause keeps that row, while a 
rewrite which dropped the
+     * key would produce no row for it and the semi join would drop the outer 
row. The aggregation of
+     * the inner side is not equivalent for such subqueries, and the 
aggregation of the outer side is
+     * only equivalent when it keeps a row for the empty domain and lets the 
aggregates above the
+     * aggregation of the domain return the values of an empty input for it 
(see
+     * keepsTheRowOfAnEmptyDomain); the caller reports the subqueries which 
neither of them can
+     * rewrite.
+     */
+    private static boolean 
observesTheEmptyInputOfAGlobalAggregate(LogicalApply<?, ?> apply,
+            TheAggregation aggregation, CorrelatedAggregatePredicates 
predicates) {
+        if (keepsTheRowOfAnEmptyDomain(apply, aggregation, predicates)) {
+            // the rewrite of the outer side keeps the row which such a key is 
missing (see
+            // keepsTheRowOfAnEmptyDomain), so the subquery is not reported
+            return false;
+        }
+        return theEmptyInputOfAGlobalAggregateIsObservable(apply, aggregation, 
predicates);
+    }
+
+    /**
+     * The detection of observesTheEmptyInputOfAGlobalAggregate on its own: 
the subqueries
+     * which this detection reports are the subqueries whose rewrite would 
drop the row which the
+     * aggregation of the original subquery returns for a correlation key 
whose rows below the
+     * aggregation of the domain are missing. The rewrite of the outer side 
keeps that row and lets the
+     * aggregates above the aggregation of the domain return the values of an 
empty input for it when
+     * every one of them is a global aggregate which ignores null arguments 
(see
+     * keepsTheRowOfAnEmptyDomain), and those subqueries are rewritten instead 
of reported.
+     */
+    private static boolean 
theEmptyInputOfAGlobalAggregateIsObservable(LogicalApply<?, ?> apply,
+            TheAggregation aggregation, CorrelatedAggregatePredicates 
predicates) {
+        List<LogicalAggregate<?>> chain = aggregation.aggregationChain();
+        if (chain.get(chain.size() - 1).getGroupByExpressions().isEmpty()
+                && theFiltersBetweenTheAggregations(aggregation).isEmpty()) {
+            // the aggregation of the domain returns a row for every 
correlation key, so no
+            // aggregate above it can observe an empty input (a filter between 
those aggregations is
+            // the HAVING clause of the aggregation of the domain: it decides 
on the row of the empty
+            // input and may reject it, which the aggregates above it observe)
+            return false;
+        }
+        // the aggregates above the deepest one: the deepest one reads the 
rows of the domain of a
+        // correlation key, and the predicates of that domain may leave them 
empty
+        List<LogicalAggregate<?>> aboveTheDomain = chain.subList(0, 
chain.size() - 1);
+        if (apply.isExist()) {
+            // the row which the global aggregate returns for the empty input 
decides whether the
+            // EXISTS reports the outer row, unless the nodes above the 
aggregation reject that row
+            // (the predicates of the HAVING clause which reference the outer 
query were pulled into
+            // the apply, and they decide on the row of the empty input as 
well)
+            List<Expression> havingConjuncts = predicates.havingPredicates();
+            return aboveTheDomain.stream()
+                    .filter(aggregate -> 
aggregate.getGroupByExpressions().isEmpty())
+                    .anyMatch(aggregate -> 
havingMayHoldWithEmptyInput(aggregate,
+                            Sets.newLinkedHashSet(havingConjuncts)));
+        }
+        if (apply.isScalar()) {
+            // A scalar subquery exposes the output of the aggregation of its 
domain: the join of
+            // the rewrite reports a null for the keys whose rows below the 
aggregation are missing,
+            // and SubqueryToApply repairs that null with the nvl of the value 
which the top
+            // aggregate returns for an empty input. A global aggregate below 
the top aggregate
+            // changes the value which the top one computes out of the row of 
the empty input.
+            boolean hasAGlobalAggregateBelowTheTop = 
aboveTheDomain.stream().skip(1)
+                    .anyMatch(aggregate -> 
aggregate.getGroupByExpressions().isEmpty());
+            return hasAGlobalAggregateBelowTheTop
+                    && (returnsAValueForAnEmptyInput(chain.get(0)) || 
aboveTheDomain.stream().skip(1)
+                            
.anyMatch(UnCorrelatedApplyAggregateFilter::returnsAValueForAnEmptyInput));
+        }
+        // An IN subquery compares the outer value with the value of the 
aggregation of its domain:
+        // the value which a global aggregate returns for an empty input can 
match the outer value,
+        // while the rewrite has no row to compare it with (a null value of 
the aggregation does not
+        // match either, so an aggregation of nullable aggregates alone is 
left alone).
+        if (aboveTheDomain.stream()
+                
.anyMatch(UnCorrelatedApplyAggregateFilter::returnsAValueForAnEmptyInput)) {
+            return true;
+        }
+        // The nodes above the aggregation of the domain may expose a value of 
their own for the empty
+        // input as well, even though the aggregates are nullable: the 
projection of the subquery of
+        //
+        //     select o.k from o where o.k in (
+        //         select coalesce(max(c), 0) from
+        //             (select count(*) as c from i where i.k = o.k group by 
i.g) x)
+        //
+        // turns the null which the max of the empty derived table returns 
into the 0 which an outer
+        // row with the value 0 compares with, while the rewrite has no row to 
compare it with and the
+        // semi join drops that row (see exposesAValueForAnEmptyInput).
+        if (exposesAValueForAnEmptyInput(apply, aggregation, aboveTheDomain)) {
+            return true;
+        }
+        // The missing row of a key is observable when the result of the IN is 
not read as the decision
+        // on the outer row alone: the null which the subquery of the original 
query compares with
+        // (the row of the aggregation of an empty derived table, for example) 
makes the IN unknown,
+        // while the rewrite compares with nothing, which is false for an IN 
and true for a NOT IN.
+        // The result of an IN which is used as a value is its mark, so its 
null and its false are
+        // observable as well (the plan of such an IN is a mark join). For 
example the subquery of
+        //
+        //     select o.k from o where o.k not in (
+        //         select max(c) from (select count(*) as c from i where i.k = 
o.k group by i.g) x)
+        //
+        // returns one row for the outer rows whose correlated domain is empty 
(the max of the empty
+        // derived table is null), so their NOT IN is unknown and those rows 
are not returned, while
+        // the rewrite produces no row for those keys and their NOT IN is 
true. A global aggregate
+        // above the aggregation of the domain is what makes such a row 
disappear: the key is added to
+        // the group by of that aggregate (see withTheKeysInTheGroupBy), so a 
key without rows below it
+        // has no group at all.
+        return (apply.isNot() || apply.getMarkJoinSlotReference().isPresent())
+                && aboveTheDomain.stream()
+                        .anyMatch(aggregate -> 
aggregate.getGroupByExpressions().isEmpty());
+    }
+
+    /**
+     * Whether one of the aggregates of the aggregation declares a value of 
its own for an empty
+     * input (the count 0 or the empty array of an array_agg, for example). An 
aggregate which
+     * declares no such value is left to the rewrite of the other cases, which 
reads the value of an
+     * empty input the way the rest of the engine does (see 
keepsTheValueOfAnEmptyDomain): the value
+     * of a UDAF is written in the UDAF itself, so its declaration does not 
tell it.
+     */
+    private static boolean returnsAValueForAnEmptyInput(LogicalAggregate<?> 
aggregate) {
+        for (NamedExpression output : aggregate.getOutputExpressions()) {
+            if (output.collect(AggregateFunction.class::isInstance).stream()
+                    .anyMatch(function -> function instanceof 
NotNullableAggregateFunction)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * The filters which sit between two aggregations of the chain: they 
decide on the rows which the
+     * aggregation below them produces (the filters directly above an 
aggregation are its HAVING
+     * clauses), so they are not evaluated for a correlation key whose rows 
below that aggregation are
+     * missing (see keepsTheRowOfAnEmptyDomain). Every interval between the 
aggregation of the domain
+     * and the top aggregation may carry such a filter: the HAVING clause of 
the aggregation of the
+     * domain is the deepest one, and an aggregation above it may have a 
HAVING clause of its own.
+     */
+    private static Set<LogicalFilter> 
theFiltersBetweenTheAggregations(TheAggregation aggregation) {
+        if (aggregation.topAggregation() == aggregation.domainAggregation()) {
+            // the aggregation of the domain is the only aggregation of the 
subquery, so there is no
+            // aggregation above it and no filter between such aggregations 
either (the filter of the
+            // domain itself reads the rows of the domain and is not a filter 
between the aggregates)
+            return ImmutableSet.of();
+        }
+        List<LogicalAggregate<?>> chain = aggregation.aggregationChain();
+        Set<LogicalFilter> filters = Sets.newLinkedHashSet();
+        for (int i = chain.size() - 2; i >= 0; i--) {
+            // the nodes between the aggregate at the index i and the one 
below it, which the chain
+            // keeps at the index i + 1
+            Plan below = chain.get(i).child(0);
+            while (below != chain.get(i + 1)) {
+                if (below instanceof LogicalFilter) {
+                    filters.add((LogicalFilter) below);
+                }
+                below = below.child(0);
+            }
+        }
+        return filters;
+    }
+
+    /**
+     * Whether the nodes above the top aggregate expose a value of their own 
for the empty input of a
+     * correlation key: the input of such a key is empty when the aggregation 
of the domain has no row
+     * for it and a global aggregate above that aggregation returns no row for 
that key in the
+     * rewrite, while the aggregation of the original subquery computes the 
nodes above it out of the
+     * row of the empty input. The projection of the subquery of
+     *
+     *     select o.k from o where o.k in (
+     *         select coalesce(max(c), 0) from
+     *             (select count(*) as c from i where i.k = o.k group by i.g) 
x)
+     *
+     * turns the null which the max of the empty derived table returns into 
the 0 which the outer row
+     * with the value 0 compares with, while the rewrite has no row to compare 
it with: the semi join
+     * would drop that outer row. The value which a node computes for the 
empty input is read by
+     * replacing the outputs of the top aggregate with the values they return 
for it and folding the
+     * expression.
+     */
+    private static boolean exposesAValueForAnEmptyInput(LogicalApply<?, ?> 
apply, TheAggregation aggregation,
+            List<LogicalAggregate<?>> aboveTheDomain) {
+        if (aboveTheDomain.stream().noneMatch(aggregate -> 
aggregate.getGroupByExpressions().isEmpty())) {
+            // every aggregate above the aggregation of the domain groups the 
rows it reads, so a key
+            // without rows below that aggregation has no group in those 
aggregates either
+            return false;
+        }
+        Map<Expression, Expression> emptyValues = Maps.newHashMap();
+        Set<Slot> outputsOfTheTopAggregation = Sets.newHashSet();
+        for (NamedExpression output : 
aggregation.topAggregation().getOutputExpressions()) {
+            Expression expression = output instanceof Alias ? ((Alias) 
output).child() : output;
+            if (!(expression instanceof AggregateFunction)) {
+                continue;
+            }
+            Expression emptyValue = 
emptyValueForEmptyInput((AggregateFunction) expression);
+            if (emptyValue == null) {
+                // the aggregate declares no value for an empty input: it 
returns the null of the empty
+                // input (the max of no row, for example)
+                emptyValue = new NullLiteral(output.getDataType());
+            }
+            emptyValues.put(output.toSlot(), emptyValue);
+            outputsOfTheTopAggregation.add(output.toSlot());
+        }
+        if (emptyValues.isEmpty()) {
+            return false;
+        }
+        Plan below = apply.right();
+        while (below != aggregation.topAggregation()) {
+            if (below instanceof LogicalProject) {
+                for (NamedExpression project : ((LogicalProject<?>) 
below).getProjects()) {
+                    Expression expression = project instanceof Alias ? 
((Alias) project).child() : project;
+                    if (Sets.intersection(expression.getInputSlots(), 
outputsOfTheTopAggregation).isEmpty()) {
+                        // the projection does not read the aggregation of the 
domain
+                        continue;
+                    }
+                    Expression folded = 
FoldConstantRuleOnFE.evaluateWithoutContext(
+                            ExpressionUtils.replace(expression, emptyValues));
+                    if (folded instanceof Literal && !(folded instanceof 
NullLiteral)) {
+                        // the nodes above the aggregation expose this value 
for the empty input
+                        return true;
+                    }
+                }
+            }
+            below = below.child(0);
+        }
+        return false;
+    }
+
+    /**
+     * Replace every aggregate of the chain with its rewritten version and 
expose the keys through
+     * the projections between them, so that every aggregate above the deepest 
one can group by the
+     * keys. The walk stops at the deepest aggregate: its rewritten version 
already reads the rows of
+     * the filter below it (see pullUpCorrelatedFilter).
+     *
+     * The projections below the top aggregate only expose the keys, while the 
projections above it
+     * expose the outputs of the top aggregate as well: the aggregate which 
defines those outputs
+     * sits above the projections below it, so they cannot produce them. The 
outputs to expose are
+     * therefore dropped as soon as the walk reaches the top aggregate.
+     *
+     * For example the plan of the example of TheAggregation is rewritten into
+     *
+     *     Apply(correlationFilter=[t2.c1 = t1.c1])
+     *       |-- t1
+     *       +-- Filter(sum(x.c) > 2)
+     *             +-- Aggregate(group by [key.c1], output [sum(c) as 
sum(x.c), key.c1])
+     *                   +-- Project([c, key.c1])                    [the key 
exposed between the
+     *                         +-- Filter(c > 1)                          
aggregates]
+     *                               +-- Aggregate(group by [key.c1, t2.c2],
+     *                                     output [t2.c2, count(*) as c, 
key.c1])
+     *                                     +-- t2
+     */
+    private static Plan rebuildTheAggregationChain(Plan plan, TheAggregation 
aggregation,
+            Map<LogicalAggregate<?>, Plan> newAggregations, Set<Slot> 
keysToExpose,
+            Set<Slot> outputsOfTheTopAggregation, Slot 
matchMarkerOfTheEmptyDomain,
+            Set<LogicalFilter> filtersWhichKeepTheRowOfAnEmptyDomain, boolean 
belowTheTopAggregate,
+            Map<Expression, Expression> nullableInnerSlots) {
+        Plan replacement = newAggregations.get(plan);
+        if (plan == aggregation.domainAggregation()) {
+            return replacement;
+        }
+        // the nodes below the top aggregate cannot produce its outputs, so 
they only carry the keys
+        Plan child = rebuildTheAggregationChain(plan.child(0), aggregation, 
newAggregations, keysToExpose,
+                plan == aggregation.topAggregation() ? ImmutableSet.of() : 
outputsOfTheTopAggregation,
+                matchMarkerOfTheEmptyDomain, 
filtersWhichKeepTheRowOfAnEmptyDomain,
+                belowTheTopAggregate || plan == aggregation.topAggregation(), 
nullableInnerSlots);
+        if (replacement != null) {
+            return replacement.withChildren(child);
+        }
+        if (filtersWhichKeepTheRowOfAnEmptyDomain.contains(plan)) {
+            // The row which the rewrite keeps for an empty domain is not a 
row of the rows below this
+            // filter (its marker is null), and the aggregation of the 
original subquery does not
+            // evaluate the filter for the empty input of such a key either: 
the row passes the filter,
+            // so that the aggregates above it return the values of an empty 
input for the key (see
+            // keepsTheRowOfAnEmptyDomain). Without the relaxation the filter 
would remove the row which
+            // the rewrite keeps for the key, and the aggregates above it 
(which the rewrite grouped by
+            // the correlation key) would produce no row at all for the key. 
The predicates of the
+            // filter are rebuilt as well: they may read the columns of the 
inner side, which the left
+            // outer join of the domain reports as nullable (see 
nullableInnerSlots).
+            List<Expression> conjuncts = Lists.newArrayList();
+            for (Expression conjunct : ((LogicalFilter<Plan>) 
plan).getConjuncts()) {
+                conjuncts.add(ExpressionUtils.or(
+                        ExpressionUtils.replace(conjunct, nullableInnerSlots),
+                        new IsNull(matchMarkerOfTheEmptyDomain)));
+            }
+            return new LogicalFilter<>(Sets.newLinkedHashSet(conjuncts), 
child);
+        }
+        if (plan instanceof LogicalProject) {
+            // the projections between the aggregates carry the columns which 
the aggregates above
+            // them need, the keys of the correlation included, and they read 
the columns of the inner
+            // side through the nullable slots of the left outer join of the 
domain
+            LogicalProject<?> project = (LogicalProject<?>) plan;
+            List<NamedExpression> projects = Lists.newArrayList();
+            for (NamedExpression projectExpression : project.getProjects()) {
+                projects.add((NamedExpression) ExpressionUtils.replace(
+                        projectExpression, nullableInnerSlots));
+            }
+            Set<Slot> exposed = projects.stream()
+                    
.map(NamedExpression::toSlot).collect(ImmutableSet.toImmutableSet());
+            boolean added = false;
+            for (Slot key : keysToExpose) {
+                if (!exposed.contains(key)) {
+                    projects.add(key);
+                    added = true;
+                }
+            }
+            for (Slot output : outputsOfTheTopAggregation) {
+                if (!exposed.contains(output)) {
+                    projects.add(output);
+                    added = true;
+                }
+            }
+            if (belowTheTopAggregate && matchMarkerOfTheEmptyDomain != null
+                    && !exposed.contains(matchMarkerOfTheEmptyDomain)) {
+                // the aggregates above the aggregation of the domain read the 
marker of the row which is
+                // kept for an empty domain from their own input, and the 
filters between those
+                // aggregations read it as well, so the projections below the 
top aggregate carry it
+                // (the projections above that aggregate are not read by any 
node which needs it)
+                projects.add(matchMarkerOfTheEmptyDomain);
+                added = true;
+            }
+            if (added || !projects.equals(project.getProjects())) {
+                return new LogicalProject<>(projects, project.isDistinct(), 
project.getAsteriskOutputs(), child);
+            }
+        }
+        return plan.withChildren(child);
+    }
+
+    /**
+     * Whether an EXISTS subquery keeps a node above its aggregation which 
decides on the rows the
+     * subquery returns: the projection of its select list, or a filter which 
sits above the HAVING
+     * clause of the subquery (see the two methods below). The two checks are 
told apart on the plan
+     * which this rule receives: the projection of the select list is not 
always kept above the
+     * aggregation, and the filter above the HAVING clause is a node above the 
aggregation of its own
+     * even when no projection wraps that aggregation (see 
hasProjectionAboveAggregate and
+     * hasFilterAboveHavingFilter). The rewrite keeps those nodes (see 
rebuildTheAggregationChain),
+     * and the aggregation of one outer row is the aggregation of the rows 
which they produce, so the
+     * aggregation is built on the outer side and the nodes are evaluated on 
the aggregation of one
+     * correlation key.
+     *
+     * Only an EXISTS subquery is decided by the nodes above its aggregation 
this way: the value
+     * which an IN or scalar subquery exposes is the output of the aggregation 
itself, which the
+     * rewrite reads through the nodes above it (see 
rebuildTheAggregationChain).
+     *
+     * For example the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2 where 
t2.c1 = t1.c1
+     *         having count(*) <= t1.c1 - 7 and count(*) >= 0)
+     *
+     * keeps the projection of its select list above the aggregation (see
+     * hasProjectionAboveAggregate), and the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select x.c from (select count(*) 
as c, random() as r
+     *         from t2 where t2.c1 = t1.c1 having count(*) = 0) x where x.r < 
-1)
+     *
+     * keeps a filter above the projection and above the HAVING clause (see
+     * hasFilterAboveHavingFilter); the nodes decide on the rows which the 
EXISTS has to report, so
+     * the aggregation of one outer row is the aggregation of the rows which 
they produce and is
+     * built on the outer side.
+     */
+    private static boolean keepsNodesAboveTheAggregation(LogicalApply<?, ?> 
apply, TheAggregation aggregation) {
+        return apply.isExist() && (hasProjectionAboveAggregate(apply, 
aggregation.topAggregation())
+                || (aggregation.havingFilter.isPresent()
+                        && hasFilterAboveHavingFilter(apply, 
aggregation.havingFilter.get())));
+    }
+
+    /**
+     * Whether a projection sits between the apply and the aggregation of the 
subquery: it exposes
+     * the select list of the subquery, which may only carry a part of the 
output of the
+     * aggregation, so the correlation key which the original rewrite adds to 
the group by of the
+     * aggregation is not part of that output. For example the projection of 
the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2 where 
t2.c1 = t1.c1
+     *         having count(*) <= t1.c1 - 7 and count(*) >= 0)
+     *
+     * sits between the apply and the aggregation when this rule matches: the 
right side of the
+     * apply is the projection of the select list of the subquery, below it 
the filter of the
+     * HAVING clause which stayed in the plan, below it the aggregation, the 
filter of the WHERE
+     * clause and t2. A projection is not always kept there (a projection 
which only passes a
+     * column of the aggregation through is merged into the nodes above it), 
so the strategy is
+     * decided on the plan which this rule receives, not on the query text.
+     */
+    private static boolean hasProjectionAboveAggregate(LogicalApply<?, ?> 
apply, LogicalAggregate<?> agg) {
+        Plan below = apply.right();
+        while (below != agg) {
+            if (below instanceof LogicalProject) {
+                return true;
+            }
+            below = below.child(0);
+        }
+        return false;
+    }
+
+    /**
+     * Whether the plan keeps a filter above the HAVING clause of the 
subquery. The walk starts at the
+     * right side of the apply and stops at the HAVING clause itself (the 
deepest filter above the top
+     * aggregation, see the field of TheAggregation), so every filter which it 
meets is a filter which
+     * sits above that clause: the walk does not stop at the filter which sits 
directly below the
+     * apply, and the filters above the HAVING clause are met before it 
whichever their number is. For
+     * example the plan
+     *
+     *     Filter(f1) - Filter(f2) - Filter(the HAVING clause) - Aggregate
+     *
+     * reports true for both f1 and f2, while a plan whose only filter is the 
HAVING clause reports
+     * false. Whether one of those filters is itself the predicate of a HAVING 
clause of a node above
+     * the aggregation (the WHERE clause of a derived table which reads the 
aggregation, for example)
+     * does not change that: the plan keeps it above the aggregation, so it is 
evaluated on the rows
+     * which the aggregation and its HAVING clause produce for one outer row, 
not on the rows of every
+     * outer row together.
+     *
+     * Such a filter decides on the rows which the subquery returns: it reads 
the projection of the
+     * select list or the output of the aggregation, and it can keep the row 
of an empty correlated
+     * domain (which the aggregation of the inner side cannot produce at all), 
so the subquery has a
+     * node above its aggregation which decides on its rows and the rewrite 
has to keep it and to
+     * build the aggregation on the outer side (see 
keepsNodesAboveTheAggregation). For example the
+     * filter of
+     *
+     *     select t1.c1 from t1 where exists (select x.c from (select count(*) 
as c, random() as r
+     *         from t2 where t2.c1 = t1.c1 having count(*) = 0) x where x.r < 
-1)
+     *
+     * Filter pushdown creates such a filter above the projection of the 
select list when a predicate
+     * reads a column of that projection which cannot be pushed below it (the 
volatile column r here),
+     * and the filter decides on the rows which the projection produces.
+     */
+    private static boolean hasFilterAboveHavingFilter(LogicalApply<?, ?> 
apply, LogicalFilter<Plan> havingFilter) {
+        Plan below = apply.right();
+        while (below != havingFilter) {
+            if (below instanceof LogicalFilter) {
+                return true;
+            }
+            below = below.child(0);
+        }
+        return false;
+    }
+
+    /**
+     * The predicates which relate the correlated subquery to the outer query, 
classified by the
+     * node on which they have to be evaluated.
+     *
+     * Domain predicates select the inner rows which belong to the correlated 
domain of one outer
+     * row, for example where t2.c1 = t1.c1. They are the predicates of the 
WHERE clause of the
+     * subquery (the filter below the aggregation) plus the predicates which 
were already pulled
+     * into the apply and do not reference the aggregation.
+     *
+     * Aggregate predicates reference the output of the aggregation and decide 
which rows of the
+     * aggregation the subquery returns for one outer row, for example having 
count(*) <= t1.c1 - 7.
+     * They are the predicates of the HAVING clause of the subquery, and the 
earlier rules
+     * (UnCorrelatedApplyFilter and UnCorrelatedApplyProjectFilter, they run 
before this one) split
+     * them by the outer query: the ones which reference the outer query were 
pulled into the apply,
+     * so they are not part of the plan of the subquery any more and this 
rewrite has to evaluate
+     * them again above the aggregation it builds (aggregatePredicates); the 
ones which do not
+     * reference the outer query stayed in the plan, in the filter between the 
apply and the
+     * aggregation, and the rewrite keeps them unchanged (havingConjuncts). 
For example, the HAVING
+     * clause of the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2 where 
t2.c1 = t1.c1
+     *         having count(*) <= t1.c1 - 7 and count(*) >= 0)
+     *
+     * is split into count(*) <= t1.c1 - 7 (it references t1.c1, so it is the 
correlation filter
+     * of the apply when this rule matches) and count(*) >= 0 (it does not 
reference the outer
+     * query, so it is the filter which still sits between the apply and the 
aggregation and stays
+     * there in the rewritten plan). The two collections are kept apart 
because of that difference,
+     * and the methods which return both of them together (havingPredicates) 
are the ones which only
+     * need to see the whole HAVING clause of the subquery, for example the 
volatility checks.
+     */
+    private static final class CorrelatedAggregatePredicates {
+        /** the correlated conjuncts of the WHERE clause (the filter below the 
aggregation) */
+        private final List<Expression> whereConjuncts = Lists.newArrayList();
+        /** the conjuncts of the HAVING clause which were already pulled into 
the apply */
+        private final List<Expression> aggregatePredicates = 
Lists.newArrayList();
+        /** the conjuncts of the HAVING clause which are still in the plan of 
the subquery */
+        private final Set<Expression> havingConjuncts = 
Sets.newLinkedHashSet();
+
+        private static CorrelatedAggregatePredicates of(LogicalApply<?, ?> 
apply,
+                List<Expression> whereConjuncts, List<LogicalFilter<Plan>> 
filtersAboveTheAggregation) {
+            CorrelatedAggregatePredicates predicates = new 
CorrelatedAggregatePredicates();
+            predicates.whereConjuncts.addAll(whereConjuncts);
+            // Every predicate which was pulled into the apply was pulled from 
the filter which sits
+            // above the aggregate (the HAVING clause of the subquery), so it 
decides which rows of
+            // the aggregation the subquery returns and has to be evaluated 
above the aggregation
+            // of the rewrite. Its provenance cannot be recovered from the 
slots it uses: a
+            // predicate such as t1.c3 = 1 references no aggregation output 
and no inner column but
+            // it still rejects the row of the aggregation.
+            apply.getCorrelationFilter()
+                    .map(ExpressionUtils::extractConjunction)
+                    .orElse(ImmutableList.of())
+                    .forEach(predicates.aggregatePredicates::add);
+            filtersAboveTheAggregation.forEach(filter -> 
predicates.havingConjuncts.addAll(filter.getConjuncts()));
+            return predicates;
+        }
+
+        private List<Expression> domainPredicates() {
+            return ImmutableList.copyOf(whereConjuncts);
+        }
+
+        private List<Expression> havingPredicates() {
+            return ImmutableList.<Expression>builder()
+                    .addAll(havingConjuncts)
+                    .addAll(aggregatePredicates)
+                    .build();
+        }
+
+        private List<Expression> pulledPredicates() {
+            return ImmutableList.copyOf(aggregatePredicates);
+        }
+
+        private Set<Expression> havingConjuncts() {
+            return havingConjuncts;
+        }
+
+        private boolean hasHaving() {
+            return !havingConjuncts.isEmpty() || 
!aggregatePredicates.isEmpty();
+        }
+
+        private boolean hasAggregatePredicates() {
+            return !aggregatePredicates.isEmpty();
+        }
+
+        /**
+         * The correlation slots which the keys of the aggregation have to 
contain: the domains of
+         * two outer rows are the same as soon as the slots their predicates 
use are equal, so
+         * pullUpCorrelatedPredicateByAggregatingOuter groups the aggregation 
of the outer copy by
+         * exactly these slots and pairs every outer row with the aggregation 
of its own key.
+         *
+         * A predicate may read slots of the outer query and slots of the 
subquery, and only the
+         * slots which belong to the outer query (the ones of the correlation 
slots of the apply)
+         * can take the place of the outer rows: the other slots a predicate 
reads are the columns
+         * of the inner side (the new aggregation reads them below itself) or 
the output of the
+         * aggregation itself (the new aggregation computes it), so 
intersecting the slots of the
+         * predicate with the correlation slots keeps exactly the keys. For 
example the domain
+         * predicate and the HAVING predicate of the subquery of
+         *
+         *     select t1.c1 from t1 where exists (select count(*) from t2 
where t2.c1 = t1.c1
+         *         having count(*) <= t1.c1 - 7)
+         *
+         * give the key t1.c1: it is the right operand of the domain predicate 
t2.c1 = t1.c1 and
+         * it appears inside the arithmetic of the HAVING predicate count(*) 
<= t1.c1 - 7 (whose
+         * count(*) is the output of the aggregation), and the aggregation of 
the rewrite is
+         * grouped by it. A subquery may use several correlation slots and a 
slot may appear in any
+         * position of the predicate: the domain predicates t2.c1 = t1.c1 and 
t2.c2 = t1.c2 give
+         * the two keys t1.c1 and t1.c2 (the aggregation is grouped by both of 
them and the join of
+         * the domain compares t2.c1 = key.c1 and t2.c2 = key.c2), while the 
predicate
+         * t2.c1 = t1.c1 + 1 gives the key t1.c1 alone (the join of the domain 
compares
+         * t2.c1 = key.c1 + 1 after the rewrite).
+         */
+        private Set<Slot> keySlots(List<Slot> correlationSlots) {
+            Set<Slot> keys = new LinkedHashSet<>();
+            for (Expression conjunct : domainPredicates()) {
+                addCorrelationSlots(conjunct, correlationSlots, keys);
+            }
+            for (Expression conjunct : aggregatePredicates) {
+                addCorrelationSlots(conjunct, correlationSlots, keys);
+            }
+            return keys;
+        }
+
+        private static void addCorrelationSlots(Expression conjunct, 
List<Slot> correlationSlots, Set<Slot> keys) {
+            for (Slot slot : conjunct.getInputSlots()) {
+                if (correlationSlots.contains(slot)) {
+                    keys.add(slot);
+                }
+            }
+        }
+
+        /**
+         * Whether every predicate can be evaluated by the plan of the 
aggregation, that is if it only
+         * uses the correlation keys, the inner rows, the output of the 
aggregation of the domain and
+         * the output of the aggregates above it (which the predicates 
evaluated above the chain may
+         * read). The predicates which are still in the plan of the subquery 
keep their node, so only
+         * the predicates which were pulled into the apply have to be 
evaluated above the aggregation
+         * of the rewrite.
+         *
+         * The predicates which the earlier rules pulled out of a filter above 
the projection of the
+         * select list may read a column of that projection, which is computed 
above the aggregation
+         * and is not available to the aggregation of the rewrite: for example 
the predicate
+         * x.c2 <= t1.c1 of the subquery of
+         *
+         *     select t1.c1 from t1 where exists (select x.c1 from (select 
count(*) as c1,
+         *         count(*) + 1 as c2 from t2 where t2.c1 = t1.c1) x where 
x.c2 <= t1.c1)
+         *
+         * reads x.c2, which the projection [c1, (c1 + 1) as c2] computes 
above the aggregation,
+         * so this rewrite cannot evaluate the predicate for the keys and 
reports the subquery with
+         * the "Unsupported correlated subquery with grouping and/or 
aggregation" error. A predicate
+         * which reads the output of the aggregation instead (for example x.c1 
<= t1.c1 in the same
+         * subquery) is evaluated without an error.
+         */
+        private boolean isResolvable(LogicalApply<?, ?> apply, TheAggregation 
aggregation,
+                LogicalFilter<Plan> filter) {
+            Set<ExprId> allowed = Sets.newHashSet();
+            apply.getCorrelationSlot().forEach(slot -> 
allowed.add(slot.getExprId()));
+            aggregation.domainAggregation().getOutput().forEach(slot -> 
allowed.add(slot.getExprId()));
+            aggregation.topAggregation().getOutput().forEach(slot -> 
allowed.add(slot.getExprId()));
+            filter.child().getOutput().forEach(slot -> 
allowed.add(slot.getExprId()));
+            return domainPredicates().stream().allMatch(conjunct -> 
allowed.containsAll(conjunct.getInputSlotExprIds()))
+                    && pulledPredicates().stream()
+                            .allMatch(conjunct -> 
allowed.containsAll(conjunct.getInputSlotExprIds()));
+        }
+    }
+
+    /**
+     * Whether the aggregation of the correlated subquery has to be built on 
the outer side.
+     *
+     * The original rewrite puts the inner side of the correlated predicate 
into the group by of the
+     * aggregate, so the HAVING clause of one group is treated as the HAVING 
clause of one outer
+     * row. That is wrong in three cases: this rewrite then pairs every outer 
row with the
+     * aggregation of its own correlation key (LEFT SEMI JOIN / LEFT ANTI JOIN 
of the outer plan
+     * with the aggregation, the aggregation grouped by the key, and a join 
between the keys and
+     * the inner side on the predicates of the domain). The three cases and 
their plans:
+     *
+     * A correlated predicate which is not an equality, eg. the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2
+     *         where t2.c1 < t1.c1 group by t2.c2 having count(*) = 2)
+     *
+     * the inner rows of one outer row are the union of several groups, so 
group wide aggregates
+     * such as count(*) are computed for a part of the domain of the outer row 
only. The domain is
+     * not one group of the aggregation, so the keys are joined with the inner 
side and the keys of
+     * the outer rows without any matching inner row have no row (the join of 
the domain is an
+     * INNER JOIN, the domain of an empty key does not exist):
+     *
+     *     LEFT SEMI JOIN (t1.c1 <=> key.c1)
+     *       |-- t1
+     *       +-- Filter(count(*) = 2)
+     *             +-- Aggregate(group by [key.c1, t2.c2], count(*))
+     *                   +-- INNER JOIN (t2.c1 < key.c1)
+     *                         |-- Aggregate(group by [t1.c1])            [the 
keys of t1]
+     *                         |     +-- t1
+     *                         +-- t2
+     *
+     * A global aggregate (no group by) whose HAVING clause holds for an empty 
input, eg. the
+     * subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2
+     *         where t2.c1 = t1.c1 having count(*) = 0)
+     *
+     * a global aggregate returns one row for every outer row, including the 
outer rows without any
+     * matching inner row, and that row disappears when the inner side of the 
correlated predicate
+     * becomes the group by key. The keys are joined with the inner side by a 
LEFT OUTER JOIN,
+     * which gives the aggregation one row for the keys without inner rows, 
and the marker of that
+     * row is null, so the counts count the marker instead of the rows:
+     *
+     *     LEFT SEMI JOIN (t1.c1 <=> key.c1)
+     *       |-- t1
+     *       +-- Filter(count(marker) = 0)
+     *             +-- Aggregate(group by [key.c1], count(marker))
+     *                   +-- LEFT OUTER JOIN (t2.c1 = key.c1)              
[keeps the empty domain]
+     *                         |-- Aggregate(group by [t1.c1])            [the 
keys of t1]
+     *                         |     +-- t1
+     *                         +-- Project([true as marker, t2.c1])
+     *                               +-- t2
+     *
+     * A HAVING clause which references the outer query, eg. the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2
+     *         where t2.c1 = t1.c1 having count(*) <= t1.c1 - 7)
+     *
+     * the row kept by that HAVING clause is the one of the domain of the 
outer row, so it cannot
+     * be evaluated on a group of the inner side when the domain is empty. The 
predicate is
+     * evaluated above the new aggregation, with the correlation key in place 
of the outer slot:
+     *
+     *     LEFT SEMI JOIN (t1.c1 <=> key.c1)
+     *       |-- t1
+     *       +-- Project([key.c1])
+     *             +-- Filter(count(marker) <= key.c1 - 7)
+     *                   +-- Aggregate(group by [key.c1], count(marker))
+     *                         +-- LEFT OUTER JOIN (t2.c1 = key.c1)
+     *                               |-- Aggregate(group by [t1.c1])      [the 
keys of t1]
+     *                               |     +-- t1
+     *                               +-- Project([true as marker, t2.c1])
+     *                                     +-- t2
+     *
+     * The same three cases apply to an IN subquery, which compares the outer 
expression with the
+     * value of the aggregation: the row of an empty correlated domain has to 
exist for that
+     * comparison as well. For example the subquery of
+     *
+     *     select t1.c1 from t1 where t1.c1 in (select count(*) from t2 where 
t2.c1 = t1.c1)
+     *
+     * is a global aggregate without a HAVING clause, and the value of the 
empty correlated domain
+     * (the count 0, which the IN compares the outer row with instead of 
dropping it) has to be
+     * produced for every outer row; the subquery of
+     *
+     *     select t1.c1 from t1 where t1.c1 in (select count(*) from t2
+     *         where t2.c1 < t1.c1 having count(*) = t1.c1)
+     *
+     * has a correlated predicate which is not an equality and a HAVING clause 
which references the
+     * outer query, so its aggregation is built on the outer side as well.
+     *
+     * An EXISTS/NOT EXISTS subquery whose aggregation of the domain is global 
is built on the outer
+     * side when an aggregate above that aggregation groups the rows it reads: 
the aggregation of the
+     * domain returns the row of the empty input of the outer rows whose 
correlated domain is empty
+     * (the count 0 of the count(*) below), and the aggregation above it 
groups that row, so its group
+     * exists for those outer rows as well. For example the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from
+     *         (select count(*) c from t2 where t2.c1 = t1.c1) x group by c)
+     *
+     * is true for every outer row, while the aggregation of the inner side 
produces no row for the
+     * keys whose rows below the aggregation of the domain are missing (the 
group above it disappears
+     * with the row of the key). The whole EXISTS is true as well when the 
aggregation above is global
+     * (see the top level scalar aggregate of SubExprAnalyzer), so the 
aggregates above decide on the
+     * EXISTS here.
+     *
+     * A scalar subquery reads the output of the aggregation of the correlated 
domain of one outer
+     * row, and the join which pairs the outer row with that aggregation (the 
left outer join of
+     * ScalarApplyToJoin, or the join this rewrite builds) keeps the groups of 
the inner side whose
+     * key is the value of the outer row: that is the aggregation of the 
domain of the outer row
+     * exactly when every correlated predicate is an equality between the 
outer side and the inner
+     * side, eg. t2.c1 = t1.c1. Every other predicate is reported by the join 
of the scalar
+     * subquery (ScalarApplyToJoin accepts an equality alone, see its guard), 
so the aggregation of
+     * the domain has to be built on the outer side: for example the scalar 
subquery of
+     *
+     *     select t1.c1, (select count(*) from t2 where t2.c1 < t1.c1) from t1
+     *
+     * is evaluated by the aggregation of the outer side (its plan is the plan 
of the EXISTS
+     * subquery above with the LEFT SEMI JOIN replaced by a LEFT OUTER JOIN, 
which keeps the outer
+     * rows whose aggregation has no row and returns null for them).
+     */
+    private static boolean needCorrelatedAggregationOnOuter(LogicalApply<?, ?> 
apply, TheAggregation aggregation,
+            List<Expression> correlatedPredicate, 
CorrelatedAggregatePredicates predicates) {
+        LogicalAggregate<?> agg = aggregation.domainAggregation();
+        // The subqueries which are aggregated on the outer side whatever 
their correlated predicates
+        // say, because the rewrite of the outer side evaluates those 
predicates itself: the plan
+        // below is a set of domains, one for every correlation key, and the 
correlated predicates
+        // are the conditions of the join which pairs a domain with the inner 
rows (see
+        // pullUpCorrelatedPredicateByAggregatingOuter), so they do not have 
to be split into an
+        // outer side and an inner side. An EXISTS subquery which has to keep 
the nodes above its
+        // aggregation (they decide on the rows of the subquery) and a global 
aggregate whose HAVING
+        // clause references the outer query (the row of the empty correlated 
domain exists as well)
+        // are those subqueries; for the others, a predicate whose sides mix 
the outer query and the
+        // subquery keeps the error of the original rewrite (see 
isSupportedCorrelatedConjuncts).
+        if (!apply.isScalar()) {
+            if (keepsNodesAboveTheAggregation(apply, aggregation)) {
+                // an EXISTS subquery which has to keep such a node cannot be 
evaluated on the
+                // groups of the inner side (see keepsNodesAboveTheAggregation)
+                return true;
+            }
+            if (predicates.hasAggregatePredicates() && 
agg.getGroupByExpressions().isEmpty()) {
+                // a predicate of the HAVING clause which references the outer 
query decides whether
+                // the row of a global aggregate is kept, and that row exists 
for every outer row
+                // including the rows of an empty correlated domain, so the 
predicate cannot be
+                // evaluated on a group of the inner side (eg. the IN subquery 
of select t1.c1
+                // from t1 where t1.c1 in (select count(*) from t2 where t2.c1 
= t1.c1 having
+                // count(*) <= t1.c1 - 7))
+                return true;
+            }
+        }
+        if (!isSupportedCorrelatedConjuncts(correlatedPredicate, 
apply.getCorrelationSlot())) {
+            // every kind of subquery keeps the error of the original rewrite 
for these predicates
+            return false;
+        }
+        if (theEmptyInputOfAGlobalAggregateIsObservable(apply, aggregation, 
predicates)
+                && keepsTheRowOfAnEmptyDomain(apply, aggregation, predicates)) 
{
+            // The aggregation of the original subquery produces one row for a 
correlation key whose
+            // rows below the aggregation of the domain are missing, and every 
aggregate above that
+            // aggregation is global: the aggregation of the outer side keeps 
such a row as well (it
+            // marks the row which it keeps for the key, so the aggregates 
above it return the values of
+            // an empty input, see 
pullUpCorrelatedPredicateByAggregatingOuter), while the aggregation
+            // of the inner side drops the key entirely (it adds the keys to 
the group by of every
+            // aggregate, see withTheKeysInTheGroupBy).
+            return true;
+        }
+        if (apply.isScalar()) {
+            // The left outer join of a scalar subquery pairs the outer row 
with the groups of the
+            // inner side whose key is the value of the outer row, which is 
the aggregation of the
+            // domain of the outer row exactly when every correlated predicate 
is an equality
+            // between the outer side and the inner side (eg. t2.c1 = t1.c1). 
Every other comparison
+            // (eg. t2.c1 < t1.c1, or t2.c1 <=> t1.c1 whose domain contains 
the inner rows of the
+            // null key) is evaluated by the aggregation of the outer side: 
the join of the scalar
+            // subquery reports those predicates (ScalarApplyToJoin admits an 
equality alone, see
+            // its guard).
+            if 
(!isEqualityBetweenTheOuterSideAndTheInnerSide(correlatedPredicate)) {
+                return true;
+            }
+            if (!agg.getGroupByExpressions().isEmpty()) {
+                // the domain of an equality correlated predicate is exactly 
one group of the
+                // aggregation: the rows of the subquery for one outer row are 
the rows of that
+                // group, and the outer rows of an empty correlated domain 
have no group at all (the
+                // subquery returns no row for them, whatever its HAVING 
clause says)
+                return false;
+            }
+            // A global aggregation returns a row for the outer rows of an 
empty correlated domain as
+            // well: the left outer join of the original rewrite keeps a null 
for the output of the
+            // subquery of those rows, which is the value of the aggregation 
of that domain (see
+            // keepsTheValueOfAnEmptyDomain), but a HAVING clause decides on 
the row of the empty
+            // domain instead, and a row of the empty domain exists whatever 
its predicate says. The
+            // aggregation of the domain is built on the outer side for those 
subqueries as well.
+            if (predicates.hasHaving()) {
+                return havingMayHoldWithEmptyInput(agg, 
predicates.havingConjuncts());
+            }
+            return !keepsTheValueOfAnEmptyDomain(agg);
+        }
+        if (apply.isExist() && !predicates.hasHaving()) {
+            // an EXISTS/NOT EXISTS subquery without a HAVING clause only 
depends on the existence of
+            // the aggregation result, which is kept by grouping the inner 
side; an IN subquery
+            // compares the value of the aggregation instead, so the row which 
a global aggregate
+            // returns for an empty correlated domain (eg. select t1.c1 from 
t1 where t1.c1 in
+            // (select count(*) from t2 where t2.c1 = t1.c1) compares the 
outer row with the count
+            // 0 of its empty domain) has to be produced for it as well
+            if (agg.getGroupByExpressions().isEmpty()) {
+                // The aggregation of the domain is global, so the subquery 
computes one row for every
+                // outer row, the outer rows whose correlated domain is empty 
included (the count 0 of
+                // the count(*) of the subquery of
+                //
+                //     select t1.c1 from t1 where exists (select count(*) from
+                //         (select count(*) c from t2 where t2.c1 = t1.c1) x 
group by c)
+                //
+                // for example), and the aggregates above the aggregation of 
the domain group that
+                // row: the group of the count 0 above has one row as well, so 
the EXISTS reports
+                // every outer row. The whole EXISTS is folded when the top 
aggregate of the subquery
+                // is global (see the top level scalar aggregate of 
SubExprAnalyzer), so the
+                // aggregates above the aggregation of the domain decide on 
the EXISTS here, and the
+                // row which the aggregation of the domain returns for an 
empty domain is the rows
+                // below those aggregates. The aggregation of the outer side 
keeps that row (the
+                // aggregation of the domain returns the value of its empty 
input for it, see
+                // pullUpCorrelatedPredicateByAggregatingOuter), while the 
rewrite of the inner side
+                // adds the keys to the group by of every aggregate of the 
chain: the key whose rows
+                // below the aggregation of the domain are missing has no row 
at all then, the groups
+                // above it disappear with it, and the semi join drops the 
outer row of that key (a
+                // NOT EXISTS reports that row instead).
+                return true;
+            }
+            return false;
+        }
+        // The IN subqueries and the EXISTS/NOT EXISTS subqueries which have a 
HAVING clause: the
+        // rows of the subquery for one outer row are the rows of the 
aggregation of its domain as
+        // long as the correlated predicates map the domain of every outer row 
onto one group of the
+        // aggregate, so that the HAVING clause of that group is the HAVING 
clause of the outer row.
+        if (hasDomainPredicateWhichBreaksThePartitionOfTheGroups(predicates, 
apply)) {
+            // the domain of one outer row is the union of several groups of 
the aggregate
+            return true;
+        }
+        if (predicates.hasAggregatePredicates()) {
+            // the domain of one outer row is exactly one group and the rows 
of the subquery are the
+            // rows of that group, so the original rewrite is still equivalent
+            return false;
+        }
+        if (!agg.getGroupByExpressions().isEmpty()) {
+            // an equality correlated predicate maps the domain of every outer 
row onto exactly one
+            // group
+            return false;
+        }
+        // The IN subqueries and the EXISTS/NOT EXISTS subqueries which have a 
HAVING clause: the
+        // row which a global aggregate returns for an empty correlated domain 
exists for every
+        // outer row, so it has to be produced whenever a HAVING clause may 
hold for it. An IN
+        // subquery without a HAVING clause needs that row as well, whatever 
the value which the
+        // aggregate returns for an empty input is: the conversion of the IN 
into a join reports the
+        // null of the inner side (the sum of an empty domain, for example) 
like a domain without a
+        // row, which turns the not in of an outer row without a match into 
true instead of the
+        // unknown which the null of the empty domain produces.
+        return havingMayHoldWithEmptyInput(agg, predicates.havingConjuncts());
+    }
+
+    /**
+     * Whether every correlated predicate is an equality between the outer 
side and the inner side,
+     * eg. t2.c1 = t1.c1 or t2.abs(c1) = t1.c1 (the caller has already checked 
that one side of
+     * every predicate is built from the outer query alone and the other one 
from the subquery
+     * alone). Those are the predicates for which the rows of the domain of 
one outer row are
+     * exactly one group of the inner side, which is what the left outer join 
of a scalar subquery
+     * pairs the outer row with.
+     *
+     * The null safe equality is the counterexample: t2.c1 <=> t1.c1 is an 
equal predicate as well,
+     * but its domain contains the inner rows whose key is null, which the 
condition of the left
+     * outer join of ScalarApplyToJoin cannot express (that join admits an 
EqualTo alone). The
+     * scalar subquery of
+     *
+     *     select nq_o.k, (select count(*) from nq_i where nq_i.k <=> nq_o.k) 
from nq_o
+     *
+     * is evaluated by the aggregation of the outer side, whose join of the 
domain compares the
+     * inner key with the key of the outer row by the null safe equality, so 
that the inner rows
+     * whose key is null are part of the domain of the outer row whose key is 
null.
+     */
+    private static boolean 
isEqualityBetweenTheOuterSideAndTheInnerSide(List<Expression> 
correlatedPredicate) {
+        for (Expression conjunct : correlatedPredicate) {
+            if (!(conjunct instanceof EqualTo)) {
+                // t2.c1 <=> t1.c1 is an EqualPredicate as well but not an 
EqualTo: its domain
+                // contains the inner rows whose key is null, which the 
condition of the left outer
+                // join of ScalarApplyToJoin cannot express (that join admits 
an EqualTo alone)
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Whether the null which the original rewrite keeps for the outer rows of 
an empty correlated
+     * domain is the value which the subquery has for that domain. The scalar 
subquery exposes the
+     * output of its aggregation, and the left outer join of the original 
rewrite reports a null for
+     * it:
+     *
+     * - an aggregate of the nullable family returns a null for an empty 
input, which is the value
+     *   the subquery has for the empty domain;
+     *
+     * - SubqueryToApply wraps the aggregates which return a value of their 
own for an empty input
+     *   (the count 0 of select count(*) from t2 where t2.c1 = t1.c1, the 
empty array of an
+     *   array_agg, ...) with an nvl on that value, and the nvl turns the null 
of the join into the
+     *   value of the aggregation of the empty domain. The count(*)/any_value 
wrapper which it adds
+     *   around the select list of a scalar subquery is not part of the value: 
its any_value is null
+     *   for the rows of the join, which is the value of the empty domain;
+     *
+     * - an aggregate which is neither of those (a UDAF, whose value for an 
empty input is written
+     *   in the UDAF itself) keeps the null of the join as well: 
SubqueryToApply adds an nvl for the
+     *   aggregates above alone (see addNvlForScalarSubqueryOutput), so no 
value of a UDAF replaces
+     *   the null which the join reports for it.
+     *
+     * An aggregate which declares a not null result without being wrapped by 
that nvl (an agg_state
+     * combinator, see UnionCombinator) has a value of its own for an empty 
input instead, so the
+     * aggregation of the subquery has to be built on the outer side for it. 
For example the max of
+     * the subquery of
+     *
+     *     select t1.c1, (select max(t2.c2) from t2 where t2.c1 = t1.c1) from 
t1
+     *
+     * keeps the aggregation on the inner side (the null which the left outer 
join of
+     * ScalarApplyToJoin keeps for the outer rows without a group is the value 
of the empty
+     * domain), and so do the count of the subquery of
+     *
+     *     select t1.c1, (select count(*) from t2 where t2.c1 = t1.c1) from t1
+     *
+     * (SubqueryToApply wraps that count as ifnull(count(*), 0) in the 
projection of the left outer
+     * join, which turns the null of the join into the count 0 of the empty 
domain) and the UDAF of
+     * the subquery of
+     *
+     *     select t1.c1, (select my_udaf(t2.c2) from t2 where t2.c1 = t1.c1) 
from t1
+     *
+     * (SubqueryToApply adds no nvl for a UDAF, so the null of the join is the 
value which the
+     * subquery exposes for the empty domain).
+     */
+    private static boolean keepsTheValueOfAnEmptyDomain(LogicalAggregate<?> 
aggregate) {
+        Set<AggregateFunction> functions = Sets.newLinkedHashSet();
+        for (NamedExpression output : aggregate.getOutputExpressions()) {
+            
functions.addAll(output.collect(AggregateFunction.class::isInstance));
+        }
+        // the null of the join is the value of the empty domain for every 
aggregate whose result is
+        // nullable, and SubqueryToApply repairs it for the aggregates which 
return a value of their
+        // own for an empty input; only the aggregates which declare a not 
null result without such
+        // a repair need the value of their own, which the aggregation of the 
outer side produces
+        return functions.stream().allMatch(function -> !(function instanceof 
AlwaysNotNullable)
+                || function instanceof NotNullableAggregateFunction);
+    }
+
+    /**
+     * Whether a filter which sits directly above the aggregation below it 
selects the rows of the
+     * domain of one outer row, instead of deciding which rows of the 
aggregation below it survive
+     * (the HAVING clause of that aggregation). For example the WHERE clause 
of the subquery of
+     *
+     *     select t1.c1 from t1 where t1.c2 >
+     *         (select col from (select c2 as col from t2 group by c2) tt 
where t1.c1 = tt.col)
+     *
+     * was pushed into the derived table, so it sits above the aggregation of 
that table and reads
+     * the columns of its output. The aggregation below such a filter already 
produces the rows
+     * which the outer row has to be paired with, while the aggregates above 
it have to group those
+     * rows by the correlation key.
+     *
+     * A predicate whose inner side is the value of an aggregation decides on 
the rows of that
+     * aggregation instead (the HAVING clause of the subquery): an aggregation 
cannot group by the
+     * value of another aggregation, so such a filter keeps the chain of the 
aggregation.
+     */
+    private static boolean selectsTheRowsOfTheDomain(LogicalFilter<Plan> 
filter, LogicalApply<?, ?> apply) {
+        Map<Boolean, List<Expression>> split =
+                Utils.splitCorrelatedConjuncts(filter.getConjuncts(), 
apply.getCorrelationSlot());
+        List<Expression> correlatedConjuncts = split.get(true);
+        if (correlatedConjuncts.isEmpty()) {
+            // a filter which does not read a column of the outer query cannot 
select the domain of
+            // an outer row
+            return false;
+        }
+        if (correlatedConjuncts.stream()
+                .anyMatch(conjunct -> !(conjunct instanceof BinaryExpression)
+                        && !(conjunct instanceof Not && conjunct.child(0) 
instanceof BinaryExpression))) {
+            // the inner side of such a predicate cannot be read (see 
Utils.getUnCorrelatedExprs)
+            return false;
+        }
+        for (Expression innerSide : 
Utils.getUnCorrelatedExprs(correlatedConjuncts, apply.getCorrelationSlot())) {
+            if (innerSide.anyMatch(AggregateFunction.class::isInstance)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Whether one of the predicates of the domain of the outer rows 
partitions the inner rows of
+     * one outer row into several groups of the aggregate (see 
breaksDomainPartition).
+     */
+    private static boolean 
hasDomainPredicateWhichBreaksThePartitionOfTheGroups(
+            CorrelatedAggregatePredicates predicates, LogicalApply<?, ?> 
apply) {
+        return predicates.domainPredicates().stream()
+                .anyMatch(conjunct -> breaksDomainPartition(conjunct, 
apply.getCorrelationSlot()));
+    }
+
+    /**
+     * Whether every correlated conjunct is a comparison which the rewrites 
can split into an outer
+     * side and an inner side, see isSupportedCorrelatedComparison: those are 
the
+     * predicates which the original rewrite groups the inner side by, and the 
predicates of which
+     * the rewrite of the outer side replaces the outer side by a correlation 
key.
+     */
+    private static boolean isSupportedCorrelatedConjuncts(
+            List<Expression> correlatedPredicate, List<Slot> correlationSlots) 
{
+        for (Expression conjunct : correlatedPredicate) {
+            if (!isSupportedCorrelatedComparison(conjunct, correlationSlots)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Whether this predicate changes the domain of an outer row in a way 
which is not a group of
+     * the aggregate. Only an equality between the outer side and the inner 
side partitions the
+     * inner rows of one outer row into exactly the groups of the aggregate, 
while a predicate which
+     * does not reference the outer query at all just filters the inner rows: 
in the subquery of
+     * select t1.c1 from t1 where exists (select count(*) from t2 where t2.c1 
< t1.c1 group by
+     * t2.c2 having count(*) = 2) the domain of one outer row is the union of 
the groups (t2.c1,
+     * t2.c2), while the predicate t2.c1 = t1.c1 maps it onto exactly one 
group.
+     */
+    private static boolean breaksDomainPartition(Expression conjunct, 
List<Slot> correlationSlots) {
+        if (conjunct instanceof EqualPredicate) {
+            return false;
+        }
+        return 
conjunct.getInputSlots().stream().anyMatch(correlationSlots::contains);
+    }
+
+    /**
+     * Whether the correlated predicate is a comparison whose sides do not mix 
the outer query and
+     * the subquery, eg. t2.c1 < t1.c1 or t1.c1 = t2.abs(c1). Those are the 
predicates which can be
+     * evaluated by joining the two sides, and the ones supported by the 
original rewrite.
+     *
+     * A comparison whose one side mixes the outer query and the subquery is 
not supported, for
+     * example t2.c1 = t1.c1 + t2.c2 (its right side reads the outer slot 
t1.c1 and the inner
+     * column t2.c2, so the two sides cannot be joined apart): the aggregation 
keeps the behavior
+     * of the original rewrite and the subquery is reported with the 
"Unsupported correlated
+     * subquery with correlated predicate t2.c1 = t1.c1 + t2.c2" error. The 
same comparison with a
+     * side which is built from the outer query alone is supported, for 
example t2.c1 = t1.c1 + 1
+     * (the key is t1.c1 and the domain is joined on t2.c1 = key.c1 + 1).
+     */
+    private static boolean isSupportedCorrelatedComparison(Expression 
conjunct, List<Slot> correlationSlots) {
+        Expression predicate = conjunct;
+        if (predicate instanceof Not && predicate.child(0) instanceof 
BinaryExpression) {
+            predicate = predicate.child(0);
+        }
+        if (!(predicate instanceof BinaryExpression)) {
+            return false;
+        }
+        Expression left = ((BinaryExpression) predicate).left();
+        Expression right = ((BinaryExpression) predicate).right();
+        Set<Slot> leftSlots = left.getInputSlots();
+        Set<Slot> rightSlots = right.getInputSlots();
+        boolean correlatedToLeft = !leftSlots.isEmpty() && 
leftSlots.stream().allMatch(correlationSlots::contains)
+                && rightSlots.stream().noneMatch(correlationSlots::contains);
+        boolean correlatedToRight = !rightSlots.isEmpty() && 
rightSlots.stream().allMatch(correlationSlots::contains)
+                && leftSlots.stream().noneMatch(correlationSlots::contains);
+        return correlatedToLeft || correlatedToRight;
+    }
+
+    /**
+     * Rewrite the subquery which has a correlated predicate below its 
aggregation into a join whose
+     * right side aggregates the outer rows together with their correlated 
inner rows, so that the
+     * aggregation of one outer row is the aggregation of exactly the inner 
rows satisfying the
+     * correlated predicate. An EXISTS/NOT EXISTS subquery is decided by that 
aggregation, which is
+     * read through a semi/anti join: for example the subquery of select t1.c1 
from t1 where exists
+     * (select count(*) from t2 where t2.c1 < t1.c1 having count(*) = 0). An 
IN subquery compares
+     * the outer expression with the value of that aggregation and a scalar 
subquery exposes it as
+     * a value: those keep the left outer join of their apply (see the end of 
this method), for
+     * example the subquery of select t1.c1, (select count(*) from t2 where 
t2.c1 < t1.c1) from t1.
+     *
+     * The right side of the join is built around two references of the outer 
plan, because the
+     * domains of two outer rows are the same as soon as their correlation 
slots are equal: the
+     * original outer plan is the left side of the semi join, and a deep copy 
of it (with its own
+     * slots and relation ids) computes the distinct correlation keys. Those 
keys are joined with
+     * the inner side on the predicates of the domain, and the result is 
aggregated again, this
+     * time grouped by the correlation keys together with the group by columns 
of the subquery: the
+     * aggregation of one correlation key is the aggregation of the subquery 
for the outer rows
+     * which own that key. The filter of the HAVING clause sits above that 
aggregation, exactly as
+     * it sat above the aggregation of the subquery, and the predicates of the 
HAVING clause which
+     * reference the outer query (an earlier rule pulled them into the apply) 
are evaluated above
+     * it as well, so that they decide on the aggregation of the whole domain 
of an outer row, the
+     * empty domain included. The outer rows are then paired with the 
aggregation of their
+     * correlation key by a LEFT SEMI JOIN (a LEFT ANTI JOIN when the subquery 
is NOT EXISTS), by a
+     * LEFT OUTER JOIN when the subquery is a scalar subquery, or by the 
correlation filter of the
+     * apply when it is an IN subquery.
+     *
+     * A grouped aggregate returns no row at all for an empty correlated 
domain, so a plain inner
+     * join between the distinct keys and the inner side reproduces the 
behaviour of the subquery.
+     * A global aggregate returns exactly one row for every outer row instead, 
the empty domain
+     * included, so the rows of an empty correlated domain have to be kept: a 
left outer join gives
+     * the aggregation one row whose inner columns are null, which is the same 
input the subquery
+     * aggregates for an empty input. Only the count aggregations tell no row 
and one row of nulls
+     * apart, and count(*) also counts the kept row itself, so the counts are 
replaced by counts of
+     * the projected marker column, which is null for the rows which were kept 
for an empty
+     * correlated domain.
+     *
+     * @return null if this rewrite cannot be applied safely, the caller then 
reports the subquery
+     *         as unsupported
+     */
+    private static Plan 
pullUpCorrelatedPredicateByAggregatingOuter(LogicalApply<?, ?> apply,
+            TheAggregation aggregation, List<Expression> unCorrelatedPredicate,
+            CorrelatedAggregatePredicates predicates) {
+        LogicalAggregate<?> agg = aggregation.domainAggregation();
+        LogicalFilter<Plan> filter = aggregation.domainFilter();
+        Set<Slot> correlationSlots = 
predicates.keySlots(apply.getCorrelationSlot());
+        if (containsSensitiveExpression(apply.left(), correlationSlots)
+                || hasNonDeterministicRows(apply.left())
+                || containsNoneMovableFunction(apply.right())
+                || containsSensitiveSubqueryExpression(apply, predicates)
+                || referencesOuterSlot(apply.right(), 
ImmutableSet.copyOf(predicates.whereConjuncts),
+                        apply.getCorrelationSlot())
+                || !predicates.isResolvable(apply, aggregation, filter)) {
+            return null;
+        }
+
+        // the domains of two outer rows are the same as soon as their 
correlation slots are equal,
+        // so the correlation slots are the only outer information the 
aggregation needs
+        LogicalPlan outer = (LogicalPlan) apply.left();
+        LogicalPlan outerCopy = LogicalPlanDeepCopier.INSTANCE.deepCopy(outer, 
new DeepCopierContext());
+        List<Slot> outerOutput = outer.getOutput();
+        List<Slot> outerCopyOutput = outerCopy.getOutput();
+        Preconditions.checkState(outerOutput.size() == outerCopyOutput.size(),
+                "the deep copy of the outer plan changed its output size");
+        Set<ExprId> outerExprIds = 
outerOutput.stream().map(Slot::getExprId).collect(ImmutableSet.toImmutableSet());
+        for (Slot copySlot : outerCopyOutput) {
+            if (outerExprIds.contains(copySlot.getExprId())) {
+                // the deep copier does not always separate the slots of the 
copy from the plan it
+                // copied (eg. LogicalTVFRelation.withRelationId reuses the 
logical properties of the
+                // relation): the join which pairs an outer row with its 
correlation key would then
+                // compare a slot with itself and keep every outer row
+                return null;
+            }
+        }
+        Map<Expression, Expression> slotToKey = Maps.newLinkedHashMap();
+        for (Slot slot : correlationSlots) {
+            int index = outerOutput.indexOf(slot);
+            if (index < 0) {
+                return null;
+            }
+            slotToKey.put(slot, outerCopyOutput.get(index));
+        }
+        List<NamedExpression> keyExpressions = new 
ArrayList<>(slotToKey.size());
+        for (Expression key : slotToKey.values()) {
+            keyExpressions.add((NamedExpression) key);
+        }
+        LogicalAggregate<Plan> keyAggregate = new LogicalAggregate<>(
+                ImmutableList.copyOf(slotToKey.values()), keyExpressions, 
outerCopy);
+
+        Plan inner = 
PlanUtils.filterOrSelf(ImmutableSet.copyOf(unCorrelatedPredicate), 
filter.child());
+        // An aggregate with a group by returns no row at all for an empty 
correlated domain, so the
+        // inner join below reproduces the behaviour of the subquery, which 
has no group to report.
+        // A global aggregate returns exactly one row for every outer row 
instead, the empty domain
+        // included, so the rows of an empty correlated domain have to be 
kept: the left outer join
+        // gives the aggregate one row whose inner columns are null, which is 
the same input the
+        // subquery aggregates for an empty input. Only the count aggregations 
tell "no row" and "one
+        // row of nulls" apart, and count(*) also counts the kept row itself, 
so the counts are
+        // replaced by counts of the projected marker, which is null for the 
rows which were kept for
+        // an empty correlated domain.
+        boolean keepsTheRowOfAnEmptyDomain = keepsTheRowOfAnEmptyDomain(apply, 
aggregation, predicates);
+        boolean keepEmptyDomain = agg.getGroupByExpressions().isEmpty() || 
keepsTheRowOfAnEmptyDomain;
+        Slot matchMarker = null;
+        // The left outer join below reports the columns of the side which it 
fills with nulls as
+        // nullable (see JoinUtils.getJoinOutput): the expressions above it 
have to read the nullable
+        // versions of those columns, because a reference to a not-nullable 
column of the inner side
+        // reaches the join from below it and makes AdjustNullable convert 
that reference, and that
+        // conversion is reported as an error while fe_debug is set. For 
example the subquery of
+        //
+        //     select * from t1 where t1.k1 =
+        //         (select sum(k1) from t3 where t1.k1 != t3.v1 and t3.v2 = 2)
+        //
+        // aggregates k1 of t3, a column which is declared not null, and the 
row which the left outer
+        // join keeps for an empty correlated domain turns the aggregation of 
the subquery into
+        // sum(if(marker, k1, null)), whose argument has to be the nullable k1 
which that join
+        // produces:
+        //
+        //     Aggregate(group by [key.k1], output [sum(if(marker, k1, null)) 
AS sum(k1), key.k1])
+        //       +-- LEFT OUTER JOIN (t3.v1 != key.k1 AND t3.v2 = 2)    [keeps 
the empty domain]
+        //             |-- Aggregate(group by [t1.k1], output [t1.k1])
+        //             |     +-- t1
+        //             +-- Project([true AS marker, t3.k1, t3.k2, t3.k3, 
t3.v1, t3.v2])
+        //                   +-- Filter(t3.v2 = 2)
+        //                         +-- t3
+        Map<Expression, Expression> nullableInnerSlots = Maps.newHashMap();
+        if (keepEmptyDomain) {
+            Alias marker = new Alias(BooleanLiteral.TRUE, 
CORRELATION_MATCH_MARKER);
+            // the marker is null for the rows which the left outer join keeps 
for an empty
+            // correlated domain, so the join reports it as a nullable column 
for the same reason
+            matchMarker = marker.toSlot().withNullable(true);
+            List<NamedExpression> projects = Lists.newArrayList(marker);
+            projects.addAll(inner.getOutput());
+            inner = new LogicalProject<>(projects, inner);
+            for (Slot slot : inner.getOutput()) {
+                if (!slot.nullable()) {
+                    nullableInnerSlots.put(slot, slot.withNullable(true));
+                }
+            }
+        }
+
+        List<Expression> domainConjuncts = 
predicates.domainPredicates().stream()
+                .map(conjunct -> ExpressionUtils.replace(conjunct, slotToKey))
+                .collect(ImmutableList.toImmutableList());
+        // a grouped aggregate reports an empty domain as "no row", a global 
aggregate as "one row"
+        Plan domainJoin = new LogicalJoin<>(keepEmptyDomain ? 
JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN,
+                ExpressionUtils.EMPTY_CONDITION, domainConjuncts, new 
DistributeHint(DistributeType.NONE),
+                Optional.empty(), keyAggregate, inner, null);
+
+        List<Expression> havingPredicates = predicates.havingPredicates();
+        Set<AggregateFunction> aggregates = Sets.newLinkedHashSet();
+        if (keepEmptyDomain) {
+            // the aggregation of the rewrite is computed over the rows of the 
inner side plus the
+            // row which the left outer join keeps for an empty correlated 
domain, and only the
+            // aggregates which ignore null arguments see that row as an empty 
input
+            for (Expression expression : agg.getOutputExpressions()) {
+                
aggregates.addAll(expression.collect(AggregateFunction.class::isInstance));
+            }
+            for (Expression conjunct : havingPredicates) {
+                
aggregates.addAll(conjunct.collect(AggregateFunction.class::isInstance));
+            }
+            if (aggregates.stream().anyMatch(function -> !(function instanceof 
NullIgnoringAggregateFunction))) {
+                // an aggregate which keeps null arguments cannot tell the row 
kept for an empty
+                // correlated domain from a row of the inner side
+                return null;
+            }
+        }
+        Map<Expression, Expression> compensated = 
guardAggregateArguments(aggregates, matchMarker);
+        if (compensated == null) {
+            // an aggregate of the subquery cannot be guarded, so the 
aggregation of the outer side
+            // cannot tell the row which was kept for an empty correlated 
domain apart (see
+            // guardAggregateArguments)
+            return null;
+        }
+
+        List<Expression> newGroupBy = Lists.newArrayList(slotToKey.values());
+        for (Expression groupByExpression : agg.getGroupByExpressions()) {
+            // The left outer join of the domain reports the columns of the 
inner side as nullable
+            // (see nullableInnerSlots), and a reference to a not nullable 
column of the inner side
+            // reaches the join from below it and makes AdjustNullable convert 
that reference, which
+            // is reported as an error while fe_debug is set: the group by of 
the new aggregation
+            // reads the columns of the join, so it reads the nullable 
versions.
+            newGroupBy.add(ExpressionUtils.replace(groupByExpression, 
nullableInnerSlots));
+        }
+        List<NamedExpression> newOutputs = Lists.newArrayList();
+        for (NamedExpression output : agg.getOutputExpressions()) {
+            newOutputs.add((NamedExpression) ExpressionUtils.replace(
+                    ExpressionUtils.replace(output, compensated), 
nullableInnerSlots));
+        }
+        // the keys are appended after the outputs of the subquery: the first 
column of the output is
+        // the value which an IN subquery compares (eg. the count which k in 
(select count(*) ...)
+        // compares the outer value with) and the value which a scalar 
subquery exposes, and neither
+        // of them may move
+        newOutputs.addAll(keyExpressions);
+        if (keepsTheRowOfAnEmptyDomain) {
+            // The aggregates above the aggregation of the domain have to tell 
the row which is kept
+            // for an empty domain apart from the rows below them, so that 
they return the values of an
+            // empty input for it (see withTheKeysInTheGroupBy), and the 
filters between those
+            // aggregations let the row pass (see rebuildTheAggregationChain): 
the aggregation of the
+            // domain exposes the marker of that row up to them. The marker is 
the same for every row of
+            // one correlation key, so the grouping of the rows of a key does 
not change.
+            newGroupBy.add(matchMarker);
+            newOutputs.add(matchMarker);
+        }
+        LogicalAggregate<Plan> newAggregate = new 
LogicalAggregate<>(newGroupBy, newOutputs, domainJoin);
+
+        // the predicates which were pulled into the apply are not part of the 
plan of the subquery
+        // any more: they were evaluated on the rows of the old aggregation 
and have to be evaluated
+        // on the rows of the new one, directly above the aggregate of the 
domain they were pulled
+        // from when the subquery does not wrap that aggregate (otherwise they 
are evaluated above
+        // the whole chain, whose aggregates are the ones their columns belong 
to)
+        Set<Expression> movedPredicates = Sets.newLinkedHashSet();
+        for (Expression conjunct : predicates.pulledPredicates()) {
+            // the predicates which were pulled into the apply are evaluated 
above the join as well, so
+            // they read the columns of the inner side through the nullable 
slots of the join
+            movedPredicates.add(ExpressionUtils.replace(
+                    ExpressionUtils.replace(ExpressionUtils.replace(conjunct, 
compensated), slotToKey),
+                    nullableInnerSlots));
+        }
+        // The aggregates above the aggregation of the domain read the rows of 
the subquery for the
+        // correlation key of an outer row, and the row which the rewrite 
keeps for a key whose rows
+        // below that aggregation are missing has to be ignored by them 
exactly when the subquery
+        // produces no row for the empty domain of that key: a grouped 
aggregation of the domain reports
+        // an empty domain as "no row" (the row which the rewrite keeps for it 
builds a group of its
+        // own), and the row which a global aggregation of the domain returns 
for an empty domain is
+        // removed by a HAVING clause which does not hold for it. Otherwise 
the kept row is the row
+        // which the original subquery computes out of the empty input, and 
the aggregates above the
+        // aggregation of the domain return the value of that row (the count 0 
of
+        // select (select count(*) from t2 where t2.c1 = t1.c1 having count(*) 
= 0) from t1, for
+        // example).
+        boolean ignoresTheKeptRowOfAnEmptyDomain = matchMarker != null
+                && (!agg.getGroupByExpressions().isEmpty()
+                        || !havingMayHoldWithEmptyInput(agg, 
Sets.newLinkedHashSet(havingPredicates)));
+        Map<LogicalAggregate<?>, Plan> newAggregations = new 
IdentityHashMap<>();
+        for (LogicalAggregate<?> aggregate : aggregation.aggregationChain()) {
+            if (aggregate == agg) {
+                newAggregations.put(aggregate, movedPredicates.isEmpty() || 
!aggregation.onlyTheAggregationOfTheDomain()
+                        ? newAggregate
+                        : new LogicalFilter<>(movedPredicates, newAggregate));
+                continue;
+            }
+            // the aggregates above the deepest one read the rows it produces, 
so they keep the rows
+            // of one correlation key together as well (the keys are appended 
to their output, so
+            // that the aggregate above them can group by them)
+            LogicalAggregate<?> withTheKeys = 
withTheKeysInTheGroupBy(aggregate, keyExpressions,
+                    keepsTheRowOfAnEmptyDomain ? matchMarker : null,
+                    aggregate != aggregation.topAggregation(), 
nullableInnerSlots,
+                    ignoresTheKeptRowOfAnEmptyDomain);
+            if (withTheKeys == null) {
+                // an aggregate above the aggregation of the domain cannot be 
guarded, so the row which
+                // is kept for an empty domain would contribute to it
+                return null;
+            }
+            newAggregations.put(aggregate, withTheKeys);
+        }
+        // the nodes above the aggregation of the subquery (the HAVING clause, 
the filters over the
+        // projection of the select list, that projection) are kept as they 
are: they are evaluated on
+        // the rows of the new aggregation, which are the rows of the 
aggregation of the subquery for
+        // the correlation key of one outer row
+        Set<Slot> keysToExpose = 
keyExpressions.stream().map(NamedExpression::toSlot)
+                .collect(ImmutableSet.toImmutableSet());
+        Set<LogicalFilter> filtersWhichKeepTheRowOfAnEmptyDomain = 
ImmutableSet.of();
+        if (keepsTheRowOfAnEmptyDomain) {
+            // The filters between the aggregation of the domain and the 
aggregation above it read the
+            // marker of the row which is kept for an empty domain, so that 
the row passes them (see
+            // rebuildTheAggregationChain). The projections below the top 
aggregation carry the marker
+            // up to the aggregates above the aggregation of the domain, which 
guard their arguments
+            // with it, while the projections above the top aggregation are 
not read by a node which
+            // needs the marker (the marker is not an output of the subquery).
+            filtersWhichKeepTheRowOfAnEmptyDomain =
+                    theFiltersBetweenTheAggregations(aggregation);
+        }
+        // the predicates of the apply are evaluated on the nodes above the 
aggregation of the
+        // subquery, which produce the outputs of that aggregation themselves, 
so no output of it has
+        // to be appended to the projections below them
+        Plan newRight = rebuildTheAggregationChain(apply.right(), aggregation, 
newAggregations, keysToExpose,
+                ImmutableSet.of(), keepsTheRowOfAnEmptyDomain ? matchMarker : 
null,
+                filtersWhichKeepTheRowOfAnEmptyDomain, false, 
nullableInnerSlots);
+        if (!movedPredicates.isEmpty() && 
!aggregation.onlyTheAggregationOfTheDomain()) {
+            newRight = new LogicalFilter<>(movedPredicates, newRight);
+        }
+
+        // the aggregate of the subquery is now computed for the correlation 
keys of every outer row,
+        // so the outer rows which own one of those groups are the rows for 
which the subquery has rows
+        List<Expression> backConjuncts = new ArrayList<>(slotToKey.size());
+        for (Map.Entry<Expression, Expression> entry : slotToKey.entrySet()) {
+            backConjuncts.add(new NullSafeEqual(entry.getKey(), 
entry.getValue()));
+        }
+        if (apply.isScalar()) {
+            // A scalar subquery exposes the output of the aggregation of its 
correlated domain, and
+            // its value is read from the first column of the right side (the 
keys are appended
+            // after the outputs of the subquery): the join which pairs an 
outer row with the
+            // aggregation of its own key is the left outer join which 
ScalarApplyToJoin builds for
+            // the scalar subqueries it can unnest itself, with the null safe 
equality which pairs
+            // the outer row with the key of its own aggregation as its 
condition (the join of
+            // ScalarApplyToJoin only admits a plain equality, see the guard 
there), and the value
+            // of the outer rows whose aggregation has no row is the null of 
the outer side. For
+            // example the scalar subquery of
+            //
+            //     select t1.c1, (select count(*) from t2 where t2.c1 < t1.c1) 
from t1
+            //
+            // becomes
+            //
+            //     LEFT OUTER JOIN (t1.c1 <=> key.c1)
+            //       |-- t1
+            //       +-- Aggregate(group by [key.c1], count(marker) as 
count(*))
+            //             +-- LEFT OUTER JOIN (t2.c1 < key.c1)              
[keeps the empty domain]
+            //                   |-- Aggregate(group by [t1.c1])            
[the keys of t1]
+            //                   |     +-- t1
+            //                   +-- Project([true as marker, t2.c1, t2.c2])
+            //                         +-- t2
+            return new LogicalJoin<>(apply.isNeedAddSubOutputToProjects()
+                    ? JoinType.LEFT_OUTER_JOIN : JoinType.LEFT_SEMI_JOIN,
+                    ExpressionUtils.EMPTY_CONDITION, backConjuncts, new 
DistributeHint(DistributeType.NONE),
+                    apply.getMarkJoinSlotReference(), outer, newRight, null);
+        }
+        if (!apply.isExist()) {
+            // an IN/NOT IN subquery keeps its apply: the correlation filter 
pairs an outer row with
+            // the aggregation of its own key (the aggregation of the domain 
of the outer row, the
+            // empty domain included), and the rule which converts the IN into 
a join (InApplyToJoin)
+            // compares the outer expression with the first column of the 
output of the aggregation
+            return new LogicalApply<>(apply.getCorrelationSlot(), 
apply.getSubqueryType(), apply.isNot(),
+                    apply.getCompareExpr(), apply.getTypeCoercionExpr(),
+                    ExpressionUtils.optionalAnd(backConjuncts), 
apply.getMarkJoinSlotReference(),
+                    apply.isNeedAddSubOutputToProjects(), 
apply.isMarkJoinSlotNotNull(), outer, newRight);
+        }
+        return new LogicalJoin<>(apply.isNot() ? JoinType.LEFT_ANTI_JOIN : 
JoinType.LEFT_SEMI_JOIN,
+                ExpressionUtils.EMPTY_CONDITION, backConjuncts, new 
DistributeHint(DistributeType.NONE),
+                apply.getMarkJoinSlotReference(), outer, newRight, null);
+    }
+
+    /**
+     * Replace the arguments of the aggregates so that the row which the left 
outer join keeps for
+     * an empty correlated domain does not contribute to the aggregation: the 
marker of that row is
+     * null, so every argument is null for it and the aggregates which ignore 
null arguments (the
+     * caller only admits those) return the value of an empty input. count(*) 
has no argument to
+     * build that guard on, so it counts the marker instead; a distinct count 
keeps its argument and
+     * its distinct flag, because the null of the kept row must not be counted 
as a value. For
+     * example count(*) becomes count(marker) and sum(1) becomes 
sum(if(marker, 1, null)), which
+     * returns null for the row which was kept for an empty domain.
+     *
+     * An argument which controls the aggregate instead of providing the 
values to aggregate is kept
+     * as it is, because the guard makes the aggregate illegal: the second 
argument of topn_array is
+     * the number of values to keep and has to stay a positive literal. 
Whether an argument controls
+     * the aggregate or provides values is decided by the checks which the 
aggregate runs after the
+     * rewrite and before the type coercion (see 
isLegalWithTheGuardedArgument). For example the
+     * subquery of
+     *
+     *     select (select topn_array(i.v, 2) from i where i.k < o.k) from o
+     *
+     * is rewritten to topn_array(if(marker, i.v, null), 2): the first 
argument is null for the row
+     * which was kept for an empty domain, so topn_array ignores that row and 
returns the value of an
+     * empty input (that is the contract of NullIgnoringAggregateFunction), 
while the guarded form
+     * topn_array(if(marker, i.v, null), if(marker, 2, null)) is rejected by 
the check of
+     * topn_array, which requires the number of values to keep to be a 
literal. An aggregate whose
+     * every argument controls it cannot be given an argument which is null 
for the kept row, so the
+     * caller does not rewrite it.
+     */
+    private static Map<Expression, Expression> guardAggregateArguments(
+            Set<AggregateFunction> aggregates, Slot matchMarker) {
+        Map<Expression, Expression> replace = Maps.newHashMap();
+        for (AggregateFunction function : aggregates) {
+            if (function instanceof Count && ((Count) function).isCountStar() 
&& !function.isDistinct()) {
+                replace.put(function, new Count(matchMarker));
+                continue;
+            }
+            List<Expression> arguments = 
Lists.newArrayListWithCapacity(function.arity());
+            boolean ignoresTheKeptRow = false;
+            for (int index = 0; index < function.arity(); index++) {
+                Expression argument = function.getArgument(index);
+                Expression guarded = new If(matchMarker, argument,
+                        new NullLiteral(argument.getDataType()));
+                if (isLegalWithTheGuardedArgument(function, index, guarded)) {
+                    arguments.add(guarded);
+                    ignoresTheKeptRow = true;
+                } else {
+                    // the argument controls the aggregate: the guard would 
make the aggregate
+                    // illegal, so the argument is kept and the arguments 
which provide the values
+                    // to aggregate make the kept row invisible
+                    arguments.add(argument);
+                }
+            }
+            if (!ignoresTheKeptRow) {
+                // every argument of the aggregate controls it, so the 
aggregate cannot tell the row
+                // which was kept for an empty correlated domain from a row of 
the inner side
+                return null;
+            }
+            replace.put(function, function.withChildren(arguments));
+        }
+        return replace;
+    }
+
+    /**
+     * Whether the aggregate stays legal when the argument at the given 
position is replaced by its
+     * guarded version (see guardAggregateArguments). An aggregate rejects a 
guarded argument when
+     * that argument controls it instead of providing the values to aggregate: 
topn_array requires
+     * the number of values to keep to be a literal (see 
checkLegalityAfterRewrite of TopNArray),
+     * and bitmap_union_int requires its arguments to be constants (see
+     * checkLegalityBeforeTypeCoercion of BitmapUnionInt).
+     */
+    private static boolean isLegalWithTheGuardedArgument(
+            AggregateFunction function, int position, Expression 
guardedArgument) {
+        List<Expression> arguments = 
Lists.newArrayList(function.getArguments());
+        arguments.set(position, guardedArgument);
+        try {
+            AggregateFunction guarded = (AggregateFunction) 
function.withChildren(arguments);
+            guarded.checkLegalityBeforeTypeCoercion();
+            guarded.checkLegalityAfterRewrite();
+            return true;
+        } catch (AnalysisException e) {
+            // the check of the aggregate does not accept the guarded argument
+            return false;
+        }
+    }
+
+    /**
+     * Whether the HAVING clause of a global aggregate can hold for the row 
which the aggregate
+     * returns for an empty input. For example having count(*) = 0 holds for 
an empty input (the row
+     * of the aggregation of the empty domain has to survive), while having 
sum(t2.c1) is not null
+     * rejects that row, so the aggregation of the subquery can stay on the 
inner side.
+     */
+    private static boolean havingMayHoldWithEmptyInput(LogicalAggregate<?> 
agg, Set<Expression> havingConjuncts) {
+        // the having clause usually references the output slots of the 
aggregate, but it may also
+        // contain the aggregate functions themselves, so both of them are 
replaced by the value
+        // which the aggregate returns for an empty input
+        Map<Expression, Expression> emptyValues = Maps.newHashMap();
+        for (NamedExpression output : agg.getOutputExpressions()) {
+            Expression expression = output instanceof Alias ? ((Alias) 
output).child() : output;
+            if (!(expression instanceof AggregateFunction)) {
+                continue;
+            }
+            Expression emptyValue = 
emptyValueForEmptyInput((AggregateFunction) expression);
+            if (emptyValue != null) {
+                emptyValues.put(output.toSlot(), emptyValue);
+                emptyValues.put(expression, emptyValue);
+            }
+        }
+        for (Expression conjunct : havingConjuncts) {
+            for (Expression simpleConjunct : 
ExpressionUtils.extractConjunction(conjunct)) {
+                Expression substituted = emptyValues.isEmpty() ? simpleConjunct
+                        : ExpressionUtils.replace(simpleConjunct, emptyValues);
+                Expression folded = 
FoldConstantRuleOnFE.evaluateWithoutContext(substituted);
+                // A comparison of a null with any other value is null as 
well, which the folding of
+                // the expressions does not evaluate, and the predicate which 
holds it rejects the
+                // row of the empty input: the comparison is replaced by a 
null boolean first.
+                folded = 
FoldConstantRuleOnFE.evaluateWithoutContext(replaceComparisonsWithNull(folded));
+                if (folded instanceof Literal && 
!BooleanLiteral.TRUE.equals(folded)) {
+                    // false or null: this conjunct rejects the row of the 
empty input, no matter what
+                    // the other conjuncts evaluate to, even the ones whose 
value is unknown
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Replace every comparison which has a null operand by a null boolean 
(the null safe equality
+     * is the exception: it evaluates the comparison of a null with a null to 
true and the
+     * comparison of a null with any other value to false).
+     */
+    private static Expression replaceComparisonsWithNull(Expression 
expression) {
+        return expression.rewriteDownShortCircuit(node -> {
+            if (!(node instanceof ComparisonPredicate) || node instanceof 
NullSafeEqual) {
+                return node;
+            }
+            for (Expression child : node.children()) {
+                if (child.isNullLiteral()) {
+                    return NullLiteral.BOOLEAN_INSTANCE;
+                }
+            }
+            return node;
+        });
+    }
+
+    /**
+     * The value which an aggregate function returns for an empty input, or 
null if it cannot be
+     * decided. For example count(*) returns 0 and sum(t2.c1) returns null, 
while the value of an
+     * array_agg(t2.c1) cannot be decided. Neither can the value of a UDAF: it 
is written in the
+     * UDAF itself, so a UDAF whose state starts at 0 (an inline sum, for 
example) returns 0 for an
+     * empty input although its result is declared nullable.
+     */
+    private static Expression emptyValueForEmptyInput(AggregateFunction 
function) {
+        if (function instanceof Count) {
+            return new BigIntLiteral(0);
+        }
+        if (function instanceof Sum || function instanceof Avg
+                || function instanceof Min || function instanceof Max) {
+            // these aggregations return null for an empty input
+            return new NullLiteral(function.getDataType());
+        }
+        // only the aggregations above are known to return a fixed value for 
an empty input
+        return null;
+    }
+
+    /**
+     * Whether the outer plan of the apply computes a value which this rewrite 
may not evaluate
+     * twice.
+     *
+     * The rewrite evaluates the outer plan twice (the original plan on the 
left of the resulting
+     * join, and a deep copy which computes the distinct correlation keys), so 
the two evaluations
+     * have to return the same rows and the same values of the correlation 
keys. The values of the
+     * plan are followed through its slots (see collectVolatileSlots), and a 
volatile value such as
+     * random() is rejected when it decides which rows the plan returns (its 
predicates and its
+     * groupings) and when it contributes to the value of a correlation key. A 
volatile column
+     * which only decorates the output is accepted, because the rewrite reads 
the correlation keys
+     * of the plan alone, and the output of the rewritten plan is the original 
plan.
+     *
+     * - a volatile correlation key is rejected: the outer plan of the query of
+     *
+     *       select t.k from (select random() as k from t1 e) t
+     *       where exists (select count(*) from t2 i where i.k < t.k having 
count(*) = 0)
+     *
+     *   is the plan
+     *
+     *       LogicalProject[k = random()] over LogicalOlapScan(t1)
+     *
+     *   and the slot k of its output is the correlation key, so the two 
evaluations of the plan
+     *   compute different keys: the aggregation of one key would not be the 
aggregation of the
+     *   subquery for the outer rows which own it.
+     *
+     * - a volatile value which decides the rows of the outer plan is rejected 
as well, for example
+     *   the plan of the outer query of
+     *
+     *       select e.k from t1 e
+     *       where random() < 0.5 and exists (select count(*) from t2 i
+     *           where i.k < e.k having count(*) = 0)
+     *
+     *       LogicalFilter[random() < 0.5] over LogicalOlapScan(t1)
+     *
+     *   (collectVolatileSlots returns null for it): the predicate decides 
which rows the two
+     *   evaluations return, and the rows which only one of them returns 
cannot be paired with
+     *   their aggregation.
+     *
+     * - a volatile column which only decorates the output is accepted, for 
example the column r of
+     *   the outer query of
+     *
+     *       select t.k from (select e.k as k, random() as r from t1 e) t
+     *       where exists (select count(*) from t2 i where i.k < t.k having 
count(*) = 0)
+     *
+     *       LogicalProject[k = e.k, r = random()] over LogicalOlapScan(t1)
+     *
+     *   because no predicate, no grouping and no correlation key reads r.
+     *
+     * A NoneMovableFunction (the only implementation today is assert_true) is 
rejected wherever it
+     * appears instead, even in such a decorative output: its evaluation must 
not be duplicated,
+     * because the query has to raise its error where the query writes the 
function, not a second
+     * time in the copy.
+     *
+     * @param correlationKeys the slots of the outer plan whose values are 
consumed as correlation
+     *        keys by the rewrite and by the aggregation it builds
+     */
+    private static boolean containsSensitiveExpression(Plan plan, Set<Slot> 
correlationKeys) {
+        if (containsNoneMovableFunction(plan)) {
+            return true;
+        }
+        Set<Slot> volatileSlots = collectVolatileSlots(plan);
+        return volatileSlots == null || 
volatileSlots.stream().anyMatch(correlationKeys::contains);
+    }
+
+    /**
+     * Whether an expression of the plan is a function whose evaluation must 
not be duplicated.
+     * The subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2 where 
t2.c1 = t1.c1
+     *         having count(*) = 0 and assert_true(t1.c2 > 0, 'positive'))
+     *
+     * is reported with the "Unsupported correlated subquery with grouping 
and/or aggregation"
+     * error: the predicate of the HAVING clause which reads assert_true is 
pulled into the apply
+     * (it references the outer row), and the rewrite would evaluate it once 
for every correlation
+     * key instead of once for every outer row.
+     */
+    private static boolean containsNoneMovableFunction(Plan plan) {
+        for (Expression expression : plan.getExpressions()) {
+            if (expression.containsType(NoneMovableFunction.class)) {
+                return true;
+            }
+        }
+        for (Plan child : plan.children()) {
+            if (containsNoneMovableFunction(child)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * The slots of the plan whose value is computed from a volatile 
expression, at any level of the
+     * plan, or null if a volatile expression contributes to the rows which 
the plan returns.
+     */
+    private static Set<Slot> collectVolatileSlots(Plan plan) {
+        Set<Slot> volatileInput = Sets.newHashSet();
+        for (Plan child : plan.children()) {
+            Set<Slot> volatileChild = collectVolatileSlots(child);
+            if (volatileChild == null) {
+                return null;
+            }
+            volatileInput.addAll(volatileChild);
+        }
+        Set<Slot> volatileSlots = Sets.newHashSet(volatileInput);
+        if (plan instanceof LogicalProject) {
+            volatileSlots.addAll(volatileSlotsOfOutputs(((LogicalProject<?>) 
plan).getProjects(), volatileInput));
+            return volatileSlots;
+        }
+        if (plan instanceof LogicalAggregate) {
+            LogicalAggregate<?> aggregate = (LogicalAggregate<?>) plan;
+            if (usesVolatile(aggregate.getGroupByExpressions(), 
volatileInput)) {
+                // the grouping decides which rows the aggregate returns
+                return null;
+            }
+            
volatileSlots.addAll(volatileSlotsOfOutputs(aggregate.getOutputExpressions(), 
volatileInput));
+            return volatileSlots;
+        }
+        if (plan instanceof LogicalFilter) {
+            if (usesVolatile(((LogicalFilter<?>) plan).getConjuncts(), 
volatileInput)) {
+                // the predicate decides which rows the filter returns
+                return null;
+            }
+            return volatileSlots;
+        }
+        if (plan instanceof LogicalJoin) {
+            LogicalJoin<?, ?> join = (LogicalJoin<?, ?>) plan;
+            if (usesVolatile(join.getHashJoinConjuncts(), volatileInput)
+                    || usesVolatile(join.getOtherJoinConjuncts(), 
volatileInput)
+                    || usesVolatile(join.getMarkJoinConjuncts(), 
volatileInput)) {
+                // the conditions decide which rows the join returns
+                return null;
+            }
+            return volatileSlots;
+        }
+        if (plan instanceof LogicalSort) {
+            // the order of the rows does not change the values of the slots
+            return volatileSlots;
+        }
+        // this rewrite does not know how the other plans compute their output 
from the values below
+        // them, so it cannot prove that a volatile value which reaches one of 
them cannot change the
+        // rows or the correlation keys
+        if (!volatileInput.isEmpty()) {
+            return null;
+        }
+        for (Expression expression : plan.getExpressions()) {
+            if (containsVolatileExpression(expression)) {
+                return null;
+            }
+        }
+        return volatileSlots;
+    }
+
+    /** the slots of the given outputs whose value is computed from one of the 
given volatile slots */
+    private static Set<Slot> volatileSlotsOfOutputs(List<? extends 
NamedExpression> outputs,
+            Set<Slot> volatileInput) {
+        Set<Slot> volatileSlots = Sets.newHashSet();
+        for (NamedExpression output : outputs) {
+            if (usesVolatile(ImmutableList.of(output), volatileInput)) {
+                volatileSlots.add(output.toSlot());
+            }
+        }
+        return volatileSlots;
+    }
+
+    /**
+     * Whether one of the expressions is volatile, directly or through one of 
the given slots.
+     */
+    private static boolean usesVolatile(Collection<? extends Expression> 
expressions, Set<Slot> volatileSlots) {
+        return expressions.stream().anyMatch(expression -> 
containsVolatileExpression(expression)
+                || 
expression.getInputSlots().stream().anyMatch(volatileSlots::contains));
+    }
+
+    /**
+     * Whether the expression contains a volatile expression, at any level of 
it.
+     *
+     * A UDF is reported as a volatile expression, because the planner cannot 
see the code of the
+     * function (see Udf, which extends VolatileExpression), and a UDF does 
not restrict this
+     * rewrite: a UDF which is declared volatile would otherwise forbid the 
rewrite of every
+     * subquery which calls it. For example the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select x.c2 from (select 
count(*) as c,
+     *         my_udf(count(*)) as c2 from t2 where t2.c1 = t1.c1 having 
count(*) = 0) x
+     *         where x.c2 < 0)
+     *
+     * reaches the check with the plan
+     *
+     *     Apply(exists)
+     *       |-- t1
+     *       +-- Filter(x.c2 < 0)
+     *             +-- Project([count(*) as c, my_udf(count(*)) as c2])
+     *                   +-- Filter(count(*) = 0)
+     *                         +-- Aggregate(group by [], output [count(*) as 
c])
+     *                               +-- Filter(t2.c1 = t1.c1)
+     *                                     +-- t2
+     *
+     * whose filter above the HAVING clause reads the column of the UDF, and 
the subquery is
+     * rewritten (the UDF is evaluated once for every correlation key).
+     *
+     * The other volatile expressions still restrict the rewrite (random, uuid 
and the others), for
+     * example the subquery of
+     *
+     *     select t1.c1 from t1 where exists (select count(*) from t2 where 
t2.c1 = t1.c1
+     *         and random() < 0.5 having count(*) = 0)
+     */
+    private static boolean containsVolatileExpression(Expression expression) {
+        return expression.containsType(VolatileExpression.class)
+                && expression.anyMatch(node -> node instanceof 
VolatileExpression
+                        && !(node instanceof Udf)
+                        && ((VolatileExpression) node).isVolatile());
+    }
+
+    /**
+     * Every node of the subquery is evaluated once for every correlation key 
by this rewrite, while
+     * the original subquery evaluates it once for every outer row: two outer 
rows with the same
+     * correlation key share one evaluation, so no value which the result of 
the subquery depends on
+     * may change between two evaluations.
+     *
+     * The volatile values of the subquery are followed through the slots of 
its plan (see
+     * collectVolatileSlots): a volatile value which only decorates an output 
that nothing reads is
+     * accepted, while a volatile value which decides the rows of a node, the 
grouping of the
+     * aggregation or the value of a predicate which decides the result of the 
subquery is rejected.
+     * The predicates which were pulled into the apply are not part of the 
plan of the subquery any
+     * more, so they are checked against the volatile values of the 
aggregation they read as well.
+     * For example the domain predicate ... where t2.c1 = t1.c1 and random() < 
0.5 ..., the grouping
+     * group by random() and a HAVING clause such as having random() > 0 are 
rejected (two outer rows
+     * with the same correlation key would share one evaluation), while the 
value of sum(random()) is
+     * accepted when nothing reads it, for example ... select count(*), 
sum(random()) as c2 from t2
+     * where t2.c1 = t1.c1 having count(*) = 0.
+     */
+    private static boolean containsSensitiveSubqueryExpression(LogicalApply<?, 
?> apply,
+            CorrelatedAggregatePredicates predicates) {
+        for (Expression conjunct : predicates.havingPredicates()) {
+            if (conjunct.containsType(NoneMovableFunction.class)) {
+                // the pulled predicates live on the apply instead of the plan 
of the subquery, whose
+                // functions the caller checks itself
+                return true;
+            }
+        }
+        Set<Slot> volatileSlots = collectVolatileSlots(apply.right());
+        return volatileSlots == null || 
usesVolatile(predicates.havingPredicates(), volatileSlots);
+    }
+
+    /**
+     * Whether the outer plan returns rows which its two evaluations may 
choose differently.
+     *
+     * The rewrite evaluates the outer plan twice (the original plan on the 
left of the resulting
+     * join, and a deep copy which computes the distinct correlation keys), 
and the rows of the two
+     * evaluations have to carry the same correlation keys: the outer rows 
whose key the deep copy
+     * did not produce find no row of the aggregation and are dropped by the 
semi/anti join.
+     *
+     * A sort alone does not change the rows a plan returns, only the operator 
which truncates the
+     * sorted rows can keep different rows in the two evaluations. That 
truncation is deterministic
+     * only when the order keys are a total order on the rows: when the limit 
falls inside a group
+     * of rows which are equal on the order keys, the query semantics allows 
any subset of that
+     * group to be returned, and the two evaluations are two instances of the 
same plan in
+     * different places of the resulting plan, so they can keep rows with 
different correlation
+     * keys. This rule does not prove that the order keys are total, so every 
topn and every limit
+     * is rejected: the outer plan of
+     *
+     *     select t.k from (select e.k as k from t1 e limit 1) t
+     *     where exists (select count(*) from t2 i where i.k < t.k having 
count(*) = 0)
+     *
+     * is the plan
+     *
+     *     LogicalLimit[limit 1] over LogicalProject[k = e.k] over 
LogicalOlapScan(t1)
+     *
+     * (the same plan with a LogicalTopN instead of the LogicalLimit when the 
query writes an order
+     * by, for example select t.k from (select e.k as k from t1 e order by e.k 
limit 1) t where
+     * exists (select count(*) from t2 i where i.k < t.k having count(*) = 
0)), and the limit
+     * without an order returns an arbitrary row: the row which one of the two 
evaluations keeps
+     * can carry another correlation key than the row of the other one.
+     *
+     * A sampled scan is rejected as well, because its two evaluations sample 
different rows: the
+     * scan of
+     *
+     *     select t.k from (select e.k as k from t1 e tablesample(1 rows)) t
+     *     where exists (select count(*) from t2 i where i.k < t.k having 
count(*) = 0)
+     *
+     * is a LogicalOlapScan which carries the table sample.
+     */
+    private static boolean hasNonDeterministicRows(Plan plan) {

Review Comment:
   [P1] Reject non-repeatable correlation keys before duplicating the outer 
plan. This guard catches LIMIT/TOP-N/OLAP sampling and recognized volatile 
expressions, but not ordinary operators that may assign arbitrary values. For 
example, two evaluations of a tied `row_number()` window may swap row numbers, 
and two `any_value(k)` aggregates over `{10,20}` may choose different keys. 
With an empty inner domain, `EXISTS (SELECT count(*) ... HAVING count(*)=0)` 
must keep the outer group for either `any_value` choice; `newRight` is keyed 
only by the copy's value, so the null-safe back join can drop the original 
group even though the ExprIds are disjoint. Reject or track any window, 
arbitrary aggregate, or other non-repeatable output that reaches a correlation 
key unless identical assignments across both evaluations are proven, and add 
tied-window plus `any_value` coverage.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to