This is an automated email from the ASF dual-hosted git repository.
yjhjstz pushed a commit to branch REL_2_STABLE
in repository https://gitbox.apache.org/repos/asf/cloudberry.git
The following commit(s) were added to refs/heads/REL_2_STABLE by this push:
new f192ed98230 ORCA: fix CTE column-pruning misalignment across consumers
f192ed98230 is described below
commit f192ed982305f1dbbee871f88f6f26770d83e6d9
Author: Jianghua Yang <[email protected]>
AuthorDate: Tue Aug 4 19:17:46 2026 +0800
ORCA: fix CTE column-pruning misalignment across consumers
When a CTE has multiple consumers requiring different column subsets,
the producer's shared-scan output is pruned to the union of all
consumers' required columns (CTranslatorDXLToExpr::PruneCTEs). But each
consumer independently decided its own output columns in
CPhysicalCTEConsumer from its own per-column GetUsage(). A consumer that
considers all of its columns used (e.g. SELECT a.*) kept every column
with an identity index map, while the producer emitted only the pruned
union. The consumer then read the shared tuple by stale positions,
producing wrong results (a join key read from the wrong slot -> LEFT
JOIN yields NULLs) and "invalid attnum N for relation shareX_refY"
during EXPLAIN.
Drive the consumer's kept columns from the producer's finalized used
mask (CLogicalCTEProducer::UsedMask) -- the single source of truth --
so every consumer exposes exactly the producer's surviving columns.
When the producer was not pruned the mask is NULL and behavior is
unchanged.
Add a regression test (cte_prune_multi_consumer) covering multi-consumer
CTEs where a SELECT * consumer is referenced directly with a join key.
It asserts the EXPLAIN no longer errors and returns correct results;
the ORCA path is exercised with shared scans and matches the Postgres
planner.
(cherry picked from commit 3d1c8cdf79af159e82cf22c0f08b1f343996efe8)
---
.../src/operators/CPhysicalCTEConsumer.cpp | 23 ++-
.../regress/expected/cte_prune_multi_consumer.out | 196 +++++++++++++++++++++
.../cte_prune_multi_consumer_optimizer.out | 194 ++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/cte_prune_multi_consumer.sql | 92 ++++++++++
5 files changed, 504 insertions(+), 3 deletions(-)
diff --git a/src/backend/gporca/libgpopt/src/operators/CPhysicalCTEConsumer.cpp
b/src/backend/gporca/libgpopt/src/operators/CPhysicalCTEConsumer.cpp
index d7a51950fa4..d3ed7de5696 100644
--- a/src/backend/gporca/libgpopt/src/operators/CPhysicalCTEConsumer.cpp
+++ b/src/backend/gporca/libgpopt/src/operators/CPhysicalCTEConsumer.cpp
@@ -13,6 +13,7 @@
#include "gpos/base.h"
+#include "gpopt/base/CCTEInfo.h"
#include "gpopt/base/CCTEMap.h"
#include "gpopt/base/COptCtxt.h"
#include "gpopt/operators/CExpression.h"
@@ -45,12 +46,30 @@ CPhysicalCTEConsumer::CPhysicalCTEConsumer(CMemoryPool *mp,
ULONG id,
m_pdrgpcr = GPOS_NEW(mp) CColRefArray(mp);
m_pidxmap = GPOS_NEW(mp) ULongPtrArray(mp);
+ // ShareInputScan does not project, so the producer finalizes its
shared-scan
+ // output as the union of all consumers' required columns (see
+ // CTranslatorDXLToExpr::PruneCTEs). Therefore every consumer must
expose
+ // exactly the producer's surviving columns: it can neither read a
column the
+ // producer pruned (that would run past the shared tuple) nor decide
what to
+ // keep from its own per-column usage. Drive the consumer's kept set
from the
+ // producer's used mask, which is the single source of truth. When the
+ // producer was not pruned (mask is NULL) fall back to the previous
behavior.
+ CCTEInfo *pcteinfo = COptCtxt::PoctxtFromTLS()->Pcteinfo();
+ CLogicalCTEProducer *popProducer =
+
CLogicalCTEProducer::PopConvert(pcteinfo->PexprCTEProducer(m_id)->Pop());
+ BOOL *producer_umask = popProducer->UsedMask();
+ GPOS_ASSERT_IMP(nullptr != producer_umask,
+ popProducer->Pdrgpcr()->Size() ==
colref_size);
+
for (ULONG index = 0; index < colref_size; index++) {
CColRef *col_ref = (*colref_array)[index];
- if (col_ref->GetUsage(true, true) == CColRef::EUsed) {
+ BOOL kept = (nullptr != producer_umask)
+ ? producer_umask[index]
+ : (col_ref->GetUsage(true,
true) == CColRef::EUsed);
+ if (kept) {
m_pdrgpcr->Append(col_ref);
m_pidxmap->Append(GPOS_NEW(m_mp) ULONG(index));
- }
+ }
}
if (m_pidxmap->Size() == colref_size) {
diff --git a/src/test/regress/expected/cte_prune_multi_consumer.out
b/src/test/regress/expected/cte_prune_multi_consumer.out
new file mode 100644
index 00000000000..e59eccd3294
--- /dev/null
+++ b/src/test/regress/expected/cte_prune_multi_consumer.out
@@ -0,0 +1,196 @@
+--
+-- Multi-consumer CTE column pruning (ORCA).
+--
+-- A CTE referenced by several consumers has its shared-scan output pruned to
the
+-- union of all consumers' required columns. Because ShareInputScan does not
+-- project, every consumer must expose exactly the producer's surviving
columns.
+-- A consumer that projects a superset (e.g. SELECT *) and is referenced
directly
+-- with a join key used to keep an identity column map and read the shared
tuple
+-- by stale positions, producing wrong results (join key read from the wrong
slot
+-- -> LEFT JOIN yields NULLs) and "invalid attnum N for relation shareX_refY"
in
+-- EXPLAIN. See CPhysicalCTEConsumer. These queries assert correct results
under
+-- both optimizers.
+--
+create schema cte_mc;
+set search_path = cte_mc;
+create table policy(pr_id text, pied_id text, p_id text, amnt numeric(18,2),
+ guarantee_period text, main_risk text, premium
numeric(18,2));
+NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named
'pr_id' as the Apache Cloudberry data distribution key for this table.
+HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make
sure column(s) chosen are the optimal data distribution key to minimize skew.
+create table mid(p_id text, tenant_id text, act_premium numeric(18,2));
+NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named
'p_id' as the Apache Cloudberry data distribution key for this table.
+HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make
sure column(s) chosen are the optimal data distribution key to minimize skew.
+insert into policy values('pr1','k1','p1',300000,'1','Y',290);
+insert into mid values('p1','t1',290);
+analyze policy;
+analyze mid;
+-- The "info" consumer projects SELECT * but only p_id/amnt/main_risk are
needed
+-- outside; the producer prunes pr_id/pied_id/guarantee_period. i.amnt and
i.p_id
+-- must not come back NULL. EXPLAIN previously raised "invalid attnum".
+explain (costs off)
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where main_risk='Y' and p_id='p1'),
+ unused as (select 1 from mid)
+select i.amnt, m.tenant_id, m.p_id, i.p_id, d.p_id, d.premium, m.act_premium
+from mid m
+left join det d on d.p_id=m.p_id and d.premium=m.act_premium
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3,4;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ Merge Key: i.amnt, m.tenant_id, i.p_id
+ -> Sort
+ Sort Key: i.amnt, m.tenant_id, i.p_id
+ -> Hash Left Join
+ Hash Cond: (m.p_id = i.p_id)
+ -> Hash Right Join
+ Hash Cond: ((agent.p_id = m.p_id) AND
((sum(agent.premium)) = m.act_premium))
+ -> Redistribute Motion 3:3 (slice2; segments: 3)
+ Hash Key: 'p1'::text
+ -> GroupAggregate
+ Group Key: agent.p_id
+ -> Redistribute Motion 3:3 (slice3;
segments: 3)
+ Hash Key: agent.p_id
+ -> Subquery Scan on agent
+ -> Seq Scan on policy
+ Filter: (p_id = 'p1'::text)
+ -> Hash
+ -> Seq Scan on mid m
+ Filter: (p_id = 'p1'::text)
+ -> Hash
+ -> Redistribute Motion 3:3 (slice4; segments: 3)
+ Hash Key: 'p1'::text
+ -> Subquery Scan on i
+ -> Seq Scan on policy policy_1
+ Filter: ((main_risk = 'Y'::text) AND
(p_id = 'p1'::text))
+ Optimizer: Postgres query optimizer
+(27 rows)
+
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where main_risk='Y' and p_id='p1'),
+ unused as (select 1 from mid)
+select i.amnt, m.tenant_id, m.p_id, i.p_id, d.p_id, d.premium, m.act_premium
+from mid m
+left join det d on d.p_id=m.p_id and d.premium=m.act_premium
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3,4;
+ amnt | tenant_id | p_id | p_id | p_id | premium | act_premium
+-----------+-----------+------+------+------+---------+-------------
+ 300000.00 | t1 | p1 | p1 | p1 | 290.00 | 290.00
+(1 row)
+
+-- Variant: project a different surviving column (main_risk) from the SELECT *
+-- consumer to exercise a different pruned layout.
+explain (costs off)
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where p_id='p1')
+select m.p_id, i.main_risk, i.amnt, d.premium
+from mid m
+left join det d on d.p_id=m.p_id
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3;
+ QUERY PLAN
+------------------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ Merge Key: i.main_risk, i.amnt
+ -> Sort
+ Sort Key: i.main_risk, i.amnt
+ -> Hash Left Join
+ Hash Cond: (m.p_id = i.p_id)
+ -> Hash Right Join
+ Hash Cond: (d.p_id = m.p_id)
+ -> Redistribute Motion 3:3 (slice2; segments: 3)
+ Hash Key: 'p1'::text
+ -> Subquery Scan on d
+ -> GroupAggregate
+ Group Key: agent.p_id
+ -> Redistribute Motion 3:3 (slice3;
segments: 3)
+ Hash Key: agent.p_id
+ -> Subquery Scan on agent
+ -> Seq Scan on policy
+ Filter: (p_id =
'p1'::text)
+ -> Hash
+ -> Seq Scan on mid m
+ Filter: (p_id = 'p1'::text)
+ -> Hash
+ -> Redistribute Motion 3:3 (slice4; segments: 3)
+ Hash Key: 'p1'::text
+ -> Subquery Scan on i
+ -> Seq Scan on policy policy_1
+ Filter: (p_id = 'p1'::text)
+ Optimizer: Postgres query optimizer
+(28 rows)
+
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where p_id='p1')
+select m.p_id, i.main_risk, i.amnt, d.premium
+from mid m
+left join det d on d.p_id=m.p_id
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3;
+ p_id | main_risk | amnt | premium
+------+-----------+-----------+---------
+ p1 | Y | 300000.00 | 290.00
+(1 row)
+
+-- Three consumers, each needing a different subset (join key on each).
+explain (costs off)
+with agent as (select p_id, amnt, main_risk, premium from policy)
+select x.p_id, y.amnt, z.main_risk
+from agent x
+join agent y on x.p_id=y.p_id
+join agent z on x.p_id=z.p_id
+order by 1,2,3;
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ Merge Key: x.p_id, y.amnt, z.main_risk
+ -> Sort
+ Sort Key: x.p_id, y.amnt, z.main_risk
+ -> Hash Join
+ Hash Cond: (x.p_id = z.p_id)
+ -> Hash Join
+ Hash Cond: (x.p_id = y.p_id)
+ -> Redistribute Motion 3:3 (slice2; segments: 3)
+ Hash Key: x.p_id
+ -> Subquery Scan on x
+ -> Seq Scan on policy
+ -> Hash
+ -> Redistribute Motion 3:3 (slice3; segments: 3)
+ Hash Key: y.p_id
+ -> Subquery Scan on y
+ -> Seq Scan on policy policy_1
+ -> Hash
+ -> Redistribute Motion 3:3 (slice4; segments: 3)
+ Hash Key: z.p_id
+ -> Subquery Scan on z
+ -> Seq Scan on policy policy_2
+ Optimizer: Postgres query optimizer
+(23 rows)
+
+with agent as (select p_id, amnt, main_risk, premium from policy)
+select x.p_id, y.amnt, z.main_risk
+from agent x
+join agent y on x.p_id=y.p_id
+join agent z on x.p_id=z.p_id
+order by 1,2,3;
+ p_id | amnt | main_risk
+------+-----------+-----------
+ p1 | 300000.00 | Y
+(1 row)
+
+-- start_ignore
+drop schema cte_mc cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table policy
+drop cascades to table mid
+-- end_ignore
diff --git a/src/test/regress/expected/cte_prune_multi_consumer_optimizer.out
b/src/test/regress/expected/cte_prune_multi_consumer_optimizer.out
new file mode 100644
index 00000000000..59b8b0eba3f
--- /dev/null
+++ b/src/test/regress/expected/cte_prune_multi_consumer_optimizer.out
@@ -0,0 +1,194 @@
+--
+-- Multi-consumer CTE column pruning (ORCA).
+--
+-- A CTE referenced by several consumers has its shared-scan output pruned to
the
+-- union of all consumers' required columns. Because ShareInputScan does not
+-- project, every consumer must expose exactly the producer's surviving
columns.
+-- A consumer that projects a superset (e.g. SELECT *) and is referenced
directly
+-- with a join key used to keep an identity column map and read the shared
tuple
+-- by stale positions, producing wrong results (join key read from the wrong
slot
+-- -> LEFT JOIN yields NULLs) and "invalid attnum N for relation shareX_refY"
in
+-- EXPLAIN. See CPhysicalCTEConsumer. These queries assert correct results
under
+-- both optimizers.
+--
+create schema cte_mc;
+set search_path = cte_mc;
+create table policy(pr_id text, pied_id text, p_id text, amnt numeric(18,2),
+ guarantee_period text, main_risk text, premium
numeric(18,2));
+NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named
'pr_id' as the Apache Cloudberry data distribution key for this table.
+HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make
sure column(s) chosen are the optimal data distribution key to minimize skew.
+create table mid(p_id text, tenant_id text, act_premium numeric(18,2));
+NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named
'p_id' as the Apache Cloudberry data distribution key for this table.
+HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make
sure column(s) chosen are the optimal data distribution key to minimize skew.
+insert into policy values('pr1','k1','p1',300000,'1','Y',290);
+insert into mid values('p1','t1',290);
+analyze policy;
+analyze mid;
+-- The "info" consumer projects SELECT * but only p_id/amnt/main_risk are
needed
+-- outside; the producer prunes pr_id/pied_id/guarantee_period. i.amnt and
i.p_id
+-- must not come back NULL. EXPLAIN previously raised "invalid attnum".
+explain (costs off)
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where main_risk='Y' and p_id='p1'),
+ unused as (select 1 from mid)
+select i.amnt, m.tenant_id, m.p_id, i.p_id, d.p_id, d.premium, m.act_premium
+from mid m
+left join det d on d.p_id=m.p_id and d.premium=m.act_premium
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3,4;
+ QUERY
PLAN
+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ Merge Key: share0_ref3.amnt, m.tenant_id, m.p_id, share0_ref3.p_id
+ -> Sort
+ Sort Key: share0_ref3.amnt, m.tenant_id, m.p_id, share0_ref3.p_id
+ -> Sequence
+ -> Shared Scan (share slice:id 1:0)
+ -> Redistribute Motion 3:3 (slice2; segments: 3)
+ Hash Key: policy.p_id
+ -> Seq Scan on policy
+ -> Hash Left Join
+ Hash Cond: ((m.p_id = share0_ref2.p_id) AND
(m.act_premium = (sum(share0_ref2.premium))))
+ -> Hash Left Join
+ Hash Cond: (m.p_id = share0_ref3.p_id)
+ -> Seq Scan on mid m
+ Filter: (p_id = 'p1'::text)
+ -> Hash
+ -> Result
+ Filter: ((share0_ref3.main_risk =
'Y'::text) AND (share0_ref3.p_id = 'p1'::text) AND (share0_ref3.p_id =
'p1'::text))
+ -> Shared Scan (share slice:id 1:0)
+ -> Hash
+ -> Result
+ Filter: (share0_ref2.p_id = 'p1'::text)
+ -> GroupAggregate
+ Group Key: share0_ref2.p_id
+ -> Sort
+ Sort Key: share0_ref2.p_id
+ -> Shared Scan (share slice:id
1:0)
+ Optimizer: GPORCA
+(28 rows)
+
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where main_risk='Y' and p_id='p1'),
+ unused as (select 1 from mid)
+select i.amnt, m.tenant_id, m.p_id, i.p_id, d.p_id, d.premium, m.act_premium
+from mid m
+left join det d on d.p_id=m.p_id and d.premium=m.act_premium
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3,4;
+ amnt | tenant_id | p_id | p_id | p_id | premium | act_premium
+-----------+-----------+------+------+------+---------+-------------
+ 300000.00 | t1 | p1 | p1 | p1 | 290.00 | 290.00
+(1 row)
+
+-- Variant: project a different surviving column (main_risk) from the SELECT *
+-- consumer to exercise a different pruned layout.
+explain (costs off)
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where p_id='p1')
+select m.p_id, i.main_risk, i.amnt, d.premium
+from mid m
+left join det d on d.p_id=m.p_id
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ Merge Key: m.p_id, share0_ref3.main_risk, share0_ref3.amnt
+ -> Sort
+ Sort Key: m.p_id, share0_ref3.main_risk, share0_ref3.amnt
+ -> Sequence
+ -> Shared Scan (share slice:id 1:0)
+ -> Redistribute Motion 3:3 (slice2; segments: 3)
+ Hash Key: policy.p_id
+ -> Seq Scan on policy
+ -> Hash Left Join
+ Hash Cond: (m.p_id = share0_ref2.p_id)
+ -> Hash Left Join
+ Hash Cond: (m.p_id = share0_ref3.p_id)
+ -> Seq Scan on mid m
+ Filter: (p_id = 'p1'::text)
+ -> Hash
+ -> Result
+ Filter: ((share0_ref3.p_id =
'p1'::text) AND (share0_ref3.p_id = 'p1'::text))
+ -> Shared Scan (share slice:id 1:0)
+ -> Hash
+ -> Result
+ Filter: (share0_ref2.p_id = 'p1'::text)
+ -> GroupAggregate
+ Group Key: share0_ref2.p_id
+ -> Sort
+ Sort Key: share0_ref2.p_id
+ -> Shared Scan (share slice:id
1:0)
+ Optimizer: GPORCA
+(28 rows)
+
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where p_id='p1')
+select m.p_id, i.main_risk, i.amnt, d.premium
+from mid m
+left join det d on d.p_id=m.p_id
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3;
+ p_id | main_risk | amnt | premium
+------+-----------+-----------+---------
+ p1 | Y | 300000.00 | 290.00
+(1 row)
+
+-- Three consumers, each needing a different subset (join key on each).
+explain (costs off)
+with agent as (select p_id, amnt, main_risk, premium from policy)
+select x.p_id, y.amnt, z.main_risk
+from agent x
+join agent y on x.p_id=y.p_id
+join agent z on x.p_id=z.p_id
+order by 1,2,3;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Gather Motion 3:1 (slice1; segments: 3)
+ Merge Key: share0_ref2.p_id, share0_ref4.amnt, share0_ref3.main_risk
+ -> Sort
+ Sort Key: share0_ref2.p_id, share0_ref4.amnt, share0_ref3.main_risk
+ -> Sequence
+ -> Shared Scan (share slice:id 1:0)
+ -> Seq Scan on policy
+ -> Hash Join
+ Hash Cond: (share0_ref4.p_id = share0_ref2.p_id)
+ -> Shared Scan (share slice:id 1:0)
+ -> Hash
+ -> Broadcast Motion 3:3 (slice2; segments: 3)
+ -> Hash Join
+ Hash Cond: (share0_ref3.p_id =
share0_ref2.p_id)
+ -> Shared Scan (share slice:id 2:0)
+ -> Hash
+ -> Broadcast Motion 3:3
(slice3; segments: 3)
+ -> Result
+ -> Shared Scan
(share slice:id 3:0)
+ Optimizer: GPORCA
+(20 rows)
+
+with agent as (select p_id, amnt, main_risk, premium from policy)
+select x.p_id, y.amnt, z.main_risk
+from agent x
+join agent y on x.p_id=y.p_id
+join agent z on x.p_id=z.p_id
+order by 1,2,3;
+ p_id | amnt | main_risk
+------+-----------+-----------
+ p1 | 300000.00 | Y
+(1 row)
+
+-- start_ignore
+drop schema cte_mc cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table policy
+drop cascades to table mid
+-- end_ignore
diff --git a/src/test/regress/parallel_schedule
b/src/test/regress/parallel_schedule
index 79eb980de74..e5551f42ecb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -166,7 +166,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding
jsonb_jsonpath
# NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion
truncate alter_table sequence polymorphism rowtypes returning with xml cte_prune
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion
truncate alter_table sequence polymorphism rowtypes returning with xml
cte_prune cte_prune_multi_consumer
# large objects are not supported by GPDB
# test: largeobject
diff --git a/src/test/regress/sql/cte_prune_multi_consumer.sql
b/src/test/regress/sql/cte_prune_multi_consumer.sql
new file mode 100644
index 00000000000..6e082547348
--- /dev/null
+++ b/src/test/regress/sql/cte_prune_multi_consumer.sql
@@ -0,0 +1,92 @@
+--
+-- Multi-consumer CTE column pruning (ORCA).
+--
+-- A CTE referenced by several consumers has its shared-scan output pruned to
the
+-- union of all consumers' required columns. Because ShareInputScan does not
+-- project, every consumer must expose exactly the producer's surviving
columns.
+-- A consumer that projects a superset (e.g. SELECT *) and is referenced
directly
+-- with a join key used to keep an identity column map and read the shared
tuple
+-- by stale positions, producing wrong results (join key read from the wrong
slot
+-- -> LEFT JOIN yields NULLs) and "invalid attnum N for relation shareX_refY"
in
+-- EXPLAIN. See CPhysicalCTEConsumer. These queries assert correct results
under
+-- both optimizers.
+--
+create schema cte_mc;
+set search_path = cte_mc;
+
+create table policy(pr_id text, pied_id text, p_id text, amnt numeric(18,2),
+ guarantee_period text, main_risk text, premium
numeric(18,2));
+create table mid(p_id text, tenant_id text, act_premium numeric(18,2));
+insert into policy values('pr1','k1','p1',300000,'1','Y',290);
+insert into mid values('p1','t1',290);
+analyze policy;
+analyze mid;
+
+-- The "info" consumer projects SELECT * but only p_id/amnt/main_risk are
needed
+-- outside; the producer prunes pr_id/pied_id/guarantee_period. i.amnt and
i.p_id
+-- must not come back NULL. EXPLAIN previously raised "invalid attnum".
+explain (costs off)
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where main_risk='Y' and p_id='p1'),
+ unused as (select 1 from mid)
+select i.amnt, m.tenant_id, m.p_id, i.p_id, d.p_id, d.premium, m.act_premium
+from mid m
+left join det d on d.p_id=m.p_id and d.premium=m.act_premium
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3,4;
+
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where main_risk='Y' and p_id='p1'),
+ unused as (select 1 from mid)
+select i.amnt, m.tenant_id, m.p_id, i.p_id, d.p_id, d.premium, m.act_premium
+from mid m
+left join det d on d.p_id=m.p_id and d.premium=m.act_premium
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3,4;
+
+-- Variant: project a different surviving column (main_risk) from the SELECT *
+-- consumer to exercise a different pruned layout.
+explain (costs off)
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where p_id='p1')
+select m.p_id, i.main_risk, i.amnt, d.premium
+from mid m
+left join det d on d.p_id=m.p_id
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3;
+
+with agent as (select
pr_id,pied_id,p_id,amnt,guarantee_period,main_risk,premium from policy),
+ det as (select p_id, sum(premium) premium from agent group by p_id),
+ info as (select * from agent where p_id='p1')
+select m.p_id, i.main_risk, i.amnt, d.premium
+from mid m
+left join det d on d.p_id=m.p_id
+left join info i on m.p_id=i.p_id
+where m.p_id='p1'
+order by 1,2,3;
+
+-- Three consumers, each needing a different subset (join key on each).
+explain (costs off)
+with agent as (select p_id, amnt, main_risk, premium from policy)
+select x.p_id, y.amnt, z.main_risk
+from agent x
+join agent y on x.p_id=y.p_id
+join agent z on x.p_id=z.p_id
+order by 1,2,3;
+
+with agent as (select p_id, amnt, main_risk, premium from policy)
+select x.p_id, y.amnt, z.main_risk
+from agent x
+join agent y on x.p_id=y.p_id
+join agent z on x.p_id=z.p_id
+order by 1,2,3;
+
+-- start_ignore
+drop schema cte_mc cascade;
+-- end_ignore
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]