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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/FillUpQualifyMissingSlot.java:
##########
@@ -189,10 +287,63 @@ interface PlanGenerator {
         Plan apply(Set<Expression> newConjuncts, List<NamedExpression> 
projects);
     }
 
-    private Plan createPlan(LogicalProject<Plan> project, Set<Expression> 
conjuncts, PlanGenerator planGenerator) {
+    private Plan createPlan(LogicalProject<Plan> project, Set<Expression> 
conjuncts,
+            Optional<Scope> outerScope, PlanGenerator planGenerator) {
         Set<Slot> projectOutputSet = project.getOutputSet();
         List<NamedExpression> newOutputSlots = Lists.newArrayList();
         Set<Expression> newConjuncts = new LinkedHashSet<>();
+
+        // A correlated column referenced in qualify may be hidden behind a 
project alias, e.g.
+        // `QUALIFY f = 1` where f is aliased as an outer column o.flag. If 
the project also
+        // contains a window expression, filter pushdown cannot rewrite f back 
to its producer
+        // before apply decorrelation, so the alias-producer dependency would 
be lost and the
+        // correlation slot would never be collected into the apply. Resolve 
such aliases whose
+        // producers reference only outer correlated slots, so the correlation 
stays visible to
+        // subquery unnesting.
+        Map<Slot, Expression> correlatedAliasToProducer = Maps.newHashMap();
+        // If any qualify conjunct contains a subquery (e.g. an IN/NOT IN 
predicate), the alias
+        // replacement below would descend into the subquery and break the 
apply decorrelation
+        // (the substituted slot would be owned by neither side of the apply). 
Skip the rewrite
+        // in that case.
+        boolean conjunctsHaveSubquery = conjuncts.stream().anyMatch(c -> 
c.containsType(SubqueryExpr.class));

Review Comment:
   [P1] Scope this fence to the subquery-bearing conjunct
   
   This query-wide flag also disables safe alias repair in unrelated conjuncts. 
For example, the right side of an outer EXISTS can contain:
   
   ```text
   Qualify[rn = 1, f = 1, scalarSubquery(j) > 0]
     Project[i.k, o.flag AS f, row_number(...) AS rn]
       Scan(i)
   ```
   
   Because only the scalar conjunct contains a `SubqueryExpr`, replacing `f -> 
o.flag` in the separate `f = 1` conjunct would not cross that subquery's Apply 
boundary. This global check skips the map entirely, however, so the enclosing 
Apply never gets a correlation filter for `o.flag`; `ExistsApplyToJoin` takes 
its uncorrelated path while the right project still consumes the left-only 
slot, and final slot validation fails. Rewrite only subquery-free conjuncts 
while retaining the fence for the unsafe conjunct itself, and add a 
full-pipeline mixed scalar/EXISTS regression.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/FillUpQualifyMissingSlot.java:
##########
@@ -158,23 +222,57 @@ public List<Rule> buildRules() {
                qualify -> having -> project(distinct)
              */
             RuleType.FILL_UP_QUALIFY_HAVING_PROJECT.build(
-                logicalQualify(logicalHaving(logicalProject())).then(qualify 
-> {
+                logicalQualify(logicalHaving(logicalProject())).thenApply(ctx 
-> {
+                    LogicalQualify<LogicalHaving<LogicalProject<Plan>>> 
qualify = ctx.root;
                     checkWindow(qualify);
+                    Optional<Scope> outerScope = 
ctx.cascadesContext.getOuterScope();
                     LogicalHaving<LogicalProject<Plan>> having = 
qualify.child();
                     LogicalProject<Plan> project = qualify.child().child();
-                    return createPlan(project, qualify.getConjuncts(), 
(newConjuncts, projects) -> {
+                    return createPlan(project, qualify.getConjuncts(), 
outerScope, (newConjuncts, projects) -> {
                         ImmutableList<NamedExpression> copyOutput = 
ImmutableList.copyOf(project.getOutput());
                         if (project.isDistinct()) {
+                            // Keep correlated predicates that only depend on 
outer slots together with the
+                            // having's own correlated predicates, on the same 
decorrelatable side of the
+                            // distinct barrier, so subquery unnesting can 
collect and decorrelate them
+                            // together (otherwise one of them is left 
dangling in the apply's right side).
+                            // A predicate that is constant per outer row is 
equivalent before/after
+                            // distinct, so moving it above the distinct 
project preserves semantics.
+                            Set<Expression> newHavingConjuncts = new 
LinkedHashSet<>(having.getConjuncts());
+                            Set<Expression> distinctQualifyConjuncts = new 
LinkedHashSet<>();
+                            if (outerScope.isPresent()) {
+                                Set<Slot> correlatedSlots = 
outerScope.get().getCorrelatedSlots();
+                                for (Expression conjunct : newConjuncts) {
+                                    Set<Slot> inputSlots = 
conjunct.getInputSlots();
+                                    // Only relocate deterministic predicates: 
visible outer slots do
+                                    // not make a predicate constant (e.g. 
`o.flag <> 1 OR random() < 0.5`
+                                    // has only {o.flag} as input slots), and 
moving a volatile
+                                    // predicate changes its evaluation domain 
(per row before DISTINCT
+                                    // vs once after DISTINCT), so keep 
volatile predicates on their
+                                    // original side of the distinct barrier.
+                                    if (!inputSlots.isEmpty() && 
correlatedSlots.containsAll(inputSlots)

Review Comment:
   [P1] Preserve nested subquery dependencies across DISTINCT
   
   `getInputSlots()` deliberately excludes a subquery's inner plan, so this 
containment test can classify a row-dependent subquery predicate as outer-only. 
A reduced case is:
   
   ```text
   Qualify[rn = 1, o.flag = 1 OR EXISTS(j.v = i.not_grouped)]
     ProjectDistinct[i.k]
       Scan(i.k, i.not_grouped)
   ```
   
   The conjunct reports only `{o.flag}` here and is moved into HAVING. 
Bottom-up `SubqueryToApply` must then create the nested EXISTS Apply above 
`ProjectDistinct[i.k]`, where `i.not_grouped` has no owner, so final validation 
rejects the plan. Merely fencing the motion is not sufficient for this 
non-output correlation because the current missing-slot logic also cannot see 
the hidden `i.not_grouped` dependency. Surface the complete nested correlation 
in the pre-DISTINCT project while keeping the predicate in its original 
evaluation domain, or reject this combined shape stably, and add nested 
scalar/EXISTS 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