englefly commented on code in PR #69111:
URL: https://github.com/apache/doris/pull/69111#discussion_r4297720517


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java:
##########
@@ -115,9 +152,15 @@ public Plan visitLogicalHaving(LogicalHaving<? extends 
Plan> having, CascadesCon
         }
     }
 
+    /** Whether this expression is an output of a CTE producer body, whose 
exprId has to stay stable. */
+    private boolean isCteProducerOutput(Expression expr) {
+        return expr instanceof Slot && 
cteProducerOutputExprIds.contains(((Slot) expr).getExprId());
+    }
+
     @Override
     public Plan visitLogicalProject(LogicalProject<? extends Plan> project, 
CascadesContext context) {
         project = visitChildren(this, project, context);
+        project = (LogicalProject<? extends Plan>) 
exprIdRewriter.rewriteExpr(project, replacedExprIds);

Review Comment:
   Fixed in `9e1c57d1f0a`.
   
   Confirmed the mechanism: `ExprIdRewriter` drove its rules through 
`ExpressionRewrite#bottomUp`, and `ExpressionBottomUpRewriter` keeps a 
`RequiresShortCircuitEvaluation` subtree opaque 
(`ExpressionBottomUpRewriter.java:79-83`), while `MergeUtils.selectBranch` 
wraps every MERGE assignment in a `ShortCircuitIf` (`MergeUtils.java:44-47`). A 
slot inside the guarded branch therefore kept the replaced exprId.
   
   `ExprIdRewriter` now replaces exprIds **structurally** instead of through 
the bottom-up expression rewriter: it rewrites the children of every expression 
first and then applies the id replacement to the node, so the guarded branches 
are entered as well. Nothing is evaluated — replacing an exprId only rebuilds 
the expression, which is exactly why the short-circuit guard must not stop it. 
The change is local to `ExprIdRewriter` (`ExprIdRewriter.java:50-67`, 
`ReplaceRule#replaceExprIds`), the `ReplaceRule` used by the group-key rules is 
unchanged, and the expression-level behaviour for unguarded expressions is 
identical (children first, same `lastExprId` chain, same object identity when 
nothing is mapped).
   
   Regression test `ExprIdRewriterTest#testGuardedBranchIsRewritten` builds 
`Alias(#9, ShortCircuitIf(v#3 = 1, k#1 + 1, k#1))` and asserts that no 
reference to `k#1` survives and `k#2` is present. It fails before this commit 
with
   
   ```text
   if((v#3 = 1), (k#1 + 1), k#1) ==> expected: <false> but was: <true>
   ```
   
   Full MERGE planning still needs a MERGE-capable mocked env, so the gate is 
at the expression tree the rewrite produces.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/RewriteCteChildren.java:
##########
@@ -99,22 +109,138 @@ public Plan visitLogicalCTEAnchor(LogicalCTEAnchor<? 
extends Plan, ? extends Pla
             
cascadesContext.addPlanProcesses(outerCascadesCtx.getPlanProcesses());
             
cascadesContext.getStatementContext().getRewrittenCteConsumer().put(cteAnchor.getCteId(),
 outer);
         }
+        Set<LogicalCTEConsumer> cteConsumers = collectCteConsumers(outer, 
cteAnchor.getCteId());
+        cascadesContext.getCteIdToConsumers().put(cteAnchor.getCteId(), 
cteConsumers);
+        if (cteConsumers.isEmpty()) {
+            return outer;
+        }
+        List<Slot> oldProducerOutputs = cteAnchor.child(0).getOutput();
+        Plan producer = cteAnchor.child(0).accept(this, cascadesContext);
+        Map<ExprId, ExprId> producerExprIdRemap = 
computeProducerExprIdRemap(oldProducerOutputs, producer.getOutput());
+        if (!producerExprIdRemap.isEmpty()) {
+            // The producer subtree is rewritten in its own context, so a rule 
of it may rename the exprIds of the
+            // producer outputs, while the consumers, which are rewritten 
before the producer, still map to the
+            // previous exprIds. Refresh both maps of every consumer of this 
CTE with the new producer slots.
+            Map<ExprId, Slot> producerOutputSlots = 
producer.getOutput().stream()
+                    .collect(Collectors.toMap(Slot::getExprId, 
Function.identity()));

Review Comment:
   Fixed in `9e1c57d1f0a`.
   
   Reproduced with your shape: the producer output of `WITH c AS (SELECT k, v, 
v, count(*) cnt, v + 1 z FROM t WHERE k = 5 GROUP BY k, v) SELECT l.*, r.* FROM 
c l CROSS JOIN c r` keeps the repeated `v#1` (both positions are the same 
producer slot and `LogicalCTEConsumer` is multimap based), and the 
`Collectors.toMap` lookup threw before the refresher ran:
   
   ```text
   java.lang.IllegalStateException: Duplicate key 1 (attempted merging values 
v#1 and v#1)
   ```
   
   The lookup now keeps the first entry per exprId 
(`RewriteCteChildren.java:121-128`), which is the same producer slot, so the 
refresh works for such a CTE. Regression test 
`ConstantPropagationFreshExprIdTest#testMaterializedCteWithRepeatedOutputSlot` 
asserts the plan rewrites, the consumer maps stay valid and the query stays 
plannable; the failure above is what it reports before this commit.
   
   Two related notes from the same review round:
   
   * your `RewriteCteChildren.java:180` finding is obsolete at this head - the 
name/lineage matcher it refers to was replaced by the published remap, and both 
constant group keys are remapped through it (`ConstantPropagation` publishes 
every renamed exprId of the producer body);
   * the fresh exprId branch of `replaceNameExpressionConstants` that the PR 
body mentioned was unreachable (the method only receives an `Alias`, and 
replacing the constants of an `Alias` yields an `Alias` again, which returns at 
the guard above it), so it is removed rather than kept as untested code.



-- 
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