aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/ruleutils.c
diff options
context:
space:
mode:
authorDean Rasheed <dean.a.rasheed@gmail.com>2025-01-16 14:57:35 +0000
committerDean Rasheed <dean.a.rasheed@gmail.com>2025-01-16 14:57:35 +0000
commit80feb727c869cc0b2e12bd1543bafa449be9c8e2 (patch)
tree27fb43ef4b09067e3d725e1b918539d492a8550c /src/backend/utils/adt/ruleutils.c
parent7407b2d48cf37bc8847ae6c47dde2164ef2faa34 (diff)
downloadpostgresql-80feb727c869cc0b2e12bd1543bafa449be9c8e2.tar.gz
postgresql-80feb727c869cc0b2e12bd1543bafa449be9c8e2.zip
Add OLD/NEW support to RETURNING in DML queries.
This allows the RETURNING list of INSERT/UPDATE/DELETE/MERGE queries to explicitly return old and new values by using the special aliases "old" and "new", which are automatically added to the query (if not already defined) while parsing its RETURNING list, allowing things like: RETURNING old.colname, new.colname, ... RETURNING old.*, new.* Additionally, a new syntax is supported, allowing the names "old" and "new" to be changed to user-supplied alias names, e.g.: RETURNING WITH (OLD AS o, NEW AS n) o.colname, n.colname, ... This is useful when the names "old" and "new" are already defined, such as inside trigger functions, allowing backwards compatibility to be maintained -- the interpretation of any existing queries that happen to already refer to relations called "old" or "new", or use those as aliases for other relations, is not changed. For an INSERT, old values will generally be NULL, and for a DELETE, new values will generally be NULL, but that may change for an INSERT with an ON CONFLICT ... DO UPDATE clause, or if a query rewrite rule changes the command type. Therefore, we put no restrictions on the use of old and new in any DML queries. Dean Rasheed, reviewed by Jian He and Jeff Davis. Discussion: https://postgr.es/m/CAEZATCWx0J0-v=Qjc6gXzR=KtsdvAE7Ow=D=mu50AgOe+pvisQ@mail.gmail.com
Diffstat (limited to 'src/backend/utils/adt/ruleutils.c')
-rw-r--r--src/backend/utils/adt/ruleutils.c113
1 files changed, 89 insertions, 24 deletions
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2a77f715fba..54dad975553 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -167,6 +167,8 @@ typedef struct
List *subplans; /* List of Plan trees for SubPlans */
List *ctes; /* List of CommonTableExpr nodes */
AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */
+ char *ret_old_alias; /* alias for OLD in RETURNING list */
+ char *ret_new_alias; /* alias for NEW in RETURNING list */
/* Workspace for column alias assignment: */
bool unique_using; /* Are we making USING names globally unique */
List *using_names; /* List of assigned names for USING columns */
@@ -426,6 +428,7 @@ static void get_merge_query_def(Query *query, deparse_context *context);
static void get_utility_query_def(Query *query, deparse_context *context);
static void get_basic_select_query(Query *query, deparse_context *context);
static void get_target_list(List *targetList, deparse_context *context);
+static void get_returning_clause(Query *query, deparse_context *context);
static void get_setop_query(Node *setOp, Query *query,
deparse_context *context);
static Node *get_rule_sortgroupclause(Index ref, List *tlist,
@@ -3804,6 +3807,10 @@ deparse_context_for_plan_tree(PlannedStmt *pstmt, List *rtable_names)
* the most-closely-nested first. This is needed to resolve PARAM_EXEC
* Params. Note we assume that all the Plan nodes share the same rtable.
*
+ * For a ModifyTable plan, we might also need to resolve references to OLD/NEW
+ * variables in the RETURNING list, so we copy the alias names of the OLD and
+ * NEW rows from the ModifyTable plan node.
+ *
* Once this function has been called, deparse_expression() can be called on
* subsidiary expression(s) of the specified Plan node. To deparse
* expressions of a different Plan node in the same Plan tree, re-call this
@@ -3824,6 +3831,13 @@ 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;
}
@@ -4021,6 +4035,8 @@ set_deparse_for_query(deparse_namespace *dpns, Query *query,
dpns->subplans = NIL;
dpns->ctes = query->cteList;
dpns->appendrels = NULL;
+ dpns->ret_old_alias = query->returningOldAlias;
+ dpns->ret_new_alias = query->returningNewAlias;
/* Assign a unique relation alias to each RTE */
set_rtable_names(dpns, parent_namespaces, NULL);
@@ -4415,8 +4431,8 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
if (rte->rtekind == RTE_FUNCTION && rte->functions != NIL)
{
/* Since we're not creating Vars, rtindex etc. don't matter */
- expandRTE(rte, 1, 0, -1, true /* include dropped */ ,
- &colnames, NULL);
+ expandRTE(rte, 1, 0, VAR_RETURNING_DEFAULT, -1,
+ true /* include dropped */ , &colnames, NULL);
}
else
colnames = rte->eref->colnames;
@@ -6343,6 +6359,45 @@ get_target_list(List *targetList, deparse_context *context)
}
static void
+get_returning_clause(Query *query, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+
+ if (query->returningList)
+ {
+ bool have_with = false;
+
+ appendContextKeyword(context, " RETURNING",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+
+ /* Add WITH (OLD/NEW) options, if they're not the defaults */
+ if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
+ {
+ appendStringInfo(buf, " WITH (OLD AS %s",
+ quote_identifier(query->returningOldAlias));
+ have_with = true;
+ }
+ if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
+ {
+ if (have_with)
+ appendStringInfo(buf, ", NEW AS %s",
+ quote_identifier(query->returningNewAlias));
+ else
+ {
+ appendStringInfo(buf, " WITH (NEW AS %s",
+ quote_identifier(query->returningNewAlias));
+ have_with = true;
+ }
+ }
+ if (have_with)
+ appendStringInfoChar(buf, ')');
+
+ /* Add the returning expressions themselves */
+ get_target_list(query->returningList, context);
+ }
+}
+
+static void
get_setop_query(Node *setOp, Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
@@ -7022,11 +7077,7 @@ get_insert_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */
if (query->returningList)
- {
- appendContextKeyword(context, " RETURNING",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_target_list(query->returningList, context);
- }
+ get_returning_clause(query, context);
}
@@ -7078,11 +7129,7 @@ get_update_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */
if (query->returningList)
- {
- appendContextKeyword(context, " RETURNING",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_target_list(query->returningList, context);
- }
+ get_returning_clause(query, context);
}
@@ -7281,11 +7328,7 @@ get_delete_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */
if (query->returningList)
- {
- appendContextKeyword(context, " RETURNING",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_target_list(query->returningList, context);
- }
+ get_returning_clause(query, context);
}
@@ -7444,11 +7487,7 @@ get_merge_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */
if (query->returningList)
- {
- appendContextKeyword(context, " RETURNING",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_target_list(query->returningList, context);
- }
+ get_returning_clause(query, context);
}
@@ -7596,7 +7635,15 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
}
rte = rt_fetch(varno, dpns->rtable);
- refname = (char *) list_nth(dpns->rtable_names, varno - 1);
+
+ /* might be returning old/new column value */
+ if (var->varreturningtype == VAR_RETURNING_OLD)
+ refname = dpns->ret_old_alias;
+ else if (var->varreturningtype == VAR_RETURNING_NEW)
+ refname = dpns->ret_new_alias;
+ else
+ refname = (char *) list_nth(dpns->rtable_names, varno - 1);
+
colinfo = deparse_columns_fetch(varno, dpns);
attnum = varattno;
}
@@ -7710,7 +7757,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
attname = get_rte_attribute_name(rte, attnum);
}
- need_prefix = (context->varprefix || attname == NULL);
+ need_prefix = (context->varprefix || attname == NULL ||
+ var->varreturningtype != VAR_RETURNING_DEFAULT);
/*
* If we're considering a plain Var in an ORDER BY (but not GROUP BY)
@@ -8807,6 +8855,9 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_ConvertRowtypeExpr:
return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg,
node, prettyFlags);
+ case T_ReturningExpr:
+ return isSimpleNode((Node *) ((ReturningExpr *) node)->retexpr,
+ node, prettyFlags);
case T_OpExpr:
{
@@ -10292,6 +10343,20 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_ReturningExpr:
+ {
+ ReturningExpr *retExpr = (ReturningExpr *) node;
+
+ /*
+ * We cannot see a ReturningExpr in rule deparsing, only while
+ * EXPLAINing a query plan (ReturningExpr nodes are only ever
+ * adding during query rewriting). Just display the expression
+ * returned (an expanded view column).
+ */
+ get_rule_expr((Node *) retExpr->retexpr, context, showimplicit);
+ }
+ break;
+
case T_PartitionBoundSpec:
{
PartitionBoundSpec *spec = (PartitionBoundSpec *) node;