This is an automated email from the ASF dual-hosted git repository.
leborchuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git
The following commit(s) were added to refs/heads/main by this push:
new f43741b804c orca: fallback to Postgres optimizer on cross-slice
replicated CTE Consumer (#1719)
f43741b804c is described below
commit f43741b804c07775ed06d267e3b6911064b23971
Author: Alena Rybakina <[email protected]>
AuthorDate: Tue May 12 13:13:00 2026 +0300
orca: fallback to Postgres optimizer on cross-slice replicated CTE Consumer
(#1719)
* orca: fallback to Postgres optimizer on cross-slice replicated CTE
Consumer.
Inspired by greengage 51fe92e: before Expr->DXL translation,
walk the physical tree and track which slice each CTE Producer
and Consumer lives on. If a Consumer is on a different slice
than its Producer and the Producer's distribution is replicated,
force a fallback to the Postgres optimizer.
The replicated filter is essential: ordinary cross-slice CTE plans
(non-replicated Producer with Gather/Redistribute Consumer) are a
normal ORCA pattern and must not trigger fallback.
51fe92e doesn't trigger when a CTE over a replicated table is
referenced from a scalar subquery, so the query hangs. This commit
replaces the single-point check with a whole-tree walker that
catches both cases.
Tests: shared_scan adds a scalar-subquery reproducer guarded by
statement_timeout. qp_orca_fallback adds two cases over a replicated
CTE: a scalar-subquery form that triggers the walker (the hang case
51fe92e missed -- fallback to Postgres), and the original 51fe92e JOIN
form where ORCA emits a safe plan with a One-Time Filter
(gp_execution_segment() = N) and the walker correctly stays silent
(guards against false positives).
(cherry picked from commit
https://github.com/open-gpdb/gpdb/commit/3a9aebfe3afb27dd060c16b5cfa7932e7a2ed43f)
---
.../gporca/libgpopt/include/gpopt/base/CUtils.h | 3 +
src/backend/gporca/libgpopt/src/base/CUtils.cpp | 124 +++++++++++++++++++++
.../src/translate/CTranslatorExprToDXL.cpp | 26 +++++
src/test/regress/expected/qp_orca_fallback.out | 86 ++++++++++++++
.../expected/qp_orca_fallback_optimizer.out | 99 ++++++++++++++++
src/test/regress/expected/shared_scan.out | 33 ++++++
.../regress/expected/shared_scan_optimizer.out | 28 +++++
src/test/regress/sql/qp_orca_fallback.sql | 47 ++++++++
src/test/regress/sql/shared_scan.sql | 28 +++++
9 files changed, 474 insertions(+)
diff --git a/src/backend/gporca/libgpopt/include/gpopt/base/CUtils.h
b/src/backend/gporca/libgpopt/include/gpopt/base/CUtils.h
index 1e873e14847..0aded041e00 100644
--- a/src/backend/gporca/libgpopt/include/gpopt/base/CUtils.h
+++ b/src/backend/gporca/libgpopt/include/gpopt/base/CUtils.h
@@ -1026,6 +1026,9 @@ public:
static CTableDescriptorHashSet *RemoveDuplicateMdids(
CMemoryPool *mp, CTableDescriptorHashSet *tabdescs);
+ static BOOL FHasCrossSliceReplicatedCTEConsumer(CMemoryPool *mp,
+
CExpression *pexpr);
+
static CExpression *ReplaceColrefWithProjectExpr(CMemoryPool *mp,
CExpression *pexpr,
CColRef *pcolref,
diff --git a/src/backend/gporca/libgpopt/src/base/CUtils.cpp
b/src/backend/gporca/libgpopt/src/base/CUtils.cpp
index b5f847b817a..4776c602fcd 100644
--- a/src/backend/gporca/libgpopt/src/base/CUtils.cpp
+++ b/src/backend/gporca/libgpopt/src/base/CUtils.cpp
@@ -978,6 +978,130 @@ CUtils::FHasCTEAnchor(CExpression *pexpr)
return false;
}
+// True if the distribution is replicated-like.
+static BOOL
+FReplicatedLikeDistribution(CDistributionSpec::EDistributionType edt)
+{
+ return (CDistributionSpec::EdtStrictReplicated == edt ||
+ CDistributionSpec::EdtTaintedReplicated == edt ||
+ CDistributionSpec::EdtUniversal == edt);
+}
+
+struct SCTEInfo
+{
+ ULONG cteId;
+ ULONG sliceId;
+
+ SCTEInfo(ULONG cte_id, ULONG slice_id) : cteId(cte_id),
sliceId(slice_id)
+ {
+ }
+};
+
+typedef CDynamicPtrArray<SCTEInfo, CleanupDelete<SCTEInfo> > CTEInfoArray;
+
+// Walk the physical tree, recording the slice id of every replicated
+// CTE Producer and every CTE Consumer. Slices are delimited by Motion
+// nodes: each non-scalar child of a Motion lives in a fresh slice --
+// same motId-stack idea as in apply_shareinput_xslice.
+static void
+CollectCTESlices(CMemoryPool *mp, CExpression *pexpr, ULONG curSlice,
+ ULONG *pNextSlice, CTEInfoArray *prodInfos,
+ CTEInfoArray *consInfos)
+{
+ GPOS_CHECK_STACK_SIZE;
+ GPOS_ASSERT(nullptr != pexpr);
+
+ COperator *pop = pexpr->Pop();
+
+ if (COperator::EopPhysicalCTEProducer == pop->Eopid())
+ {
+ // Producer's distribution comes from its only child -- inspect
+ // it there. Skip non-replicated Producers; they cannot trigger
+ // the cross-slice issue we are checking for.
+ GPOS_ASSERT(1 == pexpr->Arity());
+ CExpression *pexprChild = (*pexpr)[0];
+ CDrvdPropPlan *pdpplan =
+ CDrvdPropPlan::Pdpplan(pexprChild->PdpDerive());
+
+ if (FReplicatedLikeDistribution(pdpplan->Pds()->Edt()))
+ {
+ prodInfos->Append(GPOS_NEW(mp) SCTEInfo(
+
CPhysicalCTEProducer::PopConvert(pop)->UlCTEId(), curSlice));
+ }
+ }
+ else if (COperator::EopPhysicalCTEConsumer == pop->Eopid())
+ {
+ // Consumer is a leaf -- record (cteId, curSlice) and let the
+ // caller decide later, once the whole tree has been walked.
+ consInfos->Append(GPOS_NEW(mp) SCTEInfo(
+ CPhysicalCTEConsumer::PopConvert(pop)->UlCTEId(),
curSlice));
+ }
+
+ BOOL isMotion = CUtils::FPhysicalMotion(pop);
+
+ for (ULONG ul = 0; ul < pexpr->Arity(); ul++)
+ {
+ CExpression *pexprChild = (*pexpr)[ul];
+
+ if (pexprChild->Pop()->FScalar())
+ {
+ continue;
+ }
+
+ ULONG childSlice = curSlice;
+ if (isMotion)
+ {
+ (*pNextSlice)++;
+ childSlice = *pNextSlice;
+ }
+
+ CollectCTESlices(mp, pexprChild, childSlice, pNextSlice,
prodInfos,
+ consInfos);
+ }
+}
+
+static BOOL
+FFoundCrossSlice(const CTEInfoArray *consInfos, const CTEInfoArray *prodInfos)
+{
+ for (ULONG ic = 0; ic < consInfos->Size(); ic++)
+ {
+ SCTEInfo *cons = (*consInfos)[ic];
+
+ for (ULONG ip = 0; ip < prodInfos->Size(); ip++)
+ {
+ SCTEInfo *prod = (*prodInfos)[ip];
+ if (prod->cteId == cons->cteId && prod->sliceId !=
cons->sliceId)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+BOOL
+CUtils::FHasCrossSliceReplicatedCTEConsumer(CMemoryPool *mp, CExpression
*pexpr)
+{
+ if (NULL == pexpr)
+ {
+ return false;
+ }
+
+ CTEInfoArray *prodInfos = GPOS_NEW(mp) CTEInfoArray(mp);
+ CTEInfoArray *consInfos = GPOS_NEW(mp) CTEInfoArray(mp);
+ ULONG nextSlice = 0;
+
+ CollectCTESlices(mp, pexpr, 0 /*curSlice*/, &nextSlice, prodInfos,
+ consInfos);
+
+ BOOL cross = FFoundCrossSlice(consInfos, prodInfos);
+
+ prodInfos->Release();
+ consInfos->Release();
+
+ return cross;
+}
+
//---------------------------------------------------------------------------
// @class:
// CUtils::FHasSubqueryOrApply
diff --git a/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp
b/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp
index 6119e2ba71f..6bc89993008 100644
--- a/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp
+++ b/src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp
@@ -265,6 +265,20 @@ CTranslatorExprToDXL::PdxlnTranslate(CExpression *pexpr,
GPOS_ASSERT(nullptr == m_pdpplan);
+ // Walk the physical tree and detect a CTE Consumer placed on a
+ // different slice than its Producer when the Producer's output is
+ // replicated-like (StrictReplicated/TaintedReplicated/Universal).
+ // Fall back to the Postgres optimizer if it is detected because
+ // it breaks Producer-Consumer locality and can hang the
+ // query at execution.
+ if (CUtils::FHasCrossSliceReplicatedCTEConsumer(m_mp, pexpr))
+ {
+ GPOS_RAISE(
+ gpopt::ExmaDXL, gpopt::ExmiExpr2DXLUnsupportedFeature,
+ GPOS_WSZ_LIT(
+ "CTE Consumer placed on a different slice than
its replicated Producer"));
+ }
+
m_pdpplan = CDrvdPropPlan::Pdpplan(pexpr->PdpDerive());
m_pdpplan->AddRef();
@@ -4250,6 +4264,18 @@ CTranslatorExprToDXL::BuildScalarSubplans(
{
const ULONG size = pdrgpcrInner->Size();
+ // Fallback to Postgres optimizer if the SubPlan's inner expression
contains a
+ // CTE Consumer placed on a different slice than its replicated
Producer.
+ // Such a Consumer becomes a cross-slice Shared Scan reader without a
local
+ // Producer, which can hang the query or fail at execution time.
+ if (CUtils::FHasCrossSliceReplicatedCTEConsumer(m_mp, pexprInner))
+ {
+ GPOS_RAISE(
+ gpopt::ExmaDXL, gpopt::ExmiExpr2DXLUnsupportedFeature,
+ GPOS_WSZ_LIT(
+ "CTE Consumer placed on a different slice than
its replicated Producer"));
+ }
+
CDXLNodeArray *pdrgpdxlnInner = GPOS_NEW(m_mp) CDXLNodeArray(m_mp);
for (ULONG ul = 0; ul < size; ul++)
{
diff --git a/src/test/regress/expected/qp_orca_fallback.out
b/src/test/regress/expected/qp_orca_fallback.out
index 37378ef698f..e8e42370758 100644
--- a/src/test/regress/expected/qp_orca_fallback.out
+++ b/src/test/regress/expected/qp_orca_fallback.out
@@ -293,6 +293,92 @@ SELECT * FROM jsonb_array_elements('["b", "a"]'::jsonb)
WITH ORDINALITY;
"a" | 2
(2 rows)
+-- The walker that detects a CTE Consumer on a different slice than its
+-- replicated Producer. Without it ORCA would emit a plan with cross-slice
+-- replicated CTE Consumers that hangs at execution.
+-- start_ignore
+DROP TABLE IF EXISTS tbl1, tbl2;
+NOTICE: table "tbl1" does not exist, skipping
+NOTICE: table "tbl2" does not exist, skipping
+-- end_ignore
+CREATE TABLE tbl2 (id numeric, refrcode varchar(255), referenceid numeric)
+DISTRIBUTED REPLICATED;
+CREATE TABLE tbl1 (id bigserial, iscalctrg varchar(15) NOT NULL,
+ iscalcdetail varchar(15))
+DISTRIBUTED REPLICATED;
+-- start_ignore
+INSERT INTO tbl2 SELECT i, 'A'||(i%5), 101991
+ FROM generate_series(1, 50000) i;
+INSERT INTO tbl1 (iscalctrg, iscalcdetail)
+ SELECT 'A'||(i%5), 'A'||(i%7) FROM generate_series(1, 50000) i;
+ANALYZE tbl1;
+ANALYZE tbl2;
+-- end_ignore
+-- Case 1: walker triggers fallback. With scalar subqueries on the CTE
+-- ORCA produces a plan whose CTE Producer is replicated and Consumers
+-- live on a different slice -- the walker raises ExmiExpr2DXLUnsupported
+-- and trace_fallback DETAIL says "CTE Consumer placed on a different
+-- slice than its replicated Producer".
+EXPLAIN (COSTS OFF)
+WITH t2 AS (SELECT id, refrcode FROM tbl2 WHERE referenceid = 101991)
+SELECT p.iscalctrg,
+ (SELECT refrcode FROM t2 WHERE refrcode = p.iscalctrg LIMIT 1) AS r,
+ (SELECT refrcode FROM t2 WHERE refrcode = p.iscalcdetail LIMIT 1) AS r1
+FROM tbl1 p
+LIMIT 1;
+ QUERY PLAN
+----------------------------------------------------------------------------------------
+ Gather Motion 1:1 (slice1; segments: 1)
+ -> Limit
+ -> Seq Scan on tbl1 p
+ SubPlan 1
+ -> Limit
+ -> Result
+ Filter: ((tbl2.refrcode)::text =
(p.iscalctrg)::text)
+ -> Materialize
+ -> Seq Scan on tbl2
+ Filter: (referenceid =
'101991'::numeric)
+ SubPlan 2
+ -> Limit
+ -> Result
+ Filter: ((tbl2_1.refrcode)::text =
(p.iscalcdetail)::text)
+ -> Materialize
+ -> Seq Scan on tbl2 tbl2_1
+ Filter: (referenceid =
'101991'::numeric)
+ Optimizer: Postgres query optimizer
+(18 rows)
+
+-- Case 2: walker correctly stays silent. The same CTE referenced from a
+-- JOIN: ORCA pins the Producer body to a single segment with a One-Time
+-- Filter (gp_execution_segment() = N), so the Producer's child
+-- distribution is EdtSingleton, not replicated -- the walker skips it.
+EXPLAIN (COSTS OFF)
+WITH t1 AS (SELECT * FROM tbl1),
+ t2 AS (SELECT id, refrcode FROM tbl2 WHERE referenceid = 101991)
+SELECT p.* FROM t1 p
+ JOIN t2 r ON p.iscalctrg = r.refrcode
+ JOIN t2 r1 ON p.iscalcdetail = r1.refrcode
+LIMIT 1;
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Gather Motion 1:1 (slice1; segments: 1)
+ -> Limit
+ -> Hash Join
+ Hash Cond: ((tbl1.iscalcdetail)::text = (r1.refrcode)::text)
+ -> Hash Join
+ Hash Cond: ((tbl2.refrcode)::text =
(tbl1.iscalctrg)::text)
+ -> Seq Scan on tbl2
+ Filter: (referenceid = '101991'::numeric)
+ -> Hash
+ -> Seq Scan on tbl1
+ -> Hash
+ -> Subquery Scan on r1
+ -> Seq Scan on tbl2 tbl2_1
+ Filter: (referenceid = '101991'::numeric)
+ Optimizer: Postgres query optimizer
+(15 rows)
+
+DROP TABLE tbl1, tbl2;
-- start_ignore
-- FIXME: gpcheckcat fails due to mismatching distribution policy if this
table isn't dropped
-- Keep this table around once this is fixed
diff --git a/src/test/regress/expected/qp_orca_fallback_optimizer.out
b/src/test/regress/expected/qp_orca_fallback_optimizer.out
index ad2eeabd1e5..63c2cc0e1a8 100644
--- a/src/test/regress/expected/qp_orca_fallback_optimizer.out
+++ b/src/test/regress/expected/qp_orca_fallback_optimizer.out
@@ -353,6 +353,105 @@ DETAIL: Falling back to Postgres-based planner because
GPORCA does not support
"a" | 2
(2 rows)
+-- The walker that detects a CTE Consumer on a different slice than its
+-- replicated Producer. Without it ORCA would emit a plan with cross-slice
+-- replicated CTE Consumers that hangs at execution.
+-- start_ignore
+DROP TABLE IF EXISTS tbl1, tbl2;
+NOTICE: table "tbl1" does not exist, skipping
+NOTICE: table "tbl2" does not exist, skipping
+-- end_ignore
+CREATE TABLE tbl2 (id numeric, refrcode varchar(255), referenceid numeric)
+DISTRIBUTED REPLICATED;
+CREATE TABLE tbl1 (id bigserial, iscalctrg varchar(15) NOT NULL,
+ iscalcdetail varchar(15))
+DISTRIBUTED REPLICATED;
+-- start_ignore
+INSERT INTO tbl2 SELECT i, 'A'||(i%5), 101991
+ FROM generate_series(1, 50000) i;
+INSERT INTO tbl1 (iscalctrg, iscalcdetail)
+ SELECT 'A'||(i%5), 'A'||(i%7) FROM generate_series(1, 50000) i;
+ANALYZE tbl1;
+ANALYZE tbl2;
+-- end_ignore
+-- Case 1: walker triggers fallback. With scalar subqueries on the CTE
+-- ORCA produces a plan whose CTE Producer is replicated and Consumers
+-- live on a different slice -- the walker raises ExmiExpr2DXLUnsupported
+-- and trace_fallback DETAIL says "CTE Consumer placed on a different
+-- slice than its replicated Producer".
+EXPLAIN (COSTS OFF)
+WITH t2 AS (SELECT id, refrcode FROM tbl2 WHERE referenceid = 101991)
+SELECT p.iscalctrg,
+ (SELECT refrcode FROM t2 WHERE refrcode = p.iscalctrg LIMIT 1) AS r,
+ (SELECT refrcode FROM t2 WHERE refrcode = p.iscalcdetail LIMIT 1) AS r1
+FROM tbl1 p
+LIMIT 1;
+INFO: GPORCA failed to produce a plan, falling back to Postgres-based planner
+DETAIL: Falling back to Postgres-based planner because GPORCA does not
support the following feature: CTE Consumer placed on a different slice than
its replicated Producer
+ QUERY PLAN
+----------------------------------------------------------------------------------------
+ Gather Motion 1:1 (slice1; segments: 1)
+ -> Limit
+ -> Seq Scan on tbl1 p
+ SubPlan 1
+ -> Limit
+ -> Result
+ Filter: ((tbl2.refrcode)::text =
(p.iscalctrg)::text)
+ -> Materialize
+ -> Seq Scan on tbl2
+ Filter: (referenceid =
'101991'::numeric)
+ SubPlan 2
+ -> Limit
+ -> Result
+ Filter: ((tbl2_1.refrcode)::text =
(p.iscalcdetail)::text)
+ -> Materialize
+ -> Seq Scan on tbl2 tbl2_1
+ Filter: (referenceid =
'101991'::numeric)
+ Optimizer: Postgres query optimizer
+(18 rows)
+
+-- Case 2: walker correctly stays silent. The same CTE referenced from a
+-- JOIN: ORCA pins the Producer body to a single segment with a One-Time
+-- Filter (gp_execution_segment() = N), so the Producer's child
+-- distribution is EdtSingleton, not replicated -- the walker skips it.
+EXPLAIN (COSTS OFF)
+WITH t1 AS (SELECT * FROM tbl1),
+ t2 AS (SELECT id, refrcode FROM tbl2 WHERE referenceid = 101991)
+SELECT p.* FROM t1 p
+ JOIN t2 r ON p.iscalctrg = r.refrcode
+ JOIN t2 r1 ON p.iscalcdetail = r1.refrcode
+LIMIT 1;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ -> Sequence
+ -> Shared Scan (share slice:id 1:1)
+ -> Result
+ Filter: (tbl2.referenceid = '101991'::numeric)
+ -> Result
+ One-Time Filter: (gp_execution_segment() = 1)
+ -> Seq Scan on tbl2
+ -> Redistribute Motion 1:3 (slice2)
+ -> Limit
+ -> Gather Motion 3:1 (slice3; segments: 3)
+ -> Limit
+ -> Hash Join
+ Hash Cond: ((tbl1.iscalctrg)::text =
(share1_ref2.refrcode)::text)
+ -> Hash Join
+ Hash Cond:
((tbl1.iscalcdetail)::text = (share1_ref3.refrcode)::text)
+ -> Result
+ -> Seq Scan on tbl1
+ -> Hash
+ -> Redistribute Motion 3:3
(slice4; segments: 3)
+ Hash Key:
share1_ref3.refrcode
+ -> Shared Scan
(share slice:id 4:1)
+ -> Hash
+ -> Broadcast Motion 3:3
(slice5; segments: 3)
+ -> Shared Scan (share
slice:id 5:1)
+ Optimizer: GPORCA
+(26 rows)
+
+DROP TABLE tbl1, tbl2;
-- start_ignore
-- FIXME: gpcheckcat fails due to mismatching distribution policy if this
table isn't dropped
-- Keep this table around once this is fixed
diff --git a/src/test/regress/expected/shared_scan.out
b/src/test/regress/expected/shared_scan.out
index 2673aff0f8c..83a3815fb0e 100644
--- a/src/test/regress/expected/shared_scan.out
+++ b/src/test/regress/expected/shared_scan.out
@@ -234,3 +234,36 @@ where
Optimizer: Postgres query optimizer
(37 rows)
+-- ORCA should fallback when a CTE over a replicated table is referenced
+-- from multiple scalar subqueries.
+-- ss_t1 needs enough rows (40000) to push ORCA to the cross-slice plan;
+-- with fewer rows the bug does not manifest and the test would silently
+-- pass even without the fix.
+-- start_ignore
+DROP TABLE IF EXISTS ss_t1, ss_t2;
+NOTICE: table "ss_t1" does not exist, skipping
+NOTICE: table "ss_t2" does not exist, skipping
+-- end_ignore
+CREATE TABLE ss_t1 AS
+ SELECT generate_series(1, 40000) id
+ DISTRIBUTED BY (id);
+CREATE TABLE ss_t2 AS
+ SELECT * FROM (VALUES (1, 10), (2, 20)) AS v(id, v)
+ DISTRIBUTED REPLICATED;
+ANALYZE ss_t1;
+ANALYZE ss_t2;
+SET statement_timeout = '15s';
+WITH
+ cte1 AS (SELECT v FROM ss_t2 WHERE id = 1),
+ cte2 AS (SELECT v FROM ss_t2 WHERE id = 2)
+ SELECT (SELECT v FROM cte1) + (SELECT v FROM cte2) +
+ (SELECT v FROM cte1) + (SELECT v FROM cte2) AS result
+ FROM ss_t1
+ LIMIT 1;
+ result
+--------
+ 60
+(1 row)
+
+RESET statement_timeout;
+DROP TABLE ss_t1, ss_t2;
diff --git a/src/test/regress/expected/shared_scan_optimizer.out
b/src/test/regress/expected/shared_scan_optimizer.out
index 57ec36089d2..e71dfafe035 100644
--- a/src/test/regress/expected/shared_scan_optimizer.out
+++ b/src/test/regress/expected/shared_scan_optimizer.out
@@ -242,3 +242,31 @@ where
Optimizer: Postgres query optimizer
(37 rows)
+-- ORCA should fallback when a CTE over a replicated table is referenced
+-- from multiple scalar subqueries.
+-- ss_t1 needs enough rows (40000) to push ORCA to the cross-slice plan;
+-- with fewer rows the bug does not manifest and the test would silently
+-- pass even without the fix.
+CREATE TABLE ss_t1 AS
+ SELECT generate_series(1, 40000) id
+ DISTRIBUTED BY (id);
+CREATE TABLE ss_t2 AS
+ SELECT * FROM (VALUES (1, 10), (2, 20)) AS v(id, v)
+ DISTRIBUTED REPLICATED;
+ANALYZE ss_t1;
+ANALYZE ss_t2;
+SET statement_timeout = '15s';
+WITH
+ cte1 AS (SELECT v FROM ss_t2 WHERE id = 1),
+ cte2 AS (SELECT v FROM ss_t2 WHERE id = 2)
+ SELECT (SELECT v FROM cte1) + (SELECT v FROM cte2) +
+ (SELECT v FROM cte1) + (SELECT v FROM cte2) AS result
+ FROM ss_t1
+ LIMIT 1;
+ result
+--------
+ 60
+(1 row)
+
+RESET statement_timeout;
+DROP TABLE ss_t1, ss_t2;
diff --git a/src/test/regress/sql/qp_orca_fallback.sql
b/src/test/regress/sql/qp_orca_fallback.sql
index 42ad5eef3b9..ecb892c2aa7 100644
--- a/src/test/regress/sql/qp_orca_fallback.sql
+++ b/src/test/regress/sql/qp_orca_fallback.sql
@@ -121,6 +121,53 @@ select array_agg(a order by b)
-- Orca should fallback if a function in 'from' clause uses 'WITH ORDINALITY'
SELECT * FROM jsonb_array_elements('["b", "a"]'::jsonb) WITH ORDINALITY;
+-- The walker that detects a CTE Consumer on a different slice than its
+-- replicated Producer. Without it ORCA would emit a plan with cross-slice
+-- replicated CTE Consumers that hangs at execution.
+-- start_ignore
+DROP TABLE IF EXISTS tbl1, tbl2;
+-- end_ignore
+CREATE TABLE tbl2 (id numeric, refrcode varchar(255), referenceid numeric)
+DISTRIBUTED REPLICATED;
+CREATE TABLE tbl1 (id bigserial, iscalctrg varchar(15) NOT NULL,
+ iscalcdetail varchar(15))
+DISTRIBUTED REPLICATED;
+-- start_ignore
+INSERT INTO tbl2 SELECT i, 'A'||(i%5), 101991
+ FROM generate_series(1, 50000) i;
+INSERT INTO tbl1 (iscalctrg, iscalcdetail)
+ SELECT 'A'||(i%5), 'A'||(i%7) FROM generate_series(1, 50000) i;
+ANALYZE tbl1;
+ANALYZE tbl2;
+-- end_ignore
+
+-- Case 1: walker triggers fallback. With scalar subqueries on the CTE
+-- ORCA produces a plan whose CTE Producer is replicated and Consumers
+-- live on a different slice -- the walker raises ExmiExpr2DXLUnsupported
+-- and trace_fallback DETAIL says "CTE Consumer placed on a different
+-- slice than its replicated Producer".
+EXPLAIN (COSTS OFF)
+WITH t2 AS (SELECT id, refrcode FROM tbl2 WHERE referenceid = 101991)
+SELECT p.iscalctrg,
+ (SELECT refrcode FROM t2 WHERE refrcode = p.iscalctrg LIMIT 1) AS r,
+ (SELECT refrcode FROM t2 WHERE refrcode = p.iscalcdetail LIMIT 1) AS r1
+FROM tbl1 p
+LIMIT 1;
+
+-- Case 2: walker correctly stays silent. The same CTE referenced from a
+-- JOIN: ORCA pins the Producer body to a single segment with a One-Time
+-- Filter (gp_execution_segment() = N), so the Producer's child
+-- distribution is EdtSingleton, not replicated -- the walker skips it.
+EXPLAIN (COSTS OFF)
+WITH t1 AS (SELECT * FROM tbl1),
+ t2 AS (SELECT id, refrcode FROM tbl2 WHERE referenceid = 101991)
+SELECT p.* FROM t1 p
+ JOIN t2 r ON p.iscalctrg = r.refrcode
+ JOIN t2 r1 ON p.iscalcdetail = r1.refrcode
+LIMIT 1;
+
+DROP TABLE tbl1, tbl2;
+
-- start_ignore
-- FIXME: gpcheckcat fails due to mismatching distribution policy if this
table isn't dropped
-- Keep this table around once this is fixed
diff --git a/src/test/regress/sql/shared_scan.sql
b/src/test/regress/sql/shared_scan.sql
index 7234cef6e4a..80b4a1d52c7 100644
--- a/src/test/regress/sql/shared_scan.sql
+++ b/src/test/regress/sql/shared_scan.sql
@@ -120,3 +120,31 @@ where
(data_hour = date_trunc('day',data_hour) and stat.schema_name || '.'
||stat.table_name not in (select table_nm_23 from tbls_daily_report_23))
and (stat.schema_name || '.' ||stat.table_name not in (select
table_nm_onl_act from tbls_w_onl_actl_data))
or (stat.schema_name || '.' ||stat.table_name in (select
table_nm_onl_act from tbls_w_onl_actl_data));
+
+-- ORCA should fallback when a CTE over a replicated table is referenced
+-- from multiple scalar subqueries.
+-- ss_t1 needs enough rows (40000) to push ORCA to the cross-slice plan;
+-- with fewer rows the bug does not manifest and the test would silently
+-- pass even without the fix.
+-- start_ignore
+DROP TABLE IF EXISTS ss_t1, ss_t2;
+-- end_ignore
+CREATE TABLE ss_t1 AS
+ SELECT generate_series(1, 40000) id
+ DISTRIBUTED BY (id);
+CREATE TABLE ss_t2 AS
+ SELECT * FROM (VALUES (1, 10), (2, 20)) AS v(id, v)
+ DISTRIBUTED REPLICATED;
+ANALYZE ss_t1;
+ANALYZE ss_t2;
+
+SET statement_timeout = '15s';
+WITH
+ cte1 AS (SELECT v FROM ss_t2 WHERE id = 1),
+ cte2 AS (SELECT v FROM ss_t2 WHERE id = 2)
+ SELECT (SELECT v FROM cte1) + (SELECT v FROM cte2) +
+ (SELECT v FROM cte1) + (SELECT v FROM cte2) AS result
+ FROM ss_t1
+ LIMIT 1;
+RESET statement_timeout;
+DROP TABLE ss_t1, ss_t2;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]