Copilot commented on code in PR #1719:
URL: https://github.com/apache/cloudberry/pull/1719#discussion_r3193847884


##########
src/backend/gporca/libgpopt/src/base/CUtils.cpp:
##########
@@ -978,6 +978,180 @@ CUtils::FHasCTEAnchor(CExpression *pexpr)
        return false;
 }
 
+// return CTEConsumers' and a set of CTEProducers' CTE ids in the given subtree
+void
+CUtils::CollectConsumersAndProducers(CMemoryPool *mp, CExpression *pexpr,
+                                                                        
ULongPtrArray *cteConsumers,
+                                                                        
UlongCteIdHashSet *cteProducerSet)
+{

Review Comment:
   These new helpers are recursive but don’t call GPOS_CHECK_STACK_SIZE (unlike 
nearby recursive walkers such as FHasCTEAnchor). Consider adding 
GPOS_CHECK_STACK_SIZE at the top of CollectConsumersAndProducers() (and any 
other recursive helpers added here) to avoid stack overflow on deep expression 
trees.
   



##########
src/backend/gporca/libgpopt/src/base/CUtils.cpp:
##########
@@ -978,6 +978,180 @@ CUtils::FHasCTEAnchor(CExpression *pexpr)
        return false;
 }
 
+// return CTEConsumers' and a set of CTEProducers' CTE ids in the given subtree
+void
+CUtils::CollectConsumersAndProducers(CMemoryPool *mp, CExpression *pexpr,
+                                                                        
ULongPtrArray *cteConsumers,
+                                                                        
UlongCteIdHashSet *cteProducerSet)
+{
+       COperator *pop = pexpr->Pop();
+
+       if (COperator::EopPhysicalCTEConsumer == pop->Eopid())
+       {
+               cteConsumers->Append(GPOS_NEW(mp) ULONG(
+                       CPhysicalCTEConsumer::PopConvert(pop)->UlCTEId()));
+       }
+       else if (COperator::EopPhysicalCTEProducer == pop->Eopid())
+       {
+               cteProducerSet->Insert(GPOS_NEW(mp) ULONG(
+                       CPhysicalCTEProducer::PopConvert(pop)->UlCTEId()));
+       }
+
+       for (ULONG ul = 0; ul < pexpr->Arity(); ul++)
+       {
+               CExpression *pexprChild = (*pexpr)[ul];
+
+               if (!pexprChild->Pop()->FScalar())
+               {
+                       CollectConsumersAndProducers(mp, pexprChild, 
cteConsumers,
+                                                                               
 cteProducerSet);
+               }
+       }
+}
+
+BOOL
+CUtils::hasUnpairedCTEConsumer(CMemoryPool *mp, CExpression *pexpr)
+{
+       BOOL hasUnpairedConsumer = false;
+
+       ULongPtrArray *cteConsumers = GPOS_NEW(mp) ULongPtrArray(mp);
+       UlongCteIdHashSet *cteProducerSet = GPOS_NEW(mp) UlongCteIdHashSet(mp);
+
+       CollectConsumersAndProducers(mp, pexpr, cteConsumers, cteProducerSet);
+
+       // check if every consumer's producer is in ProducerSet
+       for (ULONG ul = 0; ul < cteConsumers->Size(); ul++)
+       {
+               if (!cteProducerSet->Contains((*cteConsumers)[ul]))
+               {
+                       hasUnpairedConsumer = true;
+                       break;
+               }
+       }
+       cteConsumers->Release();
+       cteProducerSet->Release();
+
+       return hasUnpairedConsumer;
+}
+
+// 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)
+{

Review Comment:
   CollectCTESlices() is also recursive and currently lacks a 
GPOS_CHECK_STACK_SIZE guard. Adding it at the top of the function would align 
with other deep-tree walkers in this file and reduce the risk of stack overflow 
on deeply nested plans.
   



##########
src/backend/gporca/libgpopt/include/gpopt/base/CUtils.h:
##########
@@ -1026,6 +1026,21 @@ class CUtils
        static CTableDescriptorHashSet *RemoveDuplicateMdids(
                CMemoryPool *mp, CTableDescriptorHashSet *tabdescs);
 
+       // hash set from CTE ids
+       typedef CHashSet<ULONG, gpos::HashValue<ULONG>, gpos::Equals<ULONG>,
+                                        CleanupDelete<ULONG> >
+               UlongCteIdHashSet;
+
+       static void CollectConsumersAndProducers(CMemoryPool *mp,
+                                                                               
         CExpression *pexpr,
+                                                                               
         ULongPtrArray *cteConsumers,
+                                                                               
         UlongCteIdHashSet *cteProducerSet);
+
+       static BOOL hasUnpairedCTEConsumer(CMemoryPool *mp, CExpression *pexpr);

Review Comment:
   Naming is inconsistent with surrounding boolean helpers (e.g., 
FHasCTEAnchor, FHasSubqueryOrApply): hasUnpairedCTEConsumer() starts with a 
lowercase 'h' and lacks the usual 'F' prefix. Consider renaming to match 
existing conventions to keep the public CUtils API consistent.
   



##########
src/test/regress/sql/shared_scan.sql:
##########
@@ -120,3 +120,28 @@ 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.
+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;

Review Comment:
   The test creates ss_t1/ss_t2 without first dropping them. To make the script 
more robust to partial runs or reruns (e.g., after a failure), consider adding 
a guarded `DROP TABLE IF EXISTS ss_t1, ss_t2;` before the CREATE TABLE 
statements (similar to earlier patterns in this file).



##########
src/backend/gporca/libgpopt/src/translate/CTranslatorExprToDXL.cpp:
##########
@@ -4250,6 +4264,10 @@ CTranslatorExprToDXL::BuildScalarSubplans(
 {
        const ULONG size = pdrgpcrInner->Size();
 
+       // Fallback to Postgres optimizer if the SubPlan's inner expression 
contains a
+       // CTE Consumer whose Producer lives outside this subtree. Such a 
Consumer
+       // would become a cross-slice Shared Scan reader without a local 
Producer,
+       // which can hang the query or fail at execution time.

Review Comment:
   This comment says the function will fall back to the Postgres optimizer when 
the SubPlan’s inner expression contains an unpaired CTE Consumer, but there is 
no corresponding check/raise in the code below. Either implement the described 
fallback (e.g., by detecting the condition before translating) or adjust/remove 
the comment to avoid misleading future maintainers.
   



##########
src/backend/gporca/libgpopt/include/gpopt/base/CUtils.h:
##########
@@ -1026,6 +1026,21 @@ class CUtils
        static CTableDescriptorHashSet *RemoveDuplicateMdids(
                CMemoryPool *mp, CTableDescriptorHashSet *tabdescs);
 
+       // hash set from CTE ids
+       typedef CHashSet<ULONG, gpos::HashValue<ULONG>, gpos::Equals<ULONG>,
+                                        CleanupDelete<ULONG> >
+               UlongCteIdHashSet;
+
+       static void CollectConsumersAndProducers(CMemoryPool *mp,
+                                                                               
         CExpression *pexpr,
+                                                                               
         ULongPtrArray *cteConsumers,
+                                                                               
         UlongCteIdHashSet *cteProducerSet);
+
+       static BOOL hasUnpairedCTEConsumer(CMemoryPool *mp, CExpression *pexpr);
+
+       static BOOL FHasCrossSliceReplicatedCTEConsumer(CMemoryPool *mp,
+                                                                               
                        CExpression *pexpr);

Review Comment:
   CollectConsumersAndProducers() / hasUnpairedCTEConsumer() are added to the 
public CUtils API but appear unused in the codebase (no call sites found). If 
they’re not needed, consider removing them or making them file-local to avoid 
expanding the public surface area; if they are intended to be used, wiring them 
into the relevant translation path would make that intent clear.



##########
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(
+                       gpdxl::ExmaDXL, gpdxl::ExmiExpr2DXLUnsupportedFeature,

Review Comment:
   This file generally qualifies DXL fallback exceptions via the gpopt 
namespace (e.g., gpopt::ExmaDXL / gpopt::ExmiExpr2DXLUnsupportedFeature 
elsewhere in this file). For consistency, consider using the same qualification 
here as well (or unqualified, since gpdxl is brought into gpopt in the header).
   



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