1
0
mirror of https://github.com/postgres/postgres.git synced 2025-10-16 17:07:43 +03:00

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
This commit is contained in:
Dean Rasheed
2025-01-16 14:57:35 +00:00
parent 7407b2d48c
commit 80feb727c8
61 changed files with 2910 additions and 390 deletions

View File

@@ -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;
@@ -6342,6 +6358,45 @@ get_target_list(List *targetList, deparse_context *context)
pfree(targetbuf.data);
}
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)
{
@@ -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;