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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java:
##########
@@ -218,11 +256,16 @@ public Plan visitLogicalAggregate(LogicalAggregate<? 
extends Plan> aggregate, Ca
         }
 
         if (newGroupByExprs.equals(oldGroupByExprs) && 
newOutputExprs.equals(oldOutputExprs)) {
+            // the aggregate is not changed, the plan above keeps referencing 
the output slots of the aggregate,
+            // so neither the constant project nor its fresh exprIds are used
             return aggregate;
         }
 
         aggregate = aggregate.withGroupByAndOutput(newGroupByExprs, 
newOutputExprs);
         if (containsConstantOutput) {
+            for (Pair<ExprId, ExprId> freshExprId : freshExprIds) {
+                replacedExprIds.put(freshExprId.first, freshExprId.second);

Review Comment:
   Fixed in `fe4ae780bbf`.
   
   Confirmed the stale maps first: with only the fresh-ID change, the reduced 
plan of your shape is
   
   ```text
   PRODUCER out=[k#10, v#1, s#3]
   CONSUMER RelationId#0 out=[k#4, v#5, s#6]
     c2p={k#4=k#0, v#5=v#1, s#6=s#3}      <- k#0 is not an output of the 
producer any more
     p2c={k#0=[k#4], v#1=[v#5], s#3=[s#6]}
   ```
   
   so the consumer maps were indeed left stale. Two notes on the failure mode 
though: in this shape `PlanChecker.checkPlannerResult` still succeeded, i.e. I 
could not reproduce a planning failure (the physical CTE wiring does not read 
those maps for this query), but the logical plan is inconsistent and the maps 
are used by CBO (`CascadesContext.addCTEConsumerGroup`), so it has to be fixed 
regardless.
   
   I took the "preserve the boundary's output contract" option rather than 
carrying the remap across the boundary: the consumers are rewritten *before* 
the producer and in a different `CascadesContext` 
(`RewriteCteChildren.newSubtreeContext`), so the producer invocation has no way 
to rebuild them. The rule now detects a producer body through 
`CascadesContext.getCurrentTree()` (present only for the producer side) and 
does not rename the exprIds of the producer outputs:
   
   * a constant grouping key which is a producer output keeps its grouping key, 
and its output stays the aggregate slot (no constant project, no mapping),
   * a project item which is a producer output is not replaced.
   
   Everything below the producer output keeps the fresh-exprId treatment, and 
the shadow that motivated the PR (RQG case 11000258898, no CTE) is unaffected.
   
   Regression test 
`ConstantPropagationFreshExprIdTest#testMaterializedCteConsumerMapsStayValid` 
asserts that every producer slot a consumer map points to is an output of the 
producer and that the query stays plannable. It fails before this commit with
   
   ```text
   the consumer map of RelationId#2 references k#0 which the producer does not 
output
   ```
   
   A materialized-CTE physical planning case (`checkPlannerResult` on `WITH c 
AS (SELECT k, v, count(*) cnt FROM t WHERE k = 5 GROUP BY k, v) SELECT ... FROM 
c l CROSS JOIN c r`) is part of the same test.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagation.java:
##########
@@ -295,15 +339,18 @@ public Plan visitLogicalSink(LogicalSink<? extends Plan> 
sink, CascadesContext c
         // // the select will add a parent plan: result sink. the result sink 
contains a output slot reference, and its
         // // data type is varchar(30),  but if replace the slot reference 
with a varchar literal '1',
         // // then the data type info varchar(30) will lost, because varchar 
literal '1' data type is always varchar(1),
-        // // so t's column will get a error type. so we don't rewrite logical 
sink then.
+        // // so t's column will get a error type. so we don't replace the 
slots of the sink by literals.
         // Pair<ImmutableEqualSet<Slot>, Map<Slot, Literal>> childEqualTrait
         //         = getChildEqualSetAndConstants(sink, context);
         // List<NamedExpression> newOutputExprs = 
sink.getOutputExprs().stream()
         //         .map(expr ->
         //                 replaceNameExpressionConstants(expr, context, 
childEqualTrait.first, childEqualTrait.second))
         //         .collect(ImmutableList.toImmutableList());
         // return newOutputExprs.equals(sink.getOutputExprs()) ? sink : 
sink.withOutputExprs(newOutputExprs);
-        return sink;
+        // The sink still has to follow the fresh exprIds of the constants 
materialized below it, otherwise it
+        // would reference a slot which its child does not output any more. 
Only the exprId is remapped here, so
+        // the name and the data type declared by the sink are kept.
+        return (LogicalSink<? extends Plan>) exprIdRewriter.rewriteExpr(sink, 
replacedExprIds);

Review Comment:
   Fixed in `fe4ae780bbf`.
   
   You are right that `ExpressionRewrite.buildRules()` has no 
`LogicalBlackholeSink` rule, and checking the dispatch it is not only 
blackhole: `logicalExternalRowLevelDeleteSink`, `logicalJdbcTableSink` / 
`logicalMaxComputeTableSink` (they have their own `PlanType`, so 
`logicalConnectorTableSink()` does not match them) and the unbound sinks are 
not covered either. So instead of adding one rule per sink type, 
`ExprIdRewriter.rewriteExpr` now has a sink-agnostic fallback: when no 
registered rule matched a `LogicalSink`, its `outputExprs` are rewritten 
through the same `ExpressionRuleExecutor` and applied with `withOutputExprs`. 
Every sink type, including future ones, therefore follows the fresh exprIds of 
its child, while the sink metadata (name, data type, connector/table 
properties) is kept — the fallback only remaps ids, exactly like the existing 
per-type rules do.
   
   Regression test 
`ConstantPropagationTest#testBlackholeSinkFollowsFreshExprId` builds the 
warm-up shape (`LogicalBlackholeSink` over `aggregate(group by id, gender) -> 
filter(id = 5) -> scan`) and asserts that
   
   * every output expression of the sink is an id its child really outputs, and
   * the constant column is refreshed to the fresh exprId of the constant 
project.
   
   It fails before this commit with
   
   ```text
   the sink expects id#0 which its child does not output: [sum_age#7, gender#1, 
id#8]
   ```
   
   Building an actual `WARM UP SELECT` statement would need the cloud mirror 
environment, so the test constructs the same logical shape the warm-up 
statement produces.



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