diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 81b964a2fde..07627e4fe83 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3836,13 +3836,6 @@ set_deparse_context_plan(List *dpcontext, Plan *plan, List *ancestors) dpns->ancestors = ancestors; set_deparse_plan(dpns, plan); - /* For ModifyTable, set aliases for OLD and NEW in RETURNING */ - if (IsA(plan, ModifyTable)) - { - dpns->ret_old_alias = ((ModifyTable *) plan)->returningOldAlias; - dpns->ret_new_alias = ((ModifyTable *) plan)->returningNewAlias; - } - return dpcontext; } @@ -5145,10 +5138,10 @@ get_rtable_name(int rtindex, deparse_context *context) * of a given Plan node * * This sets the plan, outer_plan, inner_plan, outer_tlist, inner_tlist, - * and index_tlist fields. Caller must already have adjusted the ancestors - * list if necessary. Note that the rtable, subplans, and ctes fields do - * not need to change when shifting attention to different plan nodes in a - * single plan tree. + * index_tlist, ret_old_alias, and ret_new_alias fields as appropriate. + * Caller must already have adjusted the ancestors list if necessary. + * Note that the rtable, subplans, and ctes fields do not need to change + * when shifting attention to different plan nodes in a single plan tree. */ static void set_deparse_plan(deparse_namespace *dpns, Plan *plan) @@ -5224,6 +5217,15 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan) dpns->index_tlist = ((CustomScan *) plan)->custom_scan_tlist; else dpns->index_tlist = NIL; + + /* Set up table aliases for OLD and NEW in RETURNING */ + if (IsA(plan, ModifyTable)) + { + dpns->ret_old_alias = ((ModifyTable *) plan)->returningOldAlias; + dpns->ret_new_alias = ((ModifyTable *) plan)->returningNewAlias; + } + else + dpns->ret_old_alias = dpns->ret_new_alias = NULL; } /* @@ -7711,9 +7713,15 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) /* might be returning old/new column value */ if (var->varreturningtype == VAR_RETURNING_OLD) + { refname = dpns->ret_old_alias; + Assert(refname); + } else if (var->varreturningtype == VAR_RETURNING_NEW) + { refname = dpns->ret_new_alias; + Assert(refname); + } else refname = (char *) list_nth(dpns->rtable_names, varno - 1); diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index dfdebf9cb67..1bbd882f2d2 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -1011,3 +1011,67 @@ BEGIN ATOMIC WHERE (foo_1.* = n.*)) AS count; END DROP FUNCTION foo_update; +-- Deparsing must print the OLD/NEW aliases of the ModifyTable that owns a +-- RETURNING list also inside its SubPlans, even when a data-modifying CTE +-- has set different aliases. +CREATE TABLE returning_e1 (k int, c int); +CREATE TABLE returning_e2 (k int, c int); +EXPLAIN (VERBOSE, COSTS OFF) +WITH x AS (UPDATE returning_e2 SET c = c + 1 + RETURNING WITH (OLD AS o, NEW AS n) o.k) +UPDATE returning_e1 SET c = c + 1 + RETURNING WITH (OLD AS p, NEW AS q) p.c, + (SELECT count(*) FROM returning_e2 WHERE returning_e2.c = p.c) AS m, + (SELECT count(*) FROM returning_e2 WHERE returning_e2.c = q.c) AS m2; + QUERY PLAN +----------------------------------------------------------------- + Update on public.returning_e1 + Output: p.c, (SubPlan 2), (SubPlan 3) + CTE x + -> Update on public.returning_e2 + Output: o.k + -> Seq Scan on public.returning_e2 + Output: (returning_e2.c + 1), returning_e2.ctid + -> Seq Scan on public.returning_e1 + Output: (returning_e1.c + 1), returning_e1.ctid + SubPlan 2 + -> Aggregate + Output: count(*) + -> Seq Scan on public.returning_e2 returning_e2_1 + Output: returning_e2_1.k, returning_e2_1.c + Filter: (returning_e2_1.c = p.c) + SubPlan 3 + -> Aggregate + Output: count(*) + -> Seq Scan on public.returning_e2 returning_e2_2 + Output: returning_e2_2.k, returning_e2_2.c + Filter: (returning_e2_2.c = q.c) +(21 rows) + +-- likewise when a SubPlan compares values returned by the CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH x AS (UPDATE returning_e2 SET c = c + 1 + RETURNING WITH (OLD AS o, NEW AS n) n.k, o.c AS oc) +UPDATE returning_e1 SET c = c + 1 + RETURNING WITH (OLD AS p) p.c, + (SELECT count(*) FROM x WHERE x.oc = p.c) AS m; + QUERY PLAN +----------------------------------------------------------------- + Update on public.returning_e1 + Output: p.c, (SubPlan 2) + CTE x + -> Update on public.returning_e2 + Output: n.k, o.c + -> Seq Scan on public.returning_e2 + Output: (returning_e2.c + 1), returning_e2.ctid + -> Seq Scan on public.returning_e1 + Output: (returning_e1.c + 1), returning_e1.ctid + SubPlan 2 + -> Aggregate + Output: count(*) + -> CTE Scan on x + Output: x.k, x.oc + Filter: (x.oc = p.c) +(15 rows) + +DROP TABLE returning_e1, returning_e2; diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql index 133e115851b..e3c0ddde05e 100644 --- a/src/test/regress/sql/returning.sql +++ b/src/test/regress/sql/returning.sql @@ -419,3 +419,27 @@ END; \sf foo_update DROP FUNCTION foo_update; + +-- Deparsing must print the OLD/NEW aliases of the ModifyTable that owns a +-- RETURNING list also inside its SubPlans, even when a data-modifying CTE +-- has set different aliases. +CREATE TABLE returning_e1 (k int, c int); +CREATE TABLE returning_e2 (k int, c int); + +EXPLAIN (VERBOSE, COSTS OFF) +WITH x AS (UPDATE returning_e2 SET c = c + 1 + RETURNING WITH (OLD AS o, NEW AS n) o.k) +UPDATE returning_e1 SET c = c + 1 + RETURNING WITH (OLD AS p, NEW AS q) p.c, + (SELECT count(*) FROM returning_e2 WHERE returning_e2.c = p.c) AS m, + (SELECT count(*) FROM returning_e2 WHERE returning_e2.c = q.c) AS m2; + +-- likewise when a SubPlan compares values returned by the CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH x AS (UPDATE returning_e2 SET c = c + 1 + RETURNING WITH (OLD AS o, NEW AS n) n.k, o.c AS oc) +UPDATE returning_e1 SET c = c + 1 + RETURNING WITH (OLD AS p) p.c, + (SELECT count(*) FROM x WHERE x.oc = p.c) AS m; + +DROP TABLE returning_e1, returning_e2;