From c507801c54acba596de14125f9d6451bb0c99624 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Tue, 24 Jun 2025 12:42:47 +0000 Subject: [PATCH 1/7] Add ExprReferencesPartitionColumn function to check partition column references in queries --- .../planner/query_pushdown_planning.c | 102 ++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/src/backend/distributed/planner/query_pushdown_planning.c b/src/backend/distributed/planner/query_pushdown_planning.c index 6150f4987..e4c859d10 100644 --- a/src/backend/distributed/planner/query_pushdown_planning.c +++ b/src/backend/distributed/planner/query_pushdown_planning.c @@ -113,6 +113,8 @@ static Var * PartitionColumnForPushedDownSubquery(Query *query); static bool ContainsReferencesToRelids(Query *query, Relids relids, int *foundRelid); static bool ContainsReferencesToRelidsWalker(Node *node, RelidsReferenceWalkerContext *context); +static bool +ExprReferencesPartitionColumn(Node *node, Query *query); /* @@ -1036,8 +1038,13 @@ DeferErrorIfCannotPushdownSubquery(Query *subqueryTree, bool outerMostQueryHasLi errorDetail = "For Update/Share commands are currently unsupported"; } - /* grouping sets are not allowed in subqueries*/ - if (subqueryTree->groupingSets) + /* real GROUPING SETS / ROLLUP / CUBE are still disallowed; + * but PG 18 also fills groupingSets with a single simple item + * when the GROUP BY list contains un-projected columns. Allow that. */ + if (subqueryTree->groupingSets && + !(list_length(subqueryTree->groupingSets) == 1 && + ((GroupingSet *) linitial(subqueryTree->groupingSets))->kind + == GROUPING_SET_SIMPLE)) { preconditionsSatisfied = false; errorDetail = "could not run distributed query with GROUPING SETS, CUBE, " @@ -1097,12 +1104,27 @@ DeferErrorIfSubqueryRequiresMerge(Query *subqueryTree, bool lateral, /* group clause list must include partition column */ if (subqueryTree->groupClause) { - List *groupClauseList = subqueryTree->groupClause; - List *targetEntryList = subqueryTree->targetList; - List *groupTargetEntryList = GroupTargetEntryList(groupClauseList, - targetEntryList); - bool groupOnPartitionColumn = - TargetListOnPartitionColumn(subqueryTree, groupTargetEntryList); + bool groupOnPartitionColumn = false; + + /* Walk every GROUP BY expression. */ + ListCell *lc; + foreach (lc, subqueryTree->groupClause) + { + Expr *expr = (Expr *) + get_sortgroupclause_expr( + (SortGroupClause *) lfirst(lc), + subqueryTree->targetList); + + expr = (Expr *) strip_implicit_coercions((Node *) expr); + + if (ExprReferencesPartitionColumn((Node *) expr, subqueryTree)) + { + groupOnPartitionColumn = true; + break; + } + } + + if (!groupOnPartitionColumn) { preconditionsSatisfied = false; @@ -1171,6 +1193,70 @@ DeferErrorIfSubqueryRequiresMerge(Query *subqueryTree, bool lateral, } +/* + * ExprReferencesPartitionColumn + * + * Recursively return true iff `node` ultimately refers to the partition + * (distribution) column of *any* distributed table in `query`. + * + * Handles: + * • plain Vars that point straight at RTE_RELATION + * • OUTER_VAR indirection (PG ≥ 18 resjunk columns) + * • Vars that point at an RTE_GROUP (PG ≥ 18); we descend into + * its groupexprs until we reach real relations. + */ +static bool +ExprReferencesPartitionColumn(Node *node, Query *query) +{ + if (node == NULL) + return false; + + /* 1. Plain or OUTER_VAR Var ---------------------------------------- */ + if (IsA(node, Var)) + { + Var *v = (Var *) node; + + /* Follow the OUTER_VAR → targetlist indirection exactly once */ + if (v->varno == OUTER_VAR) + { + TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); + return tle && ExprReferencesPartitionColumn((Node *) tle->expr, query); + } + + /* Sanity: varno must fit in rtable */ + if (v->varno <= 0 || v->varno > list_length(query->rtable)) + return false; + + RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); + + /* 1a. Real relation: compare attnos ---------------------------- */ + if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) + { + Var *partcol = DistPartitionKey(rte->relid); + return partcol && partcol->varattno == v->varattno; + } + + /* 1b. Synthetic GROUP entry: descend into its expressions ------- */ + if (rte->rtekind == RTE_GROUP && rte->groupexprs) + { + ListCell *lc; + foreach (lc, rte->groupexprs) + { + if (ExprReferencesPartitionColumn((Node *) lfirst(lc), query)) + return true; + } + } + + return false; /* other RTE kinds are irrelevant here */ + } + + /* 2. Any other node type: walk its children ------------------------ */ + return expression_tree_walker(node, + ExprReferencesPartitionColumn, + (void *) query); +} + + /* * DeferErrorIfUnsupportedTableCombination checks if the given query tree contains any * unsupported range table combinations. For this, the function walks over all From e0206e141db8597cf2ca3fcd1e00fd407ec2977a Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Wed, 25 Jun 2025 14:03:41 +0000 Subject: [PATCH 2/7] Add ExprMentionsPartitionColumn function to identify partition column references in queries --- .../planner/multi_logical_planner.c | 70 +++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index d1e2e6bca..37a00aedd 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -136,6 +136,8 @@ static MultiNode * ApplyCartesianProductReferenceJoin(MultiNode *leftNode, static MultiNode * ApplyCartesianProduct(MultiNode *leftNode, MultiNode *rightNode, List *partitionColumnList, JoinType joinType, List *joinClauses); +static bool +ExprMentionsPartitionColumn(Node *node, Query *query); /* @@ -224,14 +226,14 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell); Expr *targetExpression = targetEntry->expr; - bool skipOuterVars = true; - bool isPartitionColumn = IsPartitionColumn(targetExpression, query, - skipOuterVars); + bool isPartitionColumn = ExprMentionsPartitionColumn((Node *) targetExpression, + query); + Var *column = NULL; RangeTblEntry *rte = NULL; FindReferencedTableColumn(targetExpression, NIL, query, &column, &rte, - skipOuterVars); + /*skipOuterVars=*/false); Oid relationId = rte ? rte->relid : InvalidOid; /* @@ -292,6 +294,66 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) } +/* + * ExprMentionsPartitionColumn + * + * Return true iff `node` ultimately resolves to the partition column of + * *any* distributed table referenced by `query`. + * + * • Understands OUTER_VAR indirection (PG ≥ 17) + * • Understands synthetic RTE_GROUP entries (PG ≥ 18) + * • Falls back to original logic for PG 15/16 automatically + */ +static bool +ExprMentionsPartitionColumn(Node *node, Query *query) +{ + if (node == NULL) + return false; + + if (IsA(node, Var)) + { + Var *v = (Var *) node; + + /* Follow OUTER_VAR → target-list indirection, if present */ + if (v->varno == OUTER_VAR) + { + TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); + return tle && ExprMentionsPartitionColumn((Node *) tle->expr, query); + } + + /* Sanity-check varno */ + if (v->varno <= 0 || v->varno > list_length(query->rtable)) + return false; + + RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); + +#if PG_VERSION_NUM >= 180000 + /* Synthetic GROUP RTE – examine its expressions instead */ + if (rte->rtekind == RTE_GROUP && rte->groupexprs) + { + ListCell *lc; + foreach (lc, rte->groupexprs) + if (ExprMentionsPartitionColumn((Node *) lfirst(lc), query)) + return true; + return false; + } +#endif + /* Real table? — compare against its dist key */ + if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) + { + Var *partcol = DistPartitionKey(rte->relid); + return partcol && partcol->varattno == v->varattno; + } + return false; + } + + /* Recurse through any other node type */ + return expression_tree_walker(node, + ExprMentionsPartitionColumn, + (void *) query); +} + + /* * FindNodeMatchingCheckFunctionInRangeTableList finds a node for which the checker * function returns true. From f49caf8aca7a32d906557511102a294b99b9d2f4 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Wed, 25 Jun 2025 14:04:45 +0000 Subject: [PATCH 3/7] Revert "Add ExprReferencesPartitionColumn function to check partition column references in queries" This reverts commit c507801c54acba596de14125f9d6451bb0c99624. --- .../planner/query_pushdown_planning.c | 102 ++---------------- 1 file changed, 8 insertions(+), 94 deletions(-) diff --git a/src/backend/distributed/planner/query_pushdown_planning.c b/src/backend/distributed/planner/query_pushdown_planning.c index e4c859d10..6150f4987 100644 --- a/src/backend/distributed/planner/query_pushdown_planning.c +++ b/src/backend/distributed/planner/query_pushdown_planning.c @@ -113,8 +113,6 @@ static Var * PartitionColumnForPushedDownSubquery(Query *query); static bool ContainsReferencesToRelids(Query *query, Relids relids, int *foundRelid); static bool ContainsReferencesToRelidsWalker(Node *node, RelidsReferenceWalkerContext *context); -static bool -ExprReferencesPartitionColumn(Node *node, Query *query); /* @@ -1038,13 +1036,8 @@ DeferErrorIfCannotPushdownSubquery(Query *subqueryTree, bool outerMostQueryHasLi errorDetail = "For Update/Share commands are currently unsupported"; } - /* real GROUPING SETS / ROLLUP / CUBE are still disallowed; - * but PG 18 also fills groupingSets with a single simple item - * when the GROUP BY list contains un-projected columns. Allow that. */ - if (subqueryTree->groupingSets && - !(list_length(subqueryTree->groupingSets) == 1 && - ((GroupingSet *) linitial(subqueryTree->groupingSets))->kind - == GROUPING_SET_SIMPLE)) + /* grouping sets are not allowed in subqueries*/ + if (subqueryTree->groupingSets) { preconditionsSatisfied = false; errorDetail = "could not run distributed query with GROUPING SETS, CUBE, " @@ -1104,27 +1097,12 @@ DeferErrorIfSubqueryRequiresMerge(Query *subqueryTree, bool lateral, /* group clause list must include partition column */ if (subqueryTree->groupClause) { - bool groupOnPartitionColumn = false; - - /* Walk every GROUP BY expression. */ - ListCell *lc; - foreach (lc, subqueryTree->groupClause) - { - Expr *expr = (Expr *) - get_sortgroupclause_expr( - (SortGroupClause *) lfirst(lc), - subqueryTree->targetList); - - expr = (Expr *) strip_implicit_coercions((Node *) expr); - - if (ExprReferencesPartitionColumn((Node *) expr, subqueryTree)) - { - groupOnPartitionColumn = true; - break; - } - } - - + List *groupClauseList = subqueryTree->groupClause; + List *targetEntryList = subqueryTree->targetList; + List *groupTargetEntryList = GroupTargetEntryList(groupClauseList, + targetEntryList); + bool groupOnPartitionColumn = + TargetListOnPartitionColumn(subqueryTree, groupTargetEntryList); if (!groupOnPartitionColumn) { preconditionsSatisfied = false; @@ -1193,70 +1171,6 @@ DeferErrorIfSubqueryRequiresMerge(Query *subqueryTree, bool lateral, } -/* - * ExprReferencesPartitionColumn - * - * Recursively return true iff `node` ultimately refers to the partition - * (distribution) column of *any* distributed table in `query`. - * - * Handles: - * • plain Vars that point straight at RTE_RELATION - * • OUTER_VAR indirection (PG ≥ 18 resjunk columns) - * • Vars that point at an RTE_GROUP (PG ≥ 18); we descend into - * its groupexprs until we reach real relations. - */ -static bool -ExprReferencesPartitionColumn(Node *node, Query *query) -{ - if (node == NULL) - return false; - - /* 1. Plain or OUTER_VAR Var ---------------------------------------- */ - if (IsA(node, Var)) - { - Var *v = (Var *) node; - - /* Follow the OUTER_VAR → targetlist indirection exactly once */ - if (v->varno == OUTER_VAR) - { - TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); - return tle && ExprReferencesPartitionColumn((Node *) tle->expr, query); - } - - /* Sanity: varno must fit in rtable */ - if (v->varno <= 0 || v->varno > list_length(query->rtable)) - return false; - - RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); - - /* 1a. Real relation: compare attnos ---------------------------- */ - if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) - { - Var *partcol = DistPartitionKey(rte->relid); - return partcol && partcol->varattno == v->varattno; - } - - /* 1b. Synthetic GROUP entry: descend into its expressions ------- */ - if (rte->rtekind == RTE_GROUP && rte->groupexprs) - { - ListCell *lc; - foreach (lc, rte->groupexprs) - { - if (ExprReferencesPartitionColumn((Node *) lfirst(lc), query)) - return true; - } - } - - return false; /* other RTE kinds are irrelevant here */ - } - - /* 2. Any other node type: walk its children ------------------------ */ - return expression_tree_walker(node, - ExprReferencesPartitionColumn, - (void *) query); -} - - /* * DeferErrorIfUnsupportedTableCombination checks if the given query tree contains any * unsupported range table combinations. For this, the function walks over all From 128a259b15a3a1521b0acfc5959cf6c4427d6ab8 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Wed, 25 Jun 2025 14:06:38 +0000 Subject: [PATCH 4/7] indent --- .../planner/multi_logical_planner.c | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index 37a00aedd..a3595c92f 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -136,8 +136,7 @@ static MultiNode * ApplyCartesianProductReferenceJoin(MultiNode *leftNode, static MultiNode * ApplyCartesianProduct(MultiNode *leftNode, MultiNode *rightNode, List *partitionColumnList, JoinType joinType, List *joinClauses); -static bool -ExprMentionsPartitionColumn(Node *node, Query *query); +static bool ExprMentionsPartitionColumn(Node *node, Query *query); /* @@ -227,13 +226,14 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) Expr *targetExpression = targetEntry->expr; bool isPartitionColumn = ExprMentionsPartitionColumn((Node *) targetExpression, - query); + query); Var *column = NULL; RangeTblEntry *rte = NULL; FindReferencedTableColumn(targetExpression, NIL, query, &column, &rte, - /*skipOuterVars=*/false); + + /*skipOuterVars=*/ false); Oid relationId = rte ? rte->relid : InvalidOid; /* @@ -307,50 +307,58 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) static bool ExprMentionsPartitionColumn(Node *node, Query *query) { - if (node == NULL) - return false; + if (node == NULL) + { + return false; + } - if (IsA(node, Var)) - { - Var *v = (Var *) node; + if (IsA(node, Var)) + { + Var *v = (Var *) node; - /* Follow OUTER_VAR → target-list indirection, if present */ - if (v->varno == OUTER_VAR) - { - TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); - return tle && ExprMentionsPartitionColumn((Node *) tle->expr, query); - } + /* Follow OUTER_VAR → target-list indirection, if present */ + if (v->varno == OUTER_VAR) + { + TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); + return tle && ExprMentionsPartitionColumn((Node *) tle->expr, query); + } - /* Sanity-check varno */ - if (v->varno <= 0 || v->varno > list_length(query->rtable)) - return false; + /* Sanity-check varno */ + if (v->varno <= 0 || v->varno > list_length(query->rtable)) + { + return false; + } - RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); + RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); #if PG_VERSION_NUM >= 180000 - /* Synthetic GROUP RTE – examine its expressions instead */ - if (rte->rtekind == RTE_GROUP && rte->groupexprs) - { - ListCell *lc; - foreach (lc, rte->groupexprs) - if (ExprMentionsPartitionColumn((Node *) lfirst(lc), query)) - return true; - return false; - } -#endif - /* Real table? — compare against its dist key */ - if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) - { - Var *partcol = DistPartitionKey(rte->relid); - return partcol && partcol->varattno == v->varattno; - } - return false; - } - /* Recurse through any other node type */ - return expression_tree_walker(node, - ExprMentionsPartitionColumn, - (void *) query); + /* Synthetic GROUP RTE – examine its expressions instead */ + if (rte->rtekind == RTE_GROUP && rte->groupexprs) + { + ListCell *lc; + foreach(lc, rte->groupexprs) + if (ExprMentionsPartitionColumn((Node *) lfirst(lc), query)) + { + return true; + } + return false; + } +#endif + + /* Real table? — compare against its dist key */ + if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) + { + Var *partcol = DistPartitionKey(rte->relid); + return partcol && partcol->varattno == v->varattno; + } + return false; + } + + /* Recurse through any other node type */ + return expression_tree_walker(node, + ExprMentionsPartitionColumn, + (void *) query); } From 4457284d040c3733507bc02456aa671d52142be7 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Wed, 25 Jun 2025 14:43:42 +0000 Subject: [PATCH 5/7] Enhance partition column handling in TargetListOnPartitionColumn for PostgreSQL 18 compatibility --- .../planner/multi_logical_planner.c | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/backend/distributed/planner/multi_logical_planner.c b/src/backend/distributed/planner/multi_logical_planner.c index a3595c92f..55f1eec2a 100644 --- a/src/backend/distributed/planner/multi_logical_planner.c +++ b/src/backend/distributed/planner/multi_logical_planner.c @@ -136,7 +136,9 @@ static MultiNode * ApplyCartesianProductReferenceJoin(MultiNode *leftNode, static MultiNode * ApplyCartesianProduct(MultiNode *leftNode, MultiNode *rightNode, List *partitionColumnList, JoinType joinType, List *joinClauses); +#if PG_VERSION_NUM >= PG_VERSION_18 static bool ExprMentionsPartitionColumn(Node *node, Query *query); +#endif /* @@ -225,15 +227,26 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell); Expr *targetExpression = targetEntry->expr; - bool isPartitionColumn = ExprMentionsPartitionColumn((Node *) targetExpression, - query); + bool isPartitionColumn; + bool skipOuterVars = false; + +#if PG_VERSION_NUM >= PG_VERSION_18 + isPartitionColumn = + ExprMentionsPartitionColumn((Node *) targetExpression, query); +#else + { + skipOuterVars = true; + isPartitionColumn = + IsPartitionColumn(targetExpression, query, skipOuterVars); + } +#endif + Var *column = NULL; RangeTblEntry *rte = NULL; FindReferencedTableColumn(targetExpression, NIL, query, &column, &rte, - - /*skipOuterVars=*/ false); + skipOuterVars); Oid relationId = rte ? rte->relid : InvalidOid; /* @@ -294,6 +307,8 @@ TargetListOnPartitionColumn(Query *query, List *targetEntryList) } +#if PG_VERSION_NUM >= PG_VERSION_18 + /* * ExprMentionsPartitionColumn * @@ -331,7 +346,7 @@ ExprMentionsPartitionColumn(Node *node, Query *query) RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); -#if PG_VERSION_NUM >= 180000 +#if PG_VERSION_NUM >= PG_VERSION_18 /* Synthetic GROUP RTE – examine its expressions instead */ if (rte->rtekind == RTE_GROUP && rte->groupexprs) @@ -362,6 +377,9 @@ ExprMentionsPartitionColumn(Node *node, Query *query) } +#endif + + /* * FindNodeMatchingCheckFunctionInRangeTableList finds a node for which the checker * function returns true. From 49425b77785070b026531e7f4bca5a6ef176bd18 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Wed, 25 Jun 2025 15:17:27 +0000 Subject: [PATCH 6/7] Add support for synthetic GROUP RTE in FindReferencedTableColumn for PostgreSQL 18 --- .../planner/insert_select_planner.c | 77 +++++++++++++++++++ .../planner/multi_logical_optimizer.c | 25 ++++++ 2 files changed, 102 insertions(+) diff --git a/src/backend/distributed/planner/insert_select_planner.c b/src/backend/distributed/planner/insert_select_planner.c index 3bf0bb327..5e26c4608 100644 --- a/src/backend/distributed/planner/insert_select_planner.c +++ b/src/backend/distributed/planner/insert_select_planner.c @@ -108,6 +108,9 @@ static void ProcessEntryPair(TargetEntry *insertEntry, TargetEntry *selectEntry, Form_pg_attribute attr, int targetEntryIndex, List **projectedEntries, List **nonProjectedEntries); +#if PG_VERSION_NUM >= PG_VERSION_18 +static bool ExprMentionsPartitionColumn(Node *node, Query *query); +#endif /* depth of current insert/select planner. */ static int insertSelectPlannerLevel = 0; @@ -1404,6 +1407,7 @@ InsertPartitionColumnMatchesSelect(Query *query, RangeTblEntry *insertRte, /* we can set the select relation id */ *selectPartitionColumnTableId = subqueryPartitionColumnRelationId; + break; } @@ -1422,6 +1426,79 @@ InsertPartitionColumnMatchesSelect(Query *query, RangeTblEntry *insertRte, } +#if PG_VERSION_NUM >= PG_VERSION_18 + +/* + * ExprMentionsPartitionColumn + * + * Return true iff `node` ultimately resolves to the partition column of + * *any* distributed table referenced by `query`. + * + * • Understands OUTER_VAR indirection (PG ≥ 17) + * • Understands synthetic RTE_GROUP entries (PG ≥ 18) + * • Falls back to original logic for PG 15/16 automatically + */ +static bool +ExprMentionsPartitionColumn(Node *node, Query *query) +{ + if (node == NULL) + { + return false; + } + + if (IsA(node, Var)) + { + Var *v = (Var *) node; + + /* Follow OUTER_VAR → target-list indirection, if present */ + if (v->varno == OUTER_VAR) + { + TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); + return tle && ExprMentionsPartitionColumn((Node *) tle->expr, query); + } + + /* Sanity-check varno */ + if (v->varno <= 0 || v->varno > list_length(query->rtable)) + { + return false; + } + + RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); + +#if PG_VERSION_NUM >= PG_VERSION_18 + + /* Synthetic GROUP RTE – examine its expressions instead */ + if (rte->rtekind == RTE_GROUP && rte->groupexprs) + { + ListCell *lc; + foreach(lc, rte->groupexprs) + if (ExprMentionsPartitionColumn((Node *) lfirst(lc), query)) + { + return true; + } + return false; + } +#endif + + /* Real table? — compare against its dist key */ + if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) + { + Var *partcol = DistPartitionKey(rte->relid); + return partcol && partcol->varattno == v->varattno; + } + return false; + } + + /* Recurse through any other node type */ + return expression_tree_walker(node, + ExprMentionsPartitionColumn, + (void *) query); +} + + +#endif + + /* * CreateNonPushableInsertSelectPlan creates a query plan for a SELECT into a * distributed table. The query plan can also be executed on a worker in MX. diff --git a/src/backend/distributed/planner/multi_logical_optimizer.c b/src/backend/distributed/planner/multi_logical_optimizer.c index c4c11f4eb..de5367916 100644 --- a/src/backend/distributed/planner/multi_logical_optimizer.c +++ b/src/backend/distributed/planner/multi_logical_optimizer.c @@ -4557,6 +4557,31 @@ FindReferencedTableColumn(Expr *columnExpression, List *parentQueryList, Query * FindReferencedTableColumn(joinColumn, parentQueryList, query, column, rteContainingReferencedColumn, skipOuterVars); } +#if PG_VERSION_NUM >= 180000 + else if (rangeTableEntry->rtekind == RTE_GROUP) + { + /* + * PG 18: synthetic GROUP RTE. Each groupexprs item corresponds to the + * columns produced by the grouping step, in the *same ordinal order* as + * the Vars that reference them. + */ + List *groupexprs = rangeTableEntry->groupexprs; + AttrNumber groupIndex = candidateColumn->varattno - 1; + + /* Safety check */ + if (groupIndex < 0 || groupIndex >= list_length(groupexprs)) + { + return; /* malformed Var */ + } + Expr *groupExpr = (Expr *) list_nth(groupexprs, groupIndex); + + /* Recurse on the underlying expression (stay in the same query) */ + FindReferencedTableColumn(groupExpr, parentQueryList, query, + column, rteContainingReferencedColumn, + skipOuterVars); + } +#endif /* PG_VERSION_NUM >= 180000 */ + else if (rangeTableEntry->rtekind == RTE_CTE) { /* From 4d744ef1143f6709b8014f77c0e442ce9981b894 Mon Sep 17 00:00:00 2001 From: Mehmet Yilmaz Date: Wed, 25 Jun 2025 15:40:05 +0000 Subject: [PATCH 7/7] Remove ExprMentionsPartitionColumn function for partition column reference checks --- .../planner/insert_select_planner.c | 76 ------------------- 1 file changed, 76 deletions(-) diff --git a/src/backend/distributed/planner/insert_select_planner.c b/src/backend/distributed/planner/insert_select_planner.c index 5e26c4608..60d774d01 100644 --- a/src/backend/distributed/planner/insert_select_planner.c +++ b/src/backend/distributed/planner/insert_select_planner.c @@ -108,9 +108,6 @@ static void ProcessEntryPair(TargetEntry *insertEntry, TargetEntry *selectEntry, Form_pg_attribute attr, int targetEntryIndex, List **projectedEntries, List **nonProjectedEntries); -#if PG_VERSION_NUM >= PG_VERSION_18 -static bool ExprMentionsPartitionColumn(Node *node, Query *query); -#endif /* depth of current insert/select planner. */ static int insertSelectPlannerLevel = 0; @@ -1426,79 +1423,6 @@ InsertPartitionColumnMatchesSelect(Query *query, RangeTblEntry *insertRte, } -#if PG_VERSION_NUM >= PG_VERSION_18 - -/* - * ExprMentionsPartitionColumn - * - * Return true iff `node` ultimately resolves to the partition column of - * *any* distributed table referenced by `query`. - * - * • Understands OUTER_VAR indirection (PG ≥ 17) - * • Understands synthetic RTE_GROUP entries (PG ≥ 18) - * • Falls back to original logic for PG 15/16 automatically - */ -static bool -ExprMentionsPartitionColumn(Node *node, Query *query) -{ - if (node == NULL) - { - return false; - } - - if (IsA(node, Var)) - { - Var *v = (Var *) node; - - /* Follow OUTER_VAR → target-list indirection, if present */ - if (v->varno == OUTER_VAR) - { - TargetEntry *tle = get_tle_by_resno(query->targetList, v->varattno); - return tle && ExprMentionsPartitionColumn((Node *) tle->expr, query); - } - - /* Sanity-check varno */ - if (v->varno <= 0 || v->varno > list_length(query->rtable)) - { - return false; - } - - RangeTblEntry *rte = rt_fetch(v->varno, query->rtable); - -#if PG_VERSION_NUM >= PG_VERSION_18 - - /* Synthetic GROUP RTE – examine its expressions instead */ - if (rte->rtekind == RTE_GROUP && rte->groupexprs) - { - ListCell *lc; - foreach(lc, rte->groupexprs) - if (ExprMentionsPartitionColumn((Node *) lfirst(lc), query)) - { - return true; - } - return false; - } -#endif - - /* Real table? — compare against its dist key */ - if (rte->rtekind == RTE_RELATION && HasDistributionKey(rte->relid)) - { - Var *partcol = DistPartitionKey(rte->relid); - return partcol && partcol->varattno == v->varattno; - } - return false; - } - - /* Recurse through any other node type */ - return expression_tree_walker(node, - ExprMentionsPartitionColumn, - (void *) query); -} - - -#endif - - /* * CreateNonPushableInsertSelectPlan creates a query plan for a SELECT into a * distributed table. The query plan can also be executed on a worker in MX.