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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CTEInline.java:
##########
@@ -135,4 +139,36 @@ private boolean 
containsNondeterministicFunction(LogicalCTEProducer<?> producer)
         producer.accept(NondeterministicFunctionCollector.INSTANCE, 
nondeterministicFunctions);
         return !nondeterministicFunctions.isEmpty();
     }
+
+    /**
+     * Return true if the CTE producer's subtree is a LogicalOneRowRelation 
and its
+     * final output slots are all compile-time constants.
+     */
+    private static boolean isConstantOneRowProducer(Plan producerRoot) {
+        if (!(producerRoot instanceof LogicalCTEProducer)) {
+            return false;
+        }
+        Plan node = ((LogicalCTEProducer<?>) producerRoot).child();
+        while (node instanceof LogicalSubQueryAlias) {
+            if (node.arity() != 1) {
+                return false;
+            }
+            node = node.child(0);
+        }
+        if (node instanceof LogicalProject) {
+            LogicalProject<?> project = (LogicalProject<?>) node;
+            if (project.arity() != 1) {
+                return false;
+            }
+            if (!ExpressionUtils.allMatch(project.getProjects(), 
Expression::isConstant)) {

Review Comment:
   [P1] Classify the canonical production producer
   
   Production runs `NORMALIZE_PLAN_JOBS` before this custom rewrite. It changes 
a simple producer into `Project(c.a, c.b) -> OneRow`, and the direct-join 
example has no `LogicalApply`, so no later `MergeProjectable` removes that 
forwarding project. These `SlotReference` projects return false from 
`Expression.isConstant()`, leaving the exact positive case materialized. 
Resolving only those slots is still insufficient: `SELECT 1 WHERE TRUE` and 
`SELECT 1 LIMIT 1` retain `Filter(TRUE)`/`Limit(1)` wrappers here because their 
eliminators run only after the inline decision. The tests hide these shapes by 
invoking `CTEInline` directly after analysis. Please classify a 
canonical/simplified producer and cover the registered production path via 
`rewrite()`.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CTEInline.java:
##########
@@ -95,6 +98,7 @@ public Plan visitLogicalCTEAnchor(LogicalCTEAnchor<? extends 
Plan, ? extends Pla
                 LogicalCTEProducer<?> cteProducer = (LogicalCTEProducer<?>) 
cteAnchor.left();
                 if (connectContext.getSessionVariable().enableCTEMaterialize
                         && (consumers.size() > 
connectContext.getSessionVariable().inlineCTEReferencedThreshold
+                        && !isConstantOneRowProducer(cteProducer)

Review Comment:
   [P2] Keep runtime-only constant expressions materialized
   
   `Expression.isConstant()` is broader than the compile-time constants 
described here. For example, `WITH c AS (SELECT sleep(1) AS s) SELECT (SELECT s 
FROM c), (SELECT s FROM c)` reaches a one-row producer after scalar-subquery 
project merging; `sleep(1)` is deterministic, so this helper returns true and 
the nondeterministic check does not block it. BE constant folding deliberately 
skips `sleep`, and materialization evaluates the producer once and multicasts 
it, whereas `visitLogicalCTEConsumer` now deep-copies and executes it for each 
reference. Immutable non-foldable UDFs have the same problem. Please restrict 
this exception to expressions proven cheap/pure and compile-time-backed, and 
retain materialization for runtime-only constants.



##########
fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CTEInlineTest.java:
##########
@@ -119,4 +128,69 @@ public void 
refreshCteConsumersAfterNormalizeEliminatesEmptyBranch() {
             connectContext.getSessionVariable().setDisableNereidsRules("");
         }
     }
+
+    @Test
+    public void testConstantOneRowCteAlwaysInlined() {
+        String sql = "WITH c AS (SELECT 1 AS a, 'x' AS b) "
+                + "SELECT * FROM c c1 JOIN c c2 ON c1.a = c2.a";
+
+        LogicalPlan analyzed = (LogicalPlan) PlanChecker.from(connectContext)
+                .analyze(sql)
+                .applyCustom(new PullUpCteAnchor())
+                .applyCustom(new CTEInline())
+                .getPlan();
+
+        assertNoCteNodes(analyzed);
+    }
+
+    @Test
+    public void testConstantOneRowCteWithManyConsumersInlined() {
+        String sql = "WITH consts AS ("
+                + "  SELECT '2026-01-01' AS day_start, '2026-08-17' AS 
day_end, "
+                + "         '2025-01-01' AS tq_start,  '2025-08-17' AS tq_end)"
+                + "SELECT * FROM consts c1, consts c2, consts c3, consts c4, "
+                + "              consts c5, consts c6, consts c7, consts c8";
+
+        LogicalPlan analyzed = (LogicalPlan) PlanChecker.from(connectContext)
+                .analyze(sql)
+                .applyCustom(new PullUpCteAnchor())
+                .applyCustom(new CTEInline())
+                .getPlan();
+
+        assertNoCteNodes(analyzed);
+    }
+
+    @Test
+    public void testTableBackedCteNotForcedInline() {
+        String sql = "WITH t AS (SELECT id, score FROM T1) "
+                + "SELECT * FROM t x JOIN t y ON x.id = y.id";
+
+        PlanChecker.from(connectContext)
+                .analyze(sql)
+                .applyCustom(new PullUpCteAnchor())
+                .applyCustom(new CTEInline())
+                .matches(logicalCTEAnchor());
+    }
+
+    @Test
+    public void testNonDeterministicOneRowCteNotForcedInline() {
+        String sql = "WITH r AS (SELECT RANDOM() AS x) "
+                + "SELECT * FROM r r1 JOIN r r2 ON r1.x = r2.x";
+
+        PlanChecker.from(connectContext)
+                .analyze(sql)
+                .applyCustom(new PullUpCteAnchor())
+                .applyCustom(new CTEInline())
+                .matches(logicalCTEAnchor());
+    }
+
+    private static void assertNoCteNodes(LogicalPlan plan) {
+        plan.foreach(p -> {
+            if (p instanceof LogicalCTEAnchor || p instanceof 
LogicalCTEProducer) {

Review Comment:
   [P2] Also reject leftover CTE consumers
   
   Inlining removes the anchor separately from replacing each 
`LogicalCTEConsumer`. If the consumer traversal or CTE-id match regresses, the 
plan can contain orphan consumers while this helper still passes because it 
checks only anchors and producers. Include `LogicalCTEConsumer` in this 
assertion so these tests actually prove that all logical CTE artifacts were 
eliminated.



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