This is an automated email from the ASF dual-hosted git repository.
englefly pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new c75eba5488f [fix](fe) Prune empty and cascading CTE plans (#62828)
c75eba5488f is described below
commit c75eba5488f893f0679682dfabc088eef60ccfc8
Author: minghong <[email protected]>
AuthorDate: Tue May 19 10:05:49 2026 +0800
[fix](fe) Prune empty and cascading CTE plans (#62828)
### What problem does this PR solve?
Issue Number: None
Related PR: #60601
Problem Summary: Fix optimizer-side CTE pruning so empty-relation
producers, zero-consumer anchors, and cascading inline opportunities are
normalized to a fixpoint before memoization, and add regression coverage
for empty and cascading CTE elimination.
---
.../org/apache/doris/nereids/StatementContext.java | 11 ++
.../doris/nereids/jobs/executor/Optimizer.java | 78 ++++++++++++--
.../doris/nereids/rules/rewrite/CTEInliner.java | 86 +++++++++++++++-
.../nereids/rules/rewrite/ClearContextStatus.java | 5 +-
.../java/org/apache/doris/qe/SessionVariable.java | 2 +-
.../doris/nereids/rules/rewrite/CTEInlineTest.java | 38 +++++++
.../unique_function/agg_with_unique_function.out | 8 +-
.../query_p0/repeat/test_repeat_output_slot.out | 28 ++---
.../query_p0/cte/test_cbo_cte_inline_prune.groovy | 113 +++++++++++++++++++++
9 files changed, 327 insertions(+), 42 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
index 3dbf1fc02c7..2af64752cc4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/StatementContext.java
@@ -667,6 +667,17 @@ public class StatementContext implements Closeable {
return rewrittenCteConsumer;
}
+ /** Clear CTE-related rewrite and memo state before rebuilding it from a
new plan tree. */
+ public void clearCteEnvironment() {
+ cteIdToConsumers.clear();
+ cteIdToOutputIds.clear();
+ cteIdToProducer.clear();
+ consumerIdToFilters.clear();
+ cteIdToConsumerGroup.clear();
+ rewrittenCteProducer.clear();
+ rewrittenCteConsumer.clear();
+ }
+
/**
* Snapshot current CTE-related environment for temporary
rewrite/optimization.
*/
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Optimizer.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Optimizer.java
index 9d7f95970c4..b66a6a30c60 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Optimizer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/executor/Optimizer.java
@@ -18,6 +18,7 @@
package org.apache.doris.nereids.jobs.executor;
import org.apache.doris.nereids.CascadesContext;
+import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.jobs.cascades.DeriveStatsJob;
import org.apache.doris.nereids.jobs.cascades.OptimizeGroupJob;
import org.apache.doris.nereids.jobs.joinorder.JoinOrderJob;
@@ -36,6 +37,9 @@ import
org.apache.doris.nereids.rules.rewrite.EliminateUnnecessaryProject;
import org.apache.doris.nereids.rules.rewrite.MergeProjectable;
import
org.apache.doris.nereids.rules.rewrite.PushDownExpressionsInHashCondition;
import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEAnchor;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer;
import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
import org.apache.doris.nereids.util.MoreFieldsThread;
import org.apache.doris.qe.ConnectContext;
@@ -66,6 +70,12 @@ public class Optimizer {
*/
public void execute() {
MoreFieldsThread.keepFunctionSignature(() -> {
+ Plan rewritePlan = cascadesContext.getRewritePlan();
+ if (containsCte(rewritePlan)) {
+ Plan normalizedPlan = normalizeCtePlan(rewritePlan);
+ cascadesContext.setRewritePlan(normalizedPlan);
+ refreshCteContext(normalizedPlan);
+ }
// generate inlined CTE alternative for CBO comparison
Plan cboInlinedPlan = generateCTEInlineAlternative();
// init memo
@@ -195,9 +205,9 @@ public class Optimizer {
private Plan generateFullCTEInline() {
Plan rewritePlan = cascadesContext.getRewritePlan();
CTEInliner cteInliner = new
CTEInliner(cascadesContext.getStatementContext());
- Plan inlinedPlan = cteInliner.generateInlinedPlan(rewritePlan);
- if (inlinedPlan != null) {
- return rewriteInlinedPlan(inlinedPlan);
+ Plan pushedDownInlinedPlan =
generateFilterPushedDownInlinedPlan(cteInliner, rewritePlan);
+ if (pushedDownInlinedPlan != null) {
+ return normalizeCtePlan(pushedDownInlinedPlan);
}
return null;
}
@@ -208,22 +218,60 @@ public class Optimizer {
private Plan generateSelectiveCTEInline() {
Plan rewritePlan = cascadesContext.getRewritePlan();
CTEInliner cteInliner = new
CTEInliner(cascadesContext.getStatementContext(), true);
- Plan inlinedPlan = cteInliner.generateInlinedPlan(rewritePlan);
- if (inlinedPlan != null) {
- inlinedPlan = rewriteInlinedPlan(inlinedPlan);
- if (inlinedPlan.anyMatch(p -> p instanceof LogicalEmptyRelation)) {
- inlinedPlan = eliminateEmptyRelation(inlinedPlan);
- cascadesContext.setRewritePlan(inlinedPlan);
+ Plan pushedDownInlinedPlan =
generateFilterPushedDownInlinedPlan(cteInliner, rewritePlan);
+ if (pushedDownInlinedPlan != null) {
+ if (pushedDownInlinedPlan.anyMatch(p -> p instanceof
LogicalEmptyRelation)) {
+ pushedDownInlinedPlan =
normalizeCtePlan(pushedDownInlinedPlan);
+ cascadesContext.setRewritePlan(pushedDownInlinedPlan);
+ refreshCteContext(pushedDownInlinedPlan);
return null;
}
}
return null;
}
+ private Plan normalizeCtePlan(Plan plan) {
+ Plan currentPlan = plan;
+ while (true) {
+ if (currentPlan.anyMatch(p -> p instanceof LogicalEmptyRelation)) {
+ currentPlan = eliminateEmptyRelation(currentPlan);
+ }
+ CTEInliner cteInliner = new
CTEInliner(cascadesContext.getStatementContext());
+ CTEInliner.InlineResult inlineResult =
cteInliner.inlineByCurrentConsumerCount(currentPlan);
+ Plan normalizedPlan = inlineResult.getPlan();
+ // Do not use Plan.equals() as a fixpoint check here. Some logical
nodes,
+ // e.g. LogicalCTEAnchor and LogicalSubQueryAlias, intentionally
ignore
+ // children in equals(), so a child CTE rewrite under a kept
parent may be
+ // missed and block cascading consumer-count-based inlining.
+ if (!inlineResult.isChanged()) {
+ return normalizedPlan;
+ }
+ currentPlan = normalizedPlan;
+ }
+ }
+
+ private boolean containsCte(Plan plan) {
+ return plan.anyMatch(p -> p instanceof LogicalCTEAnchor || p
instanceof LogicalCTEConsumer);
+ }
+
+ private void refreshCteContext(Plan plan) {
+ StatementContext statementContext =
cascadesContext.getStatementContext();
+ statementContext.clearCteEnvironment();
+ plan.foreach(p -> {
+ if (p instanceof LogicalCTEAnchor) {
+ LogicalCTEAnchor<?, ?> anchor = (LogicalCTEAnchor<?, ?>) p;
+ statementContext.setCteProducer(anchor.getCteId(),
(LogicalCTEProducer<?>) anchor.left());
+ } else if (p instanceof LogicalCTEConsumer) {
+ cascadesContext.putCTEIdToConsumer((LogicalCTEConsumer) p);
+ }
+ return false;
+ });
+ }
+
private Plan eliminateEmptyRelation(Plan plan) {
CascadesContext ctx = CascadesContext.initContext(
cascadesContext.getStatementContext(), plan,
PhysicalProperties.ANY);
- // Use getCteChildrenRewriter for the same reason as
rewriteInlinedPlan:
+ // Use getCteChildrenRewriter for the same reason as
pushDownFilterAndPruneInlinedPlan:
// getWholeTreeRewriterWithCustomJobs would invoke RewriteCteChildren
which
// reads stale rewrittenCteConsumer cache from the main Rewriter phase,
// reverting the inlined CTE subtrees back to the original structure.
@@ -234,6 +282,14 @@ public class Optimizer {
return ctx.getRewritePlan();
}
+ private Plan generateFilterPushedDownInlinedPlan(CTEInliner cteInliner,
Plan rewritePlan) {
+ Plan inlinedPlan = cteInliner.generateInlinedPlan(rewritePlan);
+ if (inlinedPlan == null) {
+ return null;
+ }
+ return pushDownFilterAndPruneInlinedPlan(inlinedPlan);
+ }
+
/**
* Run filter pushdown and column pruning on the inlined plan using a
temporary
* CascadesContext.
@@ -246,7 +302,7 @@ public class Optimizer {
* phase. That cached outer query still contains LogicalCTEConsumer nodes
for the inlined CTE,
* preventing the filter from ever reaching the inlined union body.
*/
- private Plan rewriteInlinedPlan(Plan inlinedPlan) {
+ private Plan pushDownFilterAndPruneInlinedPlan(Plan inlinedPlan) {
CascadesContext inlinedContext = CascadesContext.initContext(
cascadesContext.getStatementContext(), inlinedPlan,
PhysicalProperties.ANY);
Rewriter.getCteChildrenRewriter(inlinedContext, ImmutableList.of(
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CTEInliner.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CTEInliner.java
index 47ec88c0159..36e082ecb33 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CTEInliner.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CTEInliner.java
@@ -31,6 +31,7 @@ import
org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier;
import org.apache.doris.nereids.trees.plans.logical.LogicalCTEAnchor;
import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
import org.apache.doris.nereids.trees.plans.logical.LogicalUnion;
@@ -41,8 +42,10 @@ import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Generate an inlined alternative plan for CTE optimization.
@@ -65,6 +68,7 @@ public class CTEInliner extends DefaultPlanRewriter<Void> {
private final StatementContext statementContext;
// Map from CTEId to the CTE producer node (extracted from
CTEAnchor.left())
private final Map<CTEId, LogicalCTEProducer<?>> cteProducers = new
HashMap<>();
+ private final Set<CTEId> cteIdsToRemove = new HashSet<>();
private final boolean unionAllOnly;
public CTEInliner(StatementContext statementContext) {
@@ -81,6 +85,7 @@ public class CTEInliner extends DefaultPlanRewriter<Void> {
* Returns null if no CTEs can be inlined.
*/
public Plan generateInlinedPlan(Plan plan) {
+ clearRewriteCandidates();
// First pass: collect all CTE producers that can be inlined
collectCTEProducers(plan);
@@ -92,6 +97,44 @@ public class CTEInliner extends DefaultPlanRewriter<Void> {
return plan.accept(this, null);
}
+ /**
+ * Recursively remove unused CTE anchors and inline CTEs whose live
consumer count
+ * is small enough after rewrite rules change the plan shape.
+ */
+ public InlineResult inlineByCurrentConsumerCount(Plan plan) {
+ Plan currentPlan = plan;
+ boolean changed = false;
+ while (collectConsumerDrivenCandidates(currentPlan)) {
+ changed = true;
+ currentPlan = currentPlan.accept(this, null);
+ }
+ return new InlineResult(currentPlan, changed);
+ }
+
+ /** Result of one consumer-count-driven CTE normalization round. */
+ public static class InlineResult {
+ private final Plan plan;
+ private final boolean changed;
+
+ public InlineResult(Plan plan, boolean changed) {
+ this.plan = plan;
+ this.changed = changed;
+ }
+
+ public Plan getPlan() {
+ return plan;
+ }
+
+ public boolean isChanged() {
+ return changed;
+ }
+ }
+
+ private void clearRewriteCandidates() {
+ cteProducers.clear();
+ cteIdsToRemove.clear();
+ }
+
private void collectCTEProducers(Plan plan) {
plan.foreach(p -> {
if (p instanceof LogicalCTEAnchor) {
@@ -113,6 +156,40 @@ public class CTEInliner extends DefaultPlanRewriter<Void> {
});
}
+ private boolean collectConsumerDrivenCandidates(Plan plan) {
+ clearRewriteCandidates();
+ Map<CTEId, LogicalCTEProducer<?>> allCteProducers = new HashMap<>();
+ Map<CTEId, Integer> cteConsumerCounts = new HashMap<>();
+ plan.foreach(p -> {
+ if (p instanceof LogicalCTEAnchor) {
+ LogicalCTEAnchor<?, ?> anchor = (LogicalCTEAnchor<?, ?>) p;
+ allCteProducers.put(anchor.getCteId(), (LogicalCTEProducer<?>)
anchor.left());
+ } else if (p instanceof LogicalCTEConsumer) {
+ LogicalCTEConsumer consumer = (LogicalCTEConsumer) p;
+ cteConsumerCounts.merge(consumer.getCteId(), 1, Integer::sum);
+ }
+ });
+
+ int threshold =
statementContext.getConnectContext().getSessionVariable().inlineCTEReferencedThreshold;
+ for (Map.Entry<CTEId, LogicalCTEProducer<?>> entry :
allCteProducers.entrySet()) {
+ CTEId cteId = entry.getKey();
+ LogicalCTEProducer<?> producer = entry.getValue();
+ int consumerCount = cteConsumerCounts.getOrDefault(cteId, 0);
+ if (consumerCount == 0) {
+ cteIdsToRemove.add(cteId);
+ } else if (producer.child() instanceof LogicalEmptyRelation
+ || (consumerCount <= threshold && canInline(producer))) {
+ cteProducers.put(cteId, producer);
+ }
+ }
+ return !cteProducers.isEmpty() || !cteIdsToRemove.isEmpty();
+ }
+
+ private boolean canInline(LogicalCTEProducer<?> producer) {
+ return !statementContext.isForceMaterializeCTE(producer.getCteId())
+ && !containsNondeterministicFunction(producer);
+ }
+
private boolean containsNondeterministicFunction(LogicalCTEProducer<?>
producer) {
List<Expression> nondeterministicFunctions = new ArrayList<>();
producer.accept(NondeterministicFunctionCollector.INSTANCE,
nondeterministicFunctions);
@@ -127,13 +204,14 @@ public class CTEInliner extends DefaultPlanRewriter<Void>
{
@Override
public Plan visitLogicalCTEAnchor(LogicalCTEAnchor<? extends Plan, ?
extends Plan> cteAnchor, Void context) {
CTEId cteId = cteAnchor.getCteId();
- if (cteProducers.containsKey(cteId)) {
- // Inline: skip anchor and producer, process the right (consumer)
subtree
+ if (cteProducers.containsKey(cteId) || cteIdsToRemove.contains(cteId))
{
+ // Inline or remove: skip anchor and producer, process the right
(consumer) subtree
return cteAnchor.right().accept(this, null);
} else {
- // Force materialize: keep the structure, only process the right
subtree
+ // Keep the structure and continue trimming nested CTEs in both
children.
+ Plan left = cteAnchor.left().accept(this, null);
Plan right = cteAnchor.right().accept(this, null);
- return cteAnchor.withChildren(cteAnchor.left(), right);
+ return cteAnchor.withChildren(left, right);
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ClearContextStatus.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ClearContextStatus.java
index cb5dcf5526a..72cb6328d4a 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ClearContextStatus.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/ClearContextStatus.java
@@ -31,10 +31,7 @@ public class ClearContextStatus implements CustomRewriter {
@Override
public Plan rewriteRoot(Plan plan, JobContext jobContext) {
-
jobContext.getCascadesContext().getStatementContext().getRewrittenCteConsumer().clear();
-
jobContext.getCascadesContext().getStatementContext().getRewrittenCteProducer().clear();
-
jobContext.getCascadesContext().getStatementContext().getCteIdToOutputIds().clear();
-
jobContext.getCascadesContext().getStatementContext().getConsumerIdToFilters().clear();
+
jobContext.getCascadesContext().getStatementContext().clearCteEnvironment();
return plan;
}
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 76c4ab87bf7..54095c9e08a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -2577,7 +2577,7 @@ public class SessionVariable implements Serializable,
Writable {
@VarAttrDef.VarAttr(name = ENABLE_ORDERED_SCAN_RANGE_LOCATIONS)
public boolean enableOrderedScanRangeLocations = false;
- @VarAttrDef.VarAttr(name = CTE_INLINE_MODE, alias = "cbo_cte_inline_mode",
description = {
+ @VarAttrDef.VarAttr(name = CTE_INLINE_MODE, description = {
"CTE内联模式。<0:禁用; =0:仅当CTE体含UNION ALL且filter可消除部分分支时内联;
>=1:CBO比较物化与内联",
"CTE inline mode. <0: disable; =0: only inline when CTE body
contains UNION ALL "
+ "and consumer filters can eliminate some union branches;
"
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CTEInlineTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CTEInlineTest.java
index e9767ef524b..7ae9aa1e995 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CTEInlineTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/CTEInlineTest.java
@@ -21,7 +21,9 @@ import org.apache.doris.nereids.NereidsPlanner;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.parser.NereidsParser;
import org.apache.doris.nereids.properties.PhysicalProperties;
+import org.apache.doris.nereids.trees.expressions.CTEId;
import org.apache.doris.nereids.trees.plans.commands.ExplainCommand;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEConsumer;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
import org.apache.doris.nereids.util.MemoPatternMatchSupported;
import org.apache.doris.nereids.util.MemoTestUtils;
@@ -29,13 +31,24 @@ import org.apache.doris.nereids.util.PlanChecker;
import org.apache.doris.qe.OriginStatement;
import org.apache.doris.utframe.TestWithFeService;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.Map;
+import java.util.Set;
+
public class CTEInlineTest extends TestWithFeService implements
MemoPatternMatchSupported {
@Override
protected void runBeforeAll() throws Exception {
createDatabase("test");
connectContext.setDatabase("test");
+ createTable("CREATE TABLE cte_inline_tbl (\n"
+ + " id int NULL,\n"
+ + " val int NULL\n"
+ + ") ENGINE=OLAP\n"
+ + "DUPLICATE KEY(id)\n"
+ + "DISTRIBUTED BY HASH(id) BUCKETS 1\n"
+ + "PROPERTIES (\"replication_num\" = \"1\")");
}
@Test
@@ -81,4 +94,29 @@ public class CTEInlineTest extends TestWithFeService
implements MemoPatternMatch
).when(cte -> cte.getCteName().equals("yy"))
);
}
+
+ @Test
+ public void refreshCteConsumersAfterNormalizeEliminatesEmptyBranch() {
+ int oldCteInlineMode =
connectContext.getSessionVariable().cteInlineMode;
+ int oldInlineCteReferencedThreshold =
connectContext.getSessionVariable().inlineCTEReferencedThreshold;
+ connectContext.getSessionVariable().cteInlineMode = 0;
+ connectContext.getSessionVariable().inlineCTEReferencedThreshold = 1;
+
connectContext.getSessionVariable().setDisableNereidsRules("PRUNE_EMPTY_PARTITION");
+ String sql = "with cte as (select id, val from cte_inline_tbl) "
+ + "select * from cte where id = 1 "
+ + "union all select * from cte where id = 2 "
+ + "union all select * from cte where 1 = 0";
+ try {
+ PlanChecker.from(connectContext).checkPlannerResult(sql, planner
-> {
+ Map<CTEId, Set<LogicalCTEConsumer>> consumers =
+
planner.getCascadesContext().getStatementContext().getCteIdToConsumers();
+ Assertions.assertEquals(1, consumers.size());
+ Assertions.assertEquals(2,
consumers.values().iterator().next().size());
+ });
+ } finally {
+ connectContext.getSessionVariable().cteInlineMode =
oldCteInlineMode;
+ connectContext.getSessionVariable().inlineCTEReferencedThreshold =
oldInlineCteReferencedThreshold;
+ connectContext.getSessionVariable().setDisableNereidsRules("");
+ }
+ }
}
diff --git
a/regression-test/data/nereids_rules_p0/unique_function/agg_with_unique_function.out
b/regression-test/data/nereids_rules_p0/unique_function/agg_with_unique_function.out
index dfe9a28c4bb..adc172f81d6 100644
---
a/regression-test/data/nereids_rules_p0/unique_function/agg_with_unique_function.out
+++
b/regression-test/data/nereids_rules_p0/unique_function/agg_with_unique_function.out
@@ -264,9 +264,7 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
----PhysicalProject[a + random(), a + random() + 0, abs(a + random()), sum(a +
random() + 0), sum(a + random())]
------PhysicalQuickSort[MERGE_SORT]
--------PhysicalQuickSort[LOCAL_SORT]
-----------PhysicalProject[(a + random() + 1.0) AS `(a + random() + 1.0)`, a +
random(), a + random() + 0, abs(a + random()) AS `abs(a + random())`, sum(a +
random() + 0), sum(a + random())]
-------------PhysicalUnion
---------------PhysicalEmptyRelation
---------------filter((.a + random() + 0 > 0.01))
-----------------PhysicalCteConsumer ( cteId=CTEId#0 )
+----------PhysicalProject[(a + random() + 1.0) AS `(a + random() + 1.0)`, a +
random() + 0 AS `a + random() + 0`, a + random() AS `a + random()`, abs(a +
random()) AS `abs(a + random())`, sum(a + random() + 0) AS `sum(a + random() +
0)`, sum(a + random()) AS `sum(a + random())`]
+------------filter((.a + random() + 0 > 0.01))
+--------------PhysicalCteConsumer ( cteId=CTEId#0 )
diff --git a/regression-test/data/query_p0/repeat/test_repeat_output_slot.out
b/regression-test/data/query_p0/repeat/test_repeat_output_slot.out
index f8ab9595435..8019e6bf2b5 100644
--- a/regression-test/data/query_p0/repeat/test_repeat_output_slot.out
+++ b/regression-test/data/query_p0/repeat/test_repeat_output_slot.out
@@ -39,22 +39,17 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
100000
-- !sql_2_shape --
-PhysicalCteAnchor ( cteId=CTEId#0 )
---PhysicalCteProducer ( cteId=CTEId#0 )
-----hashAgg[GLOBAL]
-------hashAgg[LOCAL]
---------PhysicalProject
-----------PhysicalOlapScan[tbl_test_repeat_output_slot]
---PhysicalResultSink
-----PhysicalProject
-------PhysicalUnion
---------PhysicalProject
-----------filter((GROUPING_PREFIX_col_varchar_50__undef_signed__index_inverted_col_datetime_6__undef_signed_col_varchar_50__undef_signed
> 0))
-------------hashAgg[GLOBAL]
---------------hashAgg[LOCAL]
-----------------PhysicalRepeat
-------------------PhysicalCteConsumer ( cteId=CTEId#0 )
---------PhysicalEmptyRelation
+PhysicalResultSink
+--PhysicalProject
+----filter((GROUPING_PREFIX_col_varchar_50__undef_signed__index_inverted_col_datetime_6__undef_signed_col_varchar_50__undef_signed
> 0))
+------hashAgg[GLOBAL]
+--------hashAgg[LOCAL]
+----------PhysicalRepeat
+------------PhysicalProject
+--------------hashAgg[GLOBAL]
+----------------hashAgg[LOCAL]
+------------------PhysicalProject
+--------------------PhysicalOlapScan[tbl_test_repeat_output_slot]
-- !sql_2_result --
\N ALL 1 6 \N \N \N
@@ -64,4 +59,3 @@ PhysicalCteAnchor ( cteId=CTEId#0 )
2020-01-04T00:00 ALL 1 6 \N \N b
2020-01-04T00:00 ALL 1 6 \N \N b
2020-01-04T00:00 ALL 1 7 \N \N \N
-
diff --git
a/regression-test/suites/query_p0/cte/test_cbo_cte_inline_prune.groovy
b/regression-test/suites/query_p0/cte/test_cbo_cte_inline_prune.groovy
new file mode 100644
index 00000000000..ffd4f2acae4
--- /dev/null
+++ b/regression-test/suites/query_p0/cte/test_cbo_cte_inline_prune.groovy
@@ -0,0 +1,113 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_cbo_cte_inline_prune") {
+ sql "DROP TABLE IF EXISTS cte_cbo_inline_tbl"
+ sql """
+ CREATE TABLE cte_cbo_inline_tbl (
+ id INT,
+ val INT
+ ) ENGINE=OLAP
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+ sql "INSERT INTO cte_cbo_inline_tbl VALUES (1, 10), (2, 20), (3, 30)"
+
+ sql "DROP TABLE IF EXISTS cte_cbo_empty_array_tbl"
+ sql """
+ CREATE TABLE cte_cbo_empty_array_tbl (
+ id INT,
+ vals ARRAY<INT>
+ ) ENGINE=OLAP
+ DUPLICATE KEY(id)
+ DISTRIBUTED BY HASH(id) BUCKETS 1
+ PROPERTIES ("replication_num" = "1")
+ """
+
+ sql "SET cte_inline_mode=1"
+ sql "SET inline_cte_referenced_threshold=1"
+
+ explain {
+ sql "SELECT count(*) FROM cte_cbo_empty_array_tbl"
+ contains("VEMPTYSET")
+ }
+
+ explain {
+ sql """
+ shape plan
+ WITH cte_base AS (
+ SELECT id, val, 1 AS tag FROM cte_cbo_inline_tbl
+ )
+ SELECT * FROM cte_base WHERE tag = 2
+ UNION ALL
+ SELECT * FROM cte_base WHERE tag = 3
+ """
+ contains("PhysicalEmptyRelation")
+ notContains("PhysicalCteProducer")
+ }
+
+ explain {
+ sql """
+ shape plan
+ WITH cte_base AS (
+ SELECT id, val, 1 AS tag FROM cte_cbo_inline_tbl
+ ),
+ cte_keep AS (
+ SELECT id, val FROM cte_base WHERE tag = 1
+ ),
+ cte_drop AS (
+ SELECT id, val FROM cte_base WHERE tag = 2
+ )
+ SELECT * FROM cte_keep
+ UNION ALL
+ SELECT * FROM cte_keep
+ UNION ALL
+ SELECT * FROM cte_drop WHERE 1 = 0
+ """
+ multiContains("PhysicalCteProducer", 0)
+ }
+
+ sql "SET cte_inline_mode=0"
+ explain {
+ sql """
+ shape plan
+ WITH cte_base AS (
+ SELECT id, val, 1 AS tag FROM cte_cbo_inline_tbl
+ ),
+ cte_keep AS (
+ SELECT id, val FROM cte_base WHERE tag = 1
+ ),
+ cte_drop AS (
+ SELECT id, val FROM cte_base WHERE tag = 2
+ ),
+ cte_outer AS (
+ SELECT * FROM cte_keep
+ UNION ALL
+ SELECT * FROM cte_drop WHERE 1 = 0
+ )
+ SELECT * FROM cte_outer
+ UNION ALL
+ SELECT * FROM cte_outer
+ """
+ // cte_outer is still referenced twice and remains materialized, but
+ // eliminating cte_drop makes cte_base have only one live consumer.
+ // The fixpoint check must revisit the kept outer CTE anchor and inline
+ // cte_base; otherwise two producers would remain.
+ multiContains("PhysicalCteProducer", 1)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]