diff options
| author | Dean Rasheed <dean.a.rasheed@gmail.com> | 2025-01-16 14:57:35 +0000 |
|---|---|---|
| committer | Dean Rasheed <dean.a.rasheed@gmail.com> | 2025-01-16 14:57:35 +0000 |
| commit | 80feb727c869cc0b2e12bd1543bafa449be9c8e2 (patch) | |
| tree | 27fb43ef4b09067e3d725e1b918539d492a8550c /src/backend | |
| parent | 7407b2d48cf37bc8847ae6c47dde2164ef2faa34 (diff) | |
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')
28 files changed, 1343 insertions, 149 deletions
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 7a800df8cab..8f28da4bf94 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -55,10 +55,15 @@ typedef struct ExprSetupInfo { - /* Highest attribute numbers fetched from inner/outer/scan tuple slots: */ + /* + * Highest attribute numbers fetched from inner/outer/scan/old/new tuple + * slots: + */ AttrNumber last_inner; AttrNumber last_outer; AttrNumber last_scan; + AttrNumber last_old; + AttrNumber last_new; /* MULTIEXPR SubPlan nodes appearing in the expression: */ List *multiexpr_subplans; } ExprSetupInfo; @@ -446,8 +451,25 @@ ExecBuildProjectionInfo(List *targetList, /* INDEX_VAR is handled by default case */ default: - /* get the tuple from the relation being scanned */ - scratch.opcode = EEOP_ASSIGN_SCAN_VAR; + + /* + * Get the tuple from the relation being scanned, or the + * old/new tuple slot, if old/new values were requested. + */ + switch (variable->varreturningtype) + { + case VAR_RETURNING_DEFAULT: + scratch.opcode = EEOP_ASSIGN_SCAN_VAR; + break; + case VAR_RETURNING_OLD: + scratch.opcode = EEOP_ASSIGN_OLD_VAR; + state->flags |= EEO_FLAG_HAS_OLD; + break; + case VAR_RETURNING_NEW: + scratch.opcode = EEOP_ASSIGN_NEW_VAR; + state->flags |= EEO_FLAG_HAS_NEW; + break; + } break; } @@ -535,7 +557,7 @@ ExecBuildUpdateProjection(List *targetList, int nAssignableCols; bool sawJunk; Bitmapset *assignedCols; - ExprSetupInfo deform = {0, 0, 0, NIL}; + ExprSetupInfo deform = {0, 0, 0, 0, 0, NIL}; ExprEvalStep scratch = {0}; int outerattnum; ListCell *lc, @@ -924,6 +946,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* system column */ scratch.d.var.attnum = variable->varattno; scratch.d.var.vartype = variable->vartype; + scratch.d.var.varreturningtype = variable->varreturningtype; switch (variable->varno) { case INNER_VAR: @@ -936,7 +959,20 @@ ExecInitExprRec(Expr *node, ExprState *state, /* INDEX_VAR is handled by default case */ default: - scratch.opcode = EEOP_SCAN_SYSVAR; + switch (variable->varreturningtype) + { + case VAR_RETURNING_DEFAULT: + scratch.opcode = EEOP_SCAN_SYSVAR; + break; + case VAR_RETURNING_OLD: + scratch.opcode = EEOP_OLD_SYSVAR; + state->flags |= EEO_FLAG_HAS_OLD; + break; + case VAR_RETURNING_NEW: + scratch.opcode = EEOP_NEW_SYSVAR; + state->flags |= EEO_FLAG_HAS_NEW; + break; + } break; } } @@ -945,6 +981,7 @@ ExecInitExprRec(Expr *node, ExprState *state, /* regular user column */ scratch.d.var.attnum = variable->varattno - 1; scratch.d.var.vartype = variable->vartype; + scratch.d.var.varreturningtype = variable->varreturningtype; switch (variable->varno) { case INNER_VAR: @@ -957,7 +994,20 @@ ExecInitExprRec(Expr *node, ExprState *state, /* INDEX_VAR is handled by default case */ default: - scratch.opcode = EEOP_SCAN_VAR; + switch (variable->varreturningtype) + { + case VAR_RETURNING_DEFAULT: + scratch.opcode = EEOP_SCAN_VAR; + break; + case VAR_RETURNING_OLD: + scratch.opcode = EEOP_OLD_VAR; + state->flags |= EEO_FLAG_HAS_OLD; + break; + case VAR_RETURNING_NEW: + scratch.opcode = EEOP_NEW_VAR; + state->flags |= EEO_FLAG_HAS_NEW; + break; + } break; } } @@ -2575,6 +2625,34 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_ReturningExpr: + { + ReturningExpr *rexpr = (ReturningExpr *) node; + int retstep; + + /* Skip expression evaluation if OLD/NEW row doesn't exist */ + scratch.opcode = EEOP_RETURNINGEXPR; + scratch.d.returningexpr.nullflag = rexpr->retold ? + EEO_FLAG_OLD_IS_NULL : EEO_FLAG_NEW_IS_NULL; + scratch.d.returningexpr.jumpdone = -1; /* set below */ + ExprEvalPushStep(state, &scratch); + retstep = state->steps_len - 1; + + /* Steps to evaluate expression to return */ + ExecInitExprRec(rexpr->retexpr, state, resv, resnull); + + /* Jump target used if OLD/NEW row doesn't exist */ + state->steps[retstep].d.returningexpr.jumpdone = state->steps_len; + + /* Update ExprState flags */ + if (rexpr->retold) + state->flags |= EEO_FLAG_HAS_OLD; + else + state->flags |= EEO_FLAG_HAS_NEW; + + break; + } + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -2786,7 +2864,7 @@ ExecInitSubPlanExpr(SubPlan *subplan, static void ExecCreateExprSetupSteps(ExprState *state, Node *node) { - ExprSetupInfo info = {0, 0, 0, NIL}; + ExprSetupInfo info = {0, 0, 0, 0, 0, NIL}; /* Prescan to find out what we need. */ expr_setup_walker(node, &info); @@ -2809,8 +2887,8 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info) scratch.resnull = NULL; /* - * Add steps deforming the ExprState's inner/outer/scan slots as much as - * required by any Vars appearing in the expression. + * Add steps deforming the ExprState's inner/outer/scan/old/new slots as + * much as required by any Vars appearing in the expression. */ if (info->last_inner > 0) { @@ -2842,6 +2920,26 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info) if (ExecComputeSlotInfo(state, &scratch)) ExprEvalPushStep(state, &scratch); } + if (info->last_old > 0) + { + scratch.opcode = EEOP_OLD_FETCHSOME; + scratch.d.fetch.last_var = info->last_old; + scratch.d.fetch.fixed = false; + scratch.d.fetch.kind = NULL; + scratch.d.fetch.known_desc = NULL; + if (ExecComputeSlotInfo(state, &scratch)) + ExprEvalPushStep(state, &scratch); + } + if (info->last_new > 0) + { + scratch.opcode = EEOP_NEW_FETCHSOME; + scratch.d.fetch.last_var = info->last_new; + scratch.d.fetch.fixed = false; + scratch.d.fetch.kind = NULL; + scratch.d.fetch.known_desc = NULL; + if (ExecComputeSlotInfo(state, &scratch)) + ExprEvalPushStep(state, &scratch); + } /* * Add steps to execute any MULTIEXPR SubPlans appearing in the @@ -2888,7 +2986,18 @@ expr_setup_walker(Node *node, ExprSetupInfo *info) /* INDEX_VAR is handled by default case */ default: - info->last_scan = Max(info->last_scan, attnum); + switch (variable->varreturningtype) + { + case VAR_RETURNING_DEFAULT: + info->last_scan = Max(info->last_scan, attnum); + break; + case VAR_RETURNING_OLD: + info->last_old = Max(info->last_old, attnum); + break; + case VAR_RETURNING_NEW: + info->last_new = Max(info->last_new, attnum); + break; + } break; } return false; @@ -2926,6 +3035,11 @@ expr_setup_walker(Node *node, ExprSetupInfo *info) * evaluation of the expression will have the same type of slot, with an * equivalent descriptor. * + * EEOP_OLD_FETCHSOME and EEOP_NEW_FETCHSOME are used to process RETURNING, if + * OLD/NEW columns are referred to explicitly. In both cases, the tuple + * descriptor comes from the parent scan node, so we treat them the same as + * EEOP_SCAN_FETCHSOME. + * * Returns true if the deforming step is required, false otherwise. */ static bool @@ -2939,7 +3053,9 @@ ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op) Assert(opcode == EEOP_INNER_FETCHSOME || opcode == EEOP_OUTER_FETCHSOME || - opcode == EEOP_SCAN_FETCHSOME); + opcode == EEOP_SCAN_FETCHSOME || + opcode == EEOP_OLD_FETCHSOME || + opcode == EEOP_NEW_FETCHSOME); if (op->d.fetch.known_desc != NULL) { @@ -2991,7 +3107,9 @@ ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op) desc = ExecGetResultType(os); } } - else if (opcode == EEOP_SCAN_FETCHSOME) + else if (opcode == EEOP_SCAN_FETCHSOME || + opcode == EEOP_OLD_FETCHSOME || + opcode == EEOP_NEW_FETCHSOME) { desc = parent->scandesc; @@ -3039,6 +3157,12 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state) scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */ scratch->d.wholerow.junkFilter = NULL; + /* update ExprState flags if Var refers to OLD/NEW */ + if (variable->varreturningtype == VAR_RETURNING_OLD) + state->flags |= EEO_FLAG_HAS_OLD; + else if (variable->varreturningtype == VAR_RETURNING_NEW) + state->flags |= EEO_FLAG_HAS_NEW; + /* * If the input tuple came from a subquery, it might contain "resjunk" * columns (such as GROUP BY or ORDER BY columns), which we don't want to @@ -3541,7 +3665,7 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, PlanState *parent = &aggstate->ss.ps; ExprEvalStep scratch = {0}; bool isCombine = DO_AGGSPLIT_COMBINE(aggstate->aggsplit); - ExprSetupInfo deform = {0, 0, 0, NIL}; + ExprSetupInfo deform = {0, 0, 0, 0, 0, NIL}; state->expr = (Expr *) aggstate; state->parent = parent; @@ -4082,6 +4206,7 @@ ExecBuildHash32FromAttrs(TupleDesc desc, const TupleTableSlotOps *ops, scratch.resnull = &fcinfo->args[0].isnull; scratch.d.var.attnum = attnum; scratch.d.var.vartype = TupleDescAttr(desc, attnum)->atttypid; + scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT; ExprEvalPushStep(state, &scratch); @@ -4407,6 +4532,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, scratch.opcode = EEOP_INNER_VAR; scratch.d.var.attnum = attno - 1; scratch.d.var.vartype = latt->atttypid; + scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT; scratch.resvalue = &fcinfo->args[0].value; scratch.resnull = &fcinfo->args[0].isnull; ExprEvalPushStep(state, &scratch); @@ -4415,6 +4541,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, scratch.opcode = EEOP_OUTER_VAR; scratch.d.var.attnum = attno - 1; scratch.d.var.vartype = ratt->atttypid; + scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT; scratch.resvalue = &fcinfo->args[1].value; scratch.resnull = &fcinfo->args[1].isnull; ExprEvalPushStep(state, &scratch); @@ -4541,6 +4668,7 @@ ExecBuildParamSetEqual(TupleDesc desc, scratch.opcode = EEOP_INNER_VAR; scratch.d.var.attnum = attno; scratch.d.var.vartype = att->atttypid; + scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT; scratch.resvalue = &fcinfo->args[0].value; scratch.resnull = &fcinfo->args[0].isnull; ExprEvalPushStep(state, &scratch); @@ -4549,6 +4677,7 @@ ExecBuildParamSetEqual(TupleDesc desc, scratch.opcode = EEOP_OUTER_VAR; scratch.d.var.attnum = attno; scratch.d.var.vartype = att->atttypid; + scratch.d.var.varreturningtype = VAR_RETURNING_DEFAULT; scratch.resvalue = &fcinfo->args[1].value; scratch.resnull = &fcinfo->args[1].isnull; ExprEvalPushStep(state, &scratch); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 7dfe17b0a86..1127e6f11eb 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -462,6 +462,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) TupleTableSlot *innerslot; TupleTableSlot *outerslot; TupleTableSlot *scanslot; + TupleTableSlot *oldslot; + TupleTableSlot *newslot; /* * This array has to be in the same order as enum ExprEvalOp. @@ -472,16 +474,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_INNER_FETCHSOME, &&CASE_EEOP_OUTER_FETCHSOME, &&CASE_EEOP_SCAN_FETCHSOME, + &&CASE_EEOP_OLD_FETCHSOME, + &&CASE_EEOP_NEW_FETCHSOME, &&CASE_EEOP_INNER_VAR, &&CASE_EEOP_OUTER_VAR, &&CASE_EEOP_SCAN_VAR, + &&CASE_EEOP_OLD_VAR, + &&CASE_EEOP_NEW_VAR, &&CASE_EEOP_INNER_SYSVAR, &&CASE_EEOP_OUTER_SYSVAR, &&CASE_EEOP_SCAN_SYSVAR, + &&CASE_EEOP_OLD_SYSVAR, + &&CASE_EEOP_NEW_SYSVAR, &&CASE_EEOP_WHOLEROW, &&CASE_EEOP_ASSIGN_INNER_VAR, &&CASE_EEOP_ASSIGN_OUTER_VAR, &&CASE_EEOP_ASSIGN_SCAN_VAR, + &&CASE_EEOP_ASSIGN_OLD_VAR, + &&CASE_EEOP_ASSIGN_NEW_VAR, &&CASE_EEOP_ASSIGN_TMP, &&CASE_EEOP_ASSIGN_TMP_MAKE_RO, &&CASE_EEOP_CONST, @@ -523,6 +533,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_SQLVALUEFUNCTION, &&CASE_EEOP_CURRENTOFEXPR, &&CASE_EEOP_NEXTVALUEEXPR, + &&CASE_EEOP_RETURNINGEXPR, &&CASE_EEOP_ARRAYEXPR, &&CASE_EEOP_ARRAYCOERCE, &&CASE_EEOP_ROW, @@ -591,6 +602,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) innerslot = econtext->ecxt_innertuple; outerslot = econtext->ecxt_outertuple; scanslot = econtext->ecxt_scantuple; + oldslot = econtext->ecxt_oldtuple; + newslot = econtext->ecxt_newtuple; #if defined(EEO_USE_COMPUTED_GOTO) EEO_DISPATCH(); @@ -630,6 +643,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_OLD_FETCHSOME) + { + CheckOpSlotCompatibility(op, oldslot); + + slot_getsomeattrs(oldslot, op->d.fetch.last_var); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NEW_FETCHSOME) + { + CheckOpSlotCompatibility(op, newslot); + + slot_getsomeattrs(newslot, op->d.fetch.last_var); + + EEO_NEXT(); + } + EEO_CASE(EEOP_INNER_VAR) { int attnum = op->d.var.attnum; @@ -673,6 +704,32 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_OLD_VAR) + { + int attnum = op->d.var.attnum; + + /* See EEOP_INNER_VAR comments */ + + Assert(attnum >= 0 && attnum < oldslot->tts_nvalid); + *op->resvalue = oldslot->tts_values[attnum]; + *op->resnull = oldslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NEW_VAR) + { + int attnum = op->d.var.attnum; + + /* See EEOP_INNER_VAR comments */ + + Assert(attnum >= 0 && attnum < newslot->tts_nvalid); + *op->resvalue = newslot->tts_values[attnum]; + *op->resnull = newslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + EEO_CASE(EEOP_INNER_SYSVAR) { ExecEvalSysVar(state, op, econtext, innerslot); @@ -691,6 +748,18 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_OLD_SYSVAR) + { + ExecEvalSysVar(state, op, econtext, oldslot); + EEO_NEXT(); + } + + EEO_CASE(EEOP_NEW_SYSVAR) + { + ExecEvalSysVar(state, op, econtext, newslot); + EEO_NEXT(); + } + EEO_CASE(EEOP_WHOLEROW) { /* too complex for an inline implementation */ @@ -750,6 +819,40 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_ASSIGN_OLD_VAR) + { + int resultnum = op->d.assign_var.resultnum; + int attnum = op->d.assign_var.attnum; + + /* + * We do not need CheckVarSlotCompatibility here; that was taken + * care of at compilation time. But see EEOP_INNER_VAR comments. + */ + Assert(attnum >= 0 && attnum < oldslot->tts_nvalid); + Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts); + resultslot->tts_values[resultnum] = oldslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = oldslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ASSIGN_NEW_VAR) + { + int resultnum = op->d.assign_var.resultnum; + int attnum = op->d.assign_var.attnum; + + /* + * We do not need CheckVarSlotCompatibility here; that was taken + * care of at compilation time. But see EEOP_INNER_VAR comments. + */ + Assert(attnum >= 0 && attnum < newslot->tts_nvalid); + Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts); + resultslot->tts_values[resultnum] = newslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = newslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + EEO_CASE(EEOP_ASSIGN_TMP) { int resultnum = op->d.assign_tmp.resultnum; @@ -1438,6 +1541,23 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_RETURNINGEXPR) + { + /* + * The next op actually evaluates the expression. If the OLD/NEW + * row doesn't exist, skip that and return NULL. + */ + if (state->flags & op->d.returningexpr.nullflag) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + + EEO_JUMP(op->d.returningexpr.jumpdone); + } + + EEO_NEXT(); + } + EEO_CASE(EEOP_ARRAYEXPR) { /* too complex for an inline implementation */ @@ -2119,10 +2239,14 @@ CheckExprStillValid(ExprState *state, ExprContext *econtext) TupleTableSlot *innerslot; TupleTableSlot *outerslot; TupleTableSlot *scanslot; + TupleTableSlot *oldslot; + TupleTableSlot *newslot; innerslot = econtext->ecxt_innertuple; outerslot = econtext->ecxt_outertuple; scanslot = econtext->ecxt_scantuple; + oldslot = econtext->ecxt_oldtuple; + newslot = econtext->ecxt_newtuple; for (int i = 0; i < state->steps_len; i++) { @@ -2153,6 +2277,22 @@ CheckExprStillValid(ExprState *state, ExprContext *econtext) CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype); break; } + + case EEOP_OLD_VAR: + { + int attnum = op->d.var.attnum; + + CheckVarSlotCompatibility(oldslot, attnum + 1, op->d.var.vartype); + break; + } + + case EEOP_NEW_VAR: + { + int attnum = op->d.var.attnum; + + CheckVarSlotCompatibility(newslot, attnum + 1, op->d.var.vartype); + break; + } default: break; } @@ -5113,7 +5253,7 @@ void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { Var *variable = op->d.wholerow.var; - TupleTableSlot *slot; + TupleTableSlot *slot = NULL; TupleDesc output_tupdesc; MemoryContext oldcontext; HeapTupleHeader dtuple; @@ -5138,8 +5278,40 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext) /* INDEX_VAR is handled by default case */ default: - /* get the tuple from the relation being scanned */ - slot = econtext->ecxt_scantuple; + + /* + * Get the tuple from the relation being scanned. + * + * By default, this uses the "scan" tuple slot, but a wholerow Var + * in the RETURNING list may explicitly refer to OLD/NEW. If the + * OLD/NEW row doesn't exist, we just return NULL. + */ + switch (variable->varreturningtype) + { + case VAR_RETURNING_DEFAULT: + slot = econtext->ecxt_scantuple; + break; + + case VAR_RETURNING_OLD: + if (state->flags & EEO_FLAG_OLD_IS_NULL) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return; + } + slot = econtext->ecxt_oldtuple; + break; + + case VAR_RETURNING_NEW: + if (state->flags & EEO_FLAG_NEW_IS_NULL) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return; + } + slot = econtext->ecxt_newtuple; + break; + } break; } @@ -5342,6 +5514,17 @@ ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, { Datum d; + /* OLD/NEW system attribute is NULL if OLD/NEW row is NULL */ + if ((op->d.var.varreturningtype == VAR_RETURNING_OLD && + state->flags & EEO_FLAG_OLD_IS_NULL) || + (op->d.var.varreturningtype == VAR_RETURNING_NEW && + state->flags & EEO_FLAG_NEW_IS_NULL)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return; + } + /* slot_getsysattr has sufficient defenses against bad attnums */ d = slot_getsysattr(slot, op->d.var.attnum, diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 2d28ec65fc4..fb8dba3ab2c 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1257,6 +1257,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_ReturningSlot = NULL; resultRelInfo->ri_TrigOldSlot = NULL; resultRelInfo->ri_TrigNewSlot = NULL; + resultRelInfo->ri_AllNullSlot = NULL; resultRelInfo->ri_MergeActions[MERGE_WHEN_MATCHED] = NIL; resultRelInfo->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] = NIL; resultRelInfo->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET] = NIL; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index f71899463b8..7c539de5cf2 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -1243,6 +1243,34 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo) } /* + * Return a relInfo's all-NULL tuple slot for processing returning tuples. + * + * Note: this slot is intentionally filled with NULLs in every column, and + * should be considered read-only --- the caller must not update it. + */ +TupleTableSlot * +ExecGetAllNullSlot(EState *estate, ResultRelInfo *relInfo) +{ + if (relInfo->ri_AllNullSlot == NULL) + { + Relation rel = relInfo->ri_RelationDesc; + MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + TupleTableSlot *slot; + + slot = ExecInitExtraTupleSlot(estate, + RelationGetDescr(rel), + table_slot_callbacks(rel)); + ExecStoreAllNullTuple(slot); + + relInfo->ri_AllNullSlot = slot; + + MemoryContextSwitchTo(oldcontext); + } + + return relInfo->ri_AllNullSlot; +} + +/* * Return the map needed to convert given child result relation's tuples to * the rowtype of the query's main target ("root") relation. Note that a * NULL result is valid and means that no conversion is needed. diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 1af8c9caf6c..bc82e035ba2 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -102,6 +102,13 @@ typedef struct ModifyTableContext TM_FailureData tmfd; /* + * The tuple deleted when doing a cross-partition UPDATE with a RETURNING + * clause that refers to OLD columns (converted to the root's tuple + * descriptor). + */ + TupleTableSlot *cpDeletedSlot; + + /* * The tuple projected by the INSERT's RETURNING clause, when doing a * cross-partition UPDATE */ @@ -243,34 +250,81 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList) /* * ExecProcessReturning --- evaluate a RETURNING list * + * context: context for the ModifyTable operation * resultRelInfo: current result rel - * tupleSlot: slot holding tuple actually inserted/updated/deleted + * cmdType: operation/merge action performed (INSERT, UPDATE, or DELETE) + * oldSlot: slot holding old tuple deleted or updated + * newSlot: slot holding new tuple inserted or updated * planSlot: slot holding tuple returned by top subplan node * - * Note: If tupleSlot is NULL, the FDW should have already provided econtext's - * scan tuple. + * Note: If oldSlot and newSlot are NULL, the FDW should have already provided + * econtext's scan tuple and its old & new tuples are not needed (FDW direct- + * modify is disabled if the RETURNING list refers to any OLD/NEW values). * * Returns a slot holding the result tuple */ static TupleTableSlot * -ExecProcessReturning(ResultRelInfo *resultRelInfo, - TupleTableSlot *tupleSlot, +ExecProcessReturning(ModifyTableContext *context, + ResultRelInfo *resultRelInfo, + CmdType cmdType, + TupleTableSlot *oldSlot, + TupleTableSlot *newSlot, TupleTableSlot *planSlot) { + EState *estate = context->estate; ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning; ExprContext *econtext = projectReturning->pi_exprContext; /* Make tuple and any needed join variables available to ExecProject */ - if (tupleSlot) - econtext->ecxt_scantuple = tupleSlot; + switch (cmdType) + { + case CMD_INSERT: + case CMD_UPDATE: + /* return new tuple by default */ + if (newSlot) + econtext->ecxt_scantuple = newSlot; + break; + + case CMD_DELETE: + /* return old tuple by default */ + if (oldSlot) + econtext->ecxt_scantuple = oldSlot; + break; + + default: + elog(ERROR, "unrecognized commandType: %d", (int) cmdType); + } econtext->ecxt_outertuple = planSlot; + /* Make old/new tuples available to ExecProject, if required */ + if (oldSlot) + econtext->ecxt_oldtuple = oldSlot; + else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD) + econtext->ecxt_oldtuple = ExecGetAllNullSlot(estate, resultRelInfo); + else + econtext->ecxt_oldtuple = NULL; /* No references to OLD columns */ + + if (newSlot) + econtext->ecxt_newtuple = newSlot; + else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW) + econtext->ecxt_newtuple = ExecGetAllNullSlot(estate, resultRelInfo); + else + econtext->ecxt_newtuple = NULL; /* No references to NEW columns */ + /* - * RETURNING expressions might reference the tableoid column, so - * reinitialize tts_tableOid before evaluating them. + * Tell ExecProject whether or not the OLD/NEW rows actually exist. This + * information is required to evaluate ReturningExpr nodes and also in + * ExecEvalSysVar() and ExecEvalWholeRowVar(). */ - econtext->ecxt_scantuple->tts_tableOid = - RelationGetRelid(resultRelInfo->ri_RelationDesc); + if (oldSlot == NULL) + projectReturning->pi_state.flags |= EEO_FLAG_OLD_IS_NULL; + else + projectReturning->pi_state.flags &= ~EEO_FLAG_OLD_IS_NULL; + + if (newSlot == NULL) + projectReturning->pi_state.flags |= EEO_FLAG_NEW_IS_NULL; + else + projectReturning->pi_state.flags &= ~EEO_FLAG_NEW_IS_NULL; /* Compute the RETURNING expressions */ return ExecProject(projectReturning); @@ -1204,7 +1258,56 @@ ExecInsert(ModifyTableContext *context, /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) - result = ExecProcessReturning(resultRelInfo, slot, planSlot); + { + TupleTableSlot *oldSlot = NULL; + + /* + * If this is part of a cross-partition UPDATE, and the RETURNING list + * refers to any OLD columns, ExecDelete() will have saved the tuple + * deleted from the original partition, which we must use here to + * compute the OLD column values. Otherwise, all OLD column values + * will be NULL. + */ + if (context->cpDeletedSlot) + { + TupleConversionMap *tupconv_map; + + /* + * Convert the OLD tuple to the new partition's format/slot, if + * needed. Note that ExceDelete() already converted it to the + * root's partition's format/slot. + */ + oldSlot = context->cpDeletedSlot; + tupconv_map = ExecGetRootToChildMap(resultRelInfo, estate); + if (tupconv_map != NULL) + { + oldSlot = execute_attr_map_slot(tupconv_map->attrMap, + oldSlot, + ExecGetReturningSlot(estate, + resultRelInfo)); + + oldSlot->tts_tableOid = context->cpDeletedSlot->tts_tableOid; + ItemPointerCopy(&context->cpDeletedSlot->tts_tid, &oldSlot->tts_tid); + } + } + + result = ExecProcessReturning(context, resultRelInfo, CMD_INSERT, + oldSlot, slot, planSlot); + + /* + * For a cross-partition UPDATE, release the old tuple, first making + * sure that the result slot has a local copy of any pass-by-reference + * values. + */ + if (context->cpDeletedSlot) + { + ExecMaterializeSlot(result); + ExecClearTuple(oldSlot); + if (context->cpDeletedSlot != oldSlot) + ExecClearTuple(context->cpDeletedSlot); + context->cpDeletedSlot = NULL; + } + } if (inserted_tuple) *inserted_tuple = slot; @@ -1442,6 +1545,7 @@ ExecDelete(ModifyTableContext *context, Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; TupleTableSlot *slot = NULL; TM_Result result; + bool saveOld; if (tupleDeleted) *tupleDeleted = false; @@ -1676,8 +1780,17 @@ ldelete: ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart); - /* Process RETURNING if present and if requested */ - if (processReturning && resultRelInfo->ri_projectReturning) + /* + * Process RETURNING if present and if requested. + * + * If this is part of a cross-partition UPDATE, and the RETURNING list + * refers to any OLD column values, save the old tuple here for later + * processing of the RETURNING list by ExecInsert(). + */ + saveOld = changingPart && resultRelInfo->ri_projectReturning && + resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD; + + if (resultRelInfo->ri_projectReturning && (processReturning || saveOld)) { /* * We have to put the target tuple into a slot, which means first we @@ -1705,7 +1818,41 @@ ldelete: } } - rslot = ExecProcessReturning(resultRelInfo, slot, context->planSlot); + /* + * If required, save the old tuple for later processing of the + * RETURNING list by ExecInsert(). + */ + if (saveOld) + { + TupleConversionMap *tupconv_map; + + /* + * Convert the tuple into the root partition's format/slot, if + * needed. ExecInsert() will then convert it to the new + * partition's format/slot, if necessary. + */ + tupconv_map = ExecGetChildToRootMap(resultRelInfo); + if (tupconv_map != NULL) + { + ResultRelInfo *rootRelInfo = context->mtstate->rootResultRelInfo; + TupleTableSlot *oldSlot = slot; + + slot = execute_attr_map_slot(tupconv_map->attrMap, + slot, + ExecGetReturningSlot(estate, + rootRelInfo)); + + slot->tts_tableOid = oldSlot->tts_tableOid; + ItemPointerCopy(&oldSlot->tts_tid, &slot->tts_tid); + } + + context->cpDeletedSlot = slot; + + return NULL; + } + + rslot = ExecProcessReturning(context, resultRelInfo, CMD_DELETE, + slot, NULL, context->planSlot); /* * Before releasing the target tuple again, make sure rslot has a @@ -1758,6 +1905,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context, bool tuple_deleted; TupleTableSlot *epqslot = NULL; + context->cpDeletedSlot = NULL; context->cpUpdateReturningSlot = NULL; *retry_slot = NULL; @@ -2258,6 +2406,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, * the planSlot. oldtuple is passed to foreign table triggers; it is * NULL when the foreign table has no relevant triggers. * + * oldSlot contains the old tuple value. * slot contains the new tuple value to be stored. * planSlot is the output of the ModifyTable's subplan; we use it * to access values from other input tables (for RETURNING), @@ -2270,8 +2419,8 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context, */ static TupleTableSlot * ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, - ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, - bool canSetTag) + ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot, + TupleTableSlot *slot, bool canSetTag) { EState *estate = context->estate; Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; @@ -2389,7 +2538,6 @@ redo_act: { TupleTableSlot *inputslot; TupleTableSlot *epqslot; - TupleTableSlot *oldSlot; if (IsolationUsesXactSnapshot()) ereport(ERROR, @@ -2504,7 +2652,8 @@ redo_act: /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) - return ExecProcessReturning(resultRelInfo, slot, context->planSlot); + return ExecProcessReturning(context, resultRelInfo, CMD_UPDATE, + oldSlot, slot, context->planSlot); return NULL; } @@ -2724,16 +2873,23 @@ ExecOnConflictUpdate(ModifyTableContext *context, /* Execute UPDATE with projection */ *returning = ExecUpdate(context, resultRelInfo, - conflictTid, NULL, + conflictTid, NULL, existing, resultRelInfo->ri_onConflict->oc_ProjSlot, canSetTag); /* * Clear out existing tuple, as there might not be another conflict among * the next input rows. Don't want to hold resources till the end of the - * query. + * query. First though, make sure that the returning slot, if any, has a + * local copy of any OLD pass-by-reference values, if it refers to any OLD + * columns. */ + if (*returning != NULL && + resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD) + ExecMaterializeSlot(*returning); + ExecClearTuple(existing); + return true; } @@ -3338,13 +3494,20 @@ lmerge_matched: switch (commandType) { case CMD_UPDATE: - rslot = ExecProcessReturning(resultRelInfo, newslot, + rslot = ExecProcessReturning(context, + resultRelInfo, + CMD_UPDATE, + resultRelInfo->ri_oldTupleSlot, + newslot, context->planSlot); break; case CMD_DELETE: - rslot = ExecProcessReturning(resultRelInfo, + rslot = ExecProcessReturning(context, + resultRelInfo, + CMD_DELETE, resultRelInfo->ri_oldTupleSlot, + NULL, context->planSlot); break; @@ -3894,6 +4057,7 @@ ExecModifyTable(PlanState *pstate) if (node->mt_merge_pending_not_matched != NULL) { context.planSlot = node->mt_merge_pending_not_matched; + context.cpDeletedSlot = NULL; slot = ExecMergeNotMatched(&context, node->resultRelInfo, node->canSetTag); @@ -3913,6 +4077,7 @@ ExecModifyTable(PlanState *pstate) /* Fetch the next row from subplan */ context.planSlot = ExecProcNode(subplanstate); + context.cpDeletedSlot = NULL; /* No more tuples to process? */ if (TupIsNull(context.planSlot)) @@ -3980,9 +4145,15 @@ ExecModifyTable(PlanState *pstate) * A scan slot containing the data that was actually inserted, * updated or deleted has already been made available to * ExecProcessReturning by IterateDirectModify, so no need to - * provide it here. + * provide it here. The individual old and new slots are not + * needed, since direct-modify is disabled if the RETURNING list + * refers to OLD/NEW values. */ - slot = ExecProcessReturning(resultRelInfo, NULL, context.planSlot); + Assert((resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD) == 0 && + (resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW) == 0); + + slot = ExecProcessReturning(&context, resultRelInfo, operation, + NULL, NULL, context.planSlot); return slot; } @@ -4172,7 +4343,7 @@ ExecModifyTable(PlanState *pstate) /* Now apply the update. */ slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple, - slot, node->canSetTag); + oldSlot, slot, node->canSetTag); if (tuplock) UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid, InplaceUpdateTupleLock); diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index b0119200dde..c1cf34f1034 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -105,6 +105,8 @@ llvm_compile_expr(ExprState *state) LLVMValueRef v_innerslot; LLVMValueRef v_outerslot; LLVMValueRef v_scanslot; + LLVMValueRef v_oldslot; + LLVMValueRef v_newslot; LLVMValueRef v_resultslot; /* nulls/values of slots */ @@ -114,6 +116,10 @@ llvm_compile_expr(ExprState *state) LLVMValueRef v_outernulls; LLVMValueRef v_scanvalues; LLVMValueRef v_scannulls; + LLVMValueRef v_oldvalues; + LLVMValueRef v_oldnulls; + LLVMValueRef v_newvalues; + LLVMValueRef v_newnulls; LLVMValueRef v_resultvalues; LLVMValueRef v_resultnulls; @@ -200,6 +206,16 @@ llvm_compile_expr(ExprState *state) v_econtext, FIELDNO_EXPRCONTEXT_OUTERTUPLE, "v_outerslot"); + v_oldslot = l_load_struct_gep(b, + StructExprContext, + v_econtext, + FIELDNO_EXPRCONTEXT_OLDTUPLE, + "v_oldslot"); + v_newslot = l_load_struct_gep(b, + StructExprContext, + v_econtext, + FIELDNO_EXPRCONTEXT_NEWTUPLE, + "v_newslot"); v_resultslot = l_load_struct_gep(b, StructExprState, v_state, @@ -237,6 +253,26 @@ llvm_compile_expr(ExprState *state) v_outerslot, FIELDNO_TUPLETABLESLOT_ISNULL, "v_outernulls"); + v_oldvalues = l_load_struct_gep(b, + StructTupleTableSlot, + v_oldslot, + FIELDNO_TUPLETABLESLOT_VALUES, + "v_oldvalues"); + v_oldnulls = l_load_struct_gep(b, + StructTupleTableSlot, + v_oldslot, + FIELDNO_TUPLETABLESLOT_ISNULL, + "v_oldnulls"); + v_newvalues = l_load_struct_gep(b, + StructTupleTableSlot, + v_newslot, + FIELDNO_TUPLETABLESLOT_VALUES, + "v_newvalues"); + v_newnulls = l_load_struct_gep(b, + StructTupleTableSlot, + v_newslot, + FIELDNO_TUPLETABLESLOT_ISNULL, + "v_newnulls"); v_resultvalues = l_load_struct_gep(b, StructTupleTableSlot, v_resultslot, @@ -302,6 +338,8 @@ llvm_compile_expr(ExprState *state) case EEOP_INNER_FETCHSOME: case EEOP_OUTER_FETCHSOME: case EEOP_SCAN_FETCHSOME: + case EEOP_OLD_FETCHSOME: + case EEOP_NEW_FETCHSOME: { TupleDesc desc = NULL; LLVMValueRef v_slot; @@ -326,8 +364,12 @@ llvm_compile_expr(ExprState *state) v_slot = v_innerslot; else if (opcode == EEOP_OUTER_FETCHSOME) v_slot = v_outerslot; - else + else if (opcode == EEOP_SCAN_FETCHSOME) v_slot = v_scanslot; + else if (opcode == EEOP_OLD_FETCHSOME) + v_slot = v_oldslot; + else + v_slot = v_newslot; /* * Check if all required attributes are available, or @@ -396,6 +438,8 @@ llvm_compile_expr(ExprState *state) case EEOP_INNER_VAR: case EEOP_OUTER_VAR: case EEOP_SCAN_VAR: + case EEOP_OLD_VAR: + case EEOP_NEW_VAR: { LLVMValueRef value, isnull; @@ -413,11 +457,21 @@ llvm_compile_expr(ExprState *state) v_values = v_outervalues; v_nulls = v_outernulls; } - else + else if (opcode == EEOP_SCAN_VAR) { v_values = v_scanvalues; v_nulls = v_scannulls; } + else if (opcode == EEOP_OLD_VAR) + { + v_values = v_oldvalues; + v_nulls = v_oldnulls; + } + else + { + v_values = v_newvalues; + v_nulls = v_newnulls; + } v_attnum = l_int32_const(lc, op->d.var.attnum); value = l_load_gep1(b, TypeSizeT, v_values, v_attnum, ""); @@ -432,6 +486,8 @@ llvm_compile_expr(ExprState *state) case EEOP_INNER_SYSVAR: case EEOP_OUTER_SYSVAR: case EEOP_SCAN_SYSVAR: + case EEOP_OLD_SYSVAR: + case EEOP_NEW_SYSVAR: { LLVMValueRef v_slot; @@ -439,8 +495,12 @@ llvm_compile_expr(ExprState *state) v_slot = v_innerslot; else if (opcode == EEOP_OUTER_SYSVAR) v_slot = v_outerslot; - else + else if (opcode == EEOP_SCAN_SYSVAR) v_slot = v_scanslot; + else if (opcode == EEOP_OLD_SYSVAR) + v_slot = v_oldslot; + else + v_slot = v_newslot; build_EvalXFunc(b, mod, "ExecEvalSysVar", v_state, op, v_econtext, v_slot); @@ -458,6 +518,8 @@ llvm_compile_expr(ExprState *state) case EEOP_ASSIGN_INNER_VAR: case EEOP_ASSIGN_OUTER_VAR: case EEOP_ASSIGN_SCAN_VAR: + case EEOP_ASSIGN_OLD_VAR: + case EEOP_ASSIGN_NEW_VAR: { LLVMValueRef v_value; LLVMValueRef v_isnull; @@ -478,11 +540,21 @@ llvm_compile_expr(ExprState *state) v_values = v_outervalues; v_nulls = v_outernulls; } - else + else if (opcode == EEOP_ASSIGN_SCAN_VAR) { v_values = v_scanvalues; v_nulls = v_scannulls; } + else if (opcode == EEOP_ASSIGN_OLD_VAR) + { + v_values = v_oldvalues; + v_nulls = v_oldnulls; + } + else + { + v_values = v_newvalues; + v_nulls = v_newnulls; + } /* load data */ v_attnum = l_int32_const(lc, op->d.assign_var.attnum); @@ -1654,6 +1726,45 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_RETURNINGEXPR: + { + LLVMBasicBlockRef b_isnull; + LLVMValueRef v_flagsp; + LLVMValueRef v_flags; + LLVMValueRef v_nullflag; + + b_isnull = l_bb_before_v(opblocks[opno + 1], + "op.%d.row.isnull", opno); + + /* + * The next op actually evaluates the expression. If the + * OLD/NEW row doesn't exist, skip that and return NULL. + */ + v_flagsp = l_struct_gep(b, + StructExprState, + v_state, + FIELDNO_EXPRSTATE_FLAGS, + "v.state.flags"); + v_flags = l_load(b, TypeStorageBool, v_flagsp, ""); + + v_nullflag = l_int8_const(lc, op->d.returningexpr.nullflag); + + LLVMBuildCondBr(b, + LLVMBuildICmp(b, LLVMIntEQ, + LLVMBuildAnd(b, v_flags, + v_nullflag, ""), + l_sbool_const(0), ""), + opblocks[opno + 1], b_isnull); + + LLVMPositionBuilderAtEnd(b, b_isnull); + + LLVMBuildStore(b, l_sizet_const(0), v_resvaluep); + LLVMBuildStore(b, l_sbool_const(1), v_resnullp); + + LLVMBuildBr(b, opblocks[op->d.returningexpr.jumpdone]); + break; + } + case EEOP_ARRAYEXPR: build_EvalXFunc(b, mod, "ExecEvalArrayExpr", v_state, op); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index b14d4d6adf4..007612563ca 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -80,12 +80,14 @@ makeVar(int varno, var->varlevelsup = varlevelsup; /* - * Only a few callers need to make Var nodes with non-null varnullingrels, - * or with varnosyn/varattnosyn different from varno/varattno. We don't - * provide separate arguments for them, but just initialize them to NULL - * and the given varno/varattno. This reduces code clutter and chance of - * error for most callers. + * Only a few callers need to make Var nodes with varreturningtype + * different from VAR_RETURNING_DEFAULT, non-null varnullingrels, or with + * varnosyn/varattnosyn different from varno/varattno. We don't provide + * separate arguments for them, but just initialize them to sensible + * default values. This reduces code clutter and chance of error for most + * callers. */ + var->varreturningtype = VAR_RETURNING_DEFAULT; var->varnullingrels = NULL; var->varnosyn = (Index) varno; var->varattnosyn = varattno; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index df779137c9d..7bc823507f1 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -278,6 +278,9 @@ exprType(const Node *expr) type = exprType((Node *) n->expr); } break; + case T_ReturningExpr: + type = exprType((Node *) ((const ReturningExpr *) expr)->retexpr); + break; case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; @@ -529,6 +532,8 @@ exprTypmod(const Node *expr) return ((const CoerceToDomainValue *) expr)->typeMod; case T_SetToDefault: return ((const SetToDefault *) expr)->typeMod; + case T_ReturningExpr: + return exprTypmod((Node *) ((const ReturningExpr *) expr)->retexpr); case T_PlaceHolderVar: return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); default: @@ -1047,6 +1052,9 @@ exprCollation(const Node *expr) case T_InferenceElem: coll = exprCollation((Node *) ((const InferenceElem *) expr)->expr); break; + case T_ReturningExpr: + coll = exprCollation((Node *) ((const ReturningExpr *) expr)->retexpr); + break; case T_PlaceHolderVar: coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; @@ -1110,7 +1118,7 @@ exprInputCollation(const Node *expr) * Assign collation information to an expression tree node. * * Note: since this is only used during parse analysis, we don't need to - * worry about subplans or PlaceHolderVars. + * worry about subplans, PlaceHolderVars, or ReturningExprs. */ void exprSetCollation(Node *expr, Oid collation) @@ -1624,6 +1632,9 @@ exprLocation(const Node *expr) case T_SetToDefault: loc = ((const SetToDefault *) expr)->location; break; + case T_ReturningExpr: + loc = exprLocation((Node *) ((const ReturningExpr *) expr)->retexpr); + break; case T_TargetEntry: /* just use argument's location */ loc = exprLocation((Node *) ((const TargetEntry *) expr)->expr); @@ -2613,6 +2624,8 @@ expression_tree_walker_impl(Node *node, return WALK(((PlaceHolderVar *) node)->phexpr); case T_InferenceElem: return WALK(((InferenceElem *) node)->expr); + case T_ReturningExpr: + return WALK(((ReturningExpr *) node)->retexpr); case T_AppendRelInfo: { AppendRelInfo *appinfo = (AppendRelInfo *) node; @@ -3454,6 +3467,16 @@ expression_tree_mutator_impl(Node *node, return (Node *) newnode; } break; + case T_ReturningExpr: + { + ReturningExpr *rexpr = (ReturningExpr *) node; + ReturningExpr *newnode; + + FLATCOPY(newnode, rexpr, ReturningExpr); + MUTATE(newnode->retexpr, rexpr->retexpr, Expr *); + return (Node *) newnode; + } + break; case T_TargetEntry: { TargetEntry *targetentry = (TargetEntry *) node; @@ -4005,6 +4028,7 @@ raw_expression_tree_walker_impl(Node *node, case T_A_Const: case T_A_Star: case T_MergeSupportFunc: + case T_ReturningOption: /* primitive node types with no subnodes */ break; case T_Alias: @@ -4233,7 +4257,7 @@ raw_expression_tree_walker_impl(Node *node, return true; if (WALK(stmt->onConflictClause)) return true; - if (WALK(stmt->returningList)) + if (WALK(stmt->returningClause)) return true; if (WALK(stmt->withClause)) return true; @@ -4249,7 +4273,7 @@ raw_expression_tree_walker_impl(Node *node, return true; if (WALK(stmt->whereClause)) return true; - if (WALK(stmt->returningList)) + if (WALK(stmt->returningClause)) return true; if (WALK(stmt->withClause)) return true; @@ -4267,7 +4291,7 @@ raw_expression_tree_walker_impl(Node *node, return true; if (WALK(stmt->fromClause)) return true; - if (WALK(stmt->returningList)) + if (WALK(stmt->returningClause)) return true; if (WALK(stmt->withClause)) return true; @@ -4285,7 +4309,7 @@ raw_expression_tree_walker_impl(Node *node, return true; if (WALK(stmt->mergeWhenClauses)) return true; - if (WALK(stmt->returningList)) + if (WALK(stmt->returningClause)) return true; if (WALK(stmt->withClause)) return true; @@ -4303,6 +4327,16 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_ReturningClause: + { + ReturningClause *returning = (ReturningClause *) node; + + if (WALK(returning->options)) + return true; + if (WALK(returning->exprs)) + return true; + } + break; case T_SelectStmt: { SelectStmt *stmt = (SelectStmt *) node; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 33645893912..1115ebeee29 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -3985,6 +3985,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual) */ qual = ReplaceVarsFromTargetList(qual, rti, 0, rte, subquery->targetList, + subquery->resultRelation, REPLACEVARS_REPORT_ERROR, 0, &subquery->hasSubLinks); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1caad5f3a61..1106cd85f0c 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -7121,6 +7121,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan, int epqParam) { ModifyTable *node = makeNode(ModifyTable); + bool returning_old_or_new = false; + bool returning_old_or_new_valid = false; List *fdw_private_list; Bitmapset *direct_modify_plans; ListCell *lc; @@ -7185,6 +7187,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan, } node->updateColnosLists = updateColnosLists; node->withCheckOptionLists = withCheckOptionLists; + node->returningOldAlias = root->parse->returningOldAlias; + node->returningNewAlias = root->parse->returningNewAlias; node->returningLists = returningLists; node->rowMarks = rowMarks; node->mergeActionLists = mergeActionLists; @@ -7265,7 +7269,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan, * callback functions needed for that and (2) there are no local * structures that need to be run for each modified row: row-level * triggers on the foreign table, stored generated columns, WITH CHECK - * OPTIONs from parent views. + * OPTIONs from parent views, or Vars returning OLD/NEW in the + * RETURNING list. */ direct_modify = false; if (fdwroutine != NULL && @@ -7276,7 +7281,18 @@ make_modifytable(PlannerInfo *root, Plan *subplan, withCheckOptionLists == NIL && !has_row_triggers(root, rti, operation) && !has_stored_generated_columns(root, rti)) - direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i); + { + /* returning_old_or_new is the same for all result relations */ + if (!returning_old_or_new_valid) + { + returning_old_or_new = + contain_vars_returning_old_or_new((Node *) + root->parse->returningList); + returning_old_or_new_valid = true; + } + if (!returning_old_or_new) + direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i); + } if (direct_modify) direct_modify_plans = bms_add_member(direct_modify_plans, i); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 81363589125..fff26555956 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -3070,6 +3070,21 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) { Var *var = (Var *) node; + /* + * Verify that Vars with non-default varreturningtype only appear in + * the RETURNING list, and refer to the target relation. + */ + if (var->varreturningtype != VAR_RETURNING_DEFAULT) + { + if (context->inner_itlist != NULL || + context->outer_itlist == NULL || + context->acceptable_rel == 0) + elog(ERROR, "variable returning old/new found outside RETURNING list"); + if (var->varno != context->acceptable_rel) + elog(ERROR, "wrong varno %d (expected %d) for variable returning old/new", + var->varno, context->acceptable_rel); + } + /* Look for the var in the input tlists, first in the outer */ if (context->outer_itlist) { diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index eaaf8c1b49a..8230cbea3c3 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -354,17 +354,19 @@ build_subplan(PlannerInfo *root, Plan *plan, Path *path, Node *arg = pitem->item; /* - * The Var, PlaceHolderVar, Aggref or GroupingFunc has already been - * adjusted to have the correct varlevelsup, phlevelsup, or - * agglevelsup. + * The Var, PlaceHolderVar, Aggref, GroupingFunc, or ReturningExpr has + * already been adjusted to have the correct varlevelsup, phlevelsup, + * agglevelsup, or retlevelsup. * - * If it's a PlaceHolderVar, Aggref or GroupingFunc, its arguments - * might contain SubLinks, which have not yet been processed (see the - * comments for SS_replace_correlation_vars). Do that now. + * If it's a PlaceHolderVar, Aggref, GroupingFunc, or ReturningExpr, + * its arguments might contain SubLinks, which have not yet been + * processed (see the comments for SS_replace_correlation_vars). Do + * that now. */ if (IsA(arg, PlaceHolderVar) || IsA(arg, Aggref) || - IsA(arg, GroupingFunc)) + IsA(arg, GroupingFunc) || + IsA(arg, ReturningExpr)) arg = SS_process_sublinks(root, arg, false); splan->parParam = lappend_int(splan->parParam, pitem->paramId); @@ -1863,8 +1865,8 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, /* * Replace correlation vars (uplevel vars) with Params. * - * Uplevel PlaceHolderVars, aggregates, GROUPING() expressions, and - * MergeSupportFuncs are replaced, too. + * Uplevel PlaceHolderVars, aggregates, GROUPING() expressions, + * MergeSupportFuncs, and ReturningExprs are replaced, too. * * Note: it is critical that this runs immediately after SS_process_sublinks. * Since we do not recurse into the arguments of uplevel PHVs and aggregates, @@ -1924,6 +1926,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) return (Node *) replace_outer_merge_support(root, (MergeSupportFunc *) node); } + if (IsA(node, ReturningExpr)) + { + if (((ReturningExpr *) node)->retlevelsup > 0) + return (Node *) replace_outer_returning(root, + (ReturningExpr *) node); + } return expression_tree_mutator(node, replace_correlation_vars_mutator, root); } @@ -1977,11 +1985,11 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) } /* - * Don't recurse into the arguments of an outer PHV, Aggref or - * GroupingFunc here. Any SubLinks in the arguments have to be dealt with - * at the outer query level; they'll be handled when build_subplan - * collects the PHV, Aggref or GroupingFunc into the arguments to be - * passed down to the current subplan. + * Don't recurse into the arguments of an outer PHV, Aggref, GroupingFunc, + * or ReturningExpr here. Any SubLinks in the arguments have to be dealt + * with at the outer query level; they'll be handled when build_subplan + * collects the PHV, Aggref, GroupingFunc, or ReturningExpr into the + * arguments to be passed down to the current subplan. */ if (IsA(node, PlaceHolderVar)) { @@ -1998,6 +2006,11 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context) if (((GroupingFunc *) node)->agglevelsup > 0) return node; } + else if (IsA(node, ReturningExpr)) + { + if (((ReturningExpr *) node)->retlevelsup > 0) + return node; + } /* * We should never see a SubPlan expression in the input (since this is @@ -2110,7 +2123,9 @@ SS_identify_outer_params(PlannerInfo *root) outer_params = NULL; for (proot = root->parent_root; proot != NULL; proot = proot->parent_root) { - /* Include ordinary Var/PHV/Aggref/GroupingFunc params */ + /* + * Include ordinary Var/PHV/Aggref/GroupingFunc/ReturningExpr params. + */ foreach(l, proot->plan_params) { PlannerParamItem *pitem = (PlannerParamItem *) lfirst(l); diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 82775a3dd51..5d9225e9909 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -2539,7 +2539,8 @@ pullup_replace_vars_callback(Var *var, * expansion with varlevelsup = 0, and then adjust below if needed. */ expandRTE(rcon->target_rte, - var->varno, 0 /* not varlevelsup */ , var->location, + var->varno, 0 /* not varlevelsup */ , + var->varreturningtype, var->location, (var->vartype != RECORDOID), &colnames, &fields); /* Expand the generated per-field Vars, but don't insert PHVs there */ diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index cece3a5be75..5b3dc0d8653 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -253,6 +253,13 @@ adjust_appendrel_attrs_mutator(Node *node, * all non-Var outputs of such subqueries, and then we could look up * the pre-existing PHV here. Or perhaps just wrap the translations * that way to begin with? + * + * If var->varreturningtype is not VAR_RETURNING_DEFAULT, then that + * also needs to be copied to the translated Var. That too would fail + * if the translation wasn't a Var, but that should never happen since + * a non-default var->varreturningtype is only used for Vars referring + * to the result relation, which should never be a flattened UNION ALL + * subquery. */ for (cnt = 0; cnt < nappinfos; cnt++) @@ -283,9 +290,17 @@ adjust_appendrel_attrs_mutator(Node *node, elog(ERROR, "attribute %d of relation \"%s\" does not exist", var->varattno, get_rel_name(appinfo->parent_reloid)); if (IsA(newnode, Var)) + { + ((Var *) newnode)->varreturningtype = var->varreturningtype; ((Var *) newnode)->varnullingrels = var->varnullingrels; - else if (var->varnullingrels != NULL) - elog(ERROR, "failed to apply nullingrels to a non-Var"); + } + else + { + if (var->varreturningtype != VAR_RETURNING_DEFAULT) + elog(ERROR, "failed to apply returningtype to a non-Var"); + if (var->varnullingrels != NULL) + elog(ERROR, "failed to apply nullingrels to a non-Var"); + } return newnode; } else if (var->varattno == 0) @@ -339,6 +354,8 @@ adjust_appendrel_attrs_mutator(Node *node, rowexpr->colnames = copyObject(rte->eref->colnames); rowexpr->location = -1; + if (var->varreturningtype != VAR_RETURNING_DEFAULT) + elog(ERROR, "failed to apply returningtype to a non-Var"); if (var->varnullingrels != NULL) elog(ERROR, "failed to apply nullingrels to a non-Var"); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index de1f340cbe9..43dfecfb47f 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1295,6 +1295,7 @@ contain_leaked_vars_walker(Node *node, void *context) case T_NullTest: case T_BooleanTest: case T_NextValueExpr: + case T_ReturningExpr: case T_List: /* @@ -3404,6 +3405,8 @@ eval_const_expressions_mutator(Node *node, fselect->resulttypmod, fselect->resultcollid, ((Var *) arg)->varlevelsup); + /* New Var has same OLD/NEW returning as old one */ + newvar->varreturningtype = ((Var *) arg)->varreturningtype; /* New Var is nullable by same rels as the old one */ newvar->varnullingrels = ((Var *) arg)->varnullingrels; return (Node *) newvar; diff --git a/src/backend/optimizer/util/paramassign.c b/src/backend/optimizer/util/paramassign.c index 8e089c27070..3bd3ce37c8f 100644 --- a/src/backend/optimizer/util/paramassign.c +++ b/src/backend/optimizer/util/paramassign.c @@ -91,6 +91,7 @@ assign_param_for_var(PlannerInfo *root, Var *var) pvar->vartype == var->vartype && pvar->vartypmod == var->vartypmod && pvar->varcollid == var->varcollid && + pvar->varreturningtype == var->varreturningtype && bms_equal(pvar->varnullingrels, var->varnullingrels)) return pitem->paramId; } @@ -359,6 +360,52 @@ replace_outer_merge_support(PlannerInfo *root, MergeSupportFunc *msf) } /* + * Generate a Param node to replace the given ReturningExpr expression which + * is expected to have retlevelsup > 0 (ie, it is not local). Record the need + * for the ReturningExpr in the proper upper-level root->plan_params. + */ +Param * +replace_outer_returning(PlannerInfo *root, ReturningExpr *rexpr) +{ + Param *retval; + PlannerParamItem *pitem; + Index levelsup; + Oid ptype = exprType((Node *) rexpr->retexpr); + + Assert(rexpr->retlevelsup > 0 && rexpr->retlevelsup < root->query_level); + + /* Find the query level the ReturningExpr belongs to */ + for (levelsup = rexpr->retlevelsup; levelsup > 0; levelsup--) + root = root->parent_root; + + /* + * It does not seem worthwhile to try to de-duplicate references to outer + * ReturningExprs. Just make a new slot every time. + */ + rexpr = copyObject(rexpr); + IncrementVarSublevelsUp((Node *) rexpr, -((int) rexpr->retlevelsup), 0); + Assert(rexpr->retlevelsup == 0); + + pitem = makeNode(PlannerParamItem); + pitem->item = (Node *) rexpr; + pitem->paramId = list_length(root->glob->paramExecTypes); + root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, + ptype); + + root->plan_params = lappend(root->plan_params, pitem); + + retval = makeNode(Param); + retval->paramkind = PARAM_EXEC; + retval->paramid = pitem->paramId; + retval->paramtype = ptype; + retval->paramtypmod = exprTypmod((Node *) rexpr->retexpr); + retval->paramcollid = exprCollation((Node *) rexpr->retexpr); + retval->location = exprLocation((Node *) rexpr->retexpr); + + return retval; +} + +/* * Generate a Param node to replace the given Var, * which is expected to come from some upper NestLoop plan node. * Record the need for the Var in root->curOuterParams. diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index f2d319101d3..71abb01f655 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1857,8 +1857,8 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) case RTE_NAMEDTUPLESTORE: case RTE_RESULT: /* Not all of these can have dropped cols, but share code anyway */ - expandRTE(rte, varno, 0, -1, true /* include dropped */ , - NULL, &colvars); + expandRTE(rte, varno, 0, VAR_RETURNING_DEFAULT, -1, + true /* include dropped */ , NULL, &colvars); foreach(l, colvars) { var = (Var *) lfirst(l); diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 367d080ccf9..8065237a189 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -76,6 +76,7 @@ static bool pull_varattnos_walker(Node *node, pull_varattnos_context *context); static bool pull_vars_walker(Node *node, pull_vars_context *context); static bool contain_var_clause_walker(Node *node, void *context); static bool contain_vars_of_level_walker(Node *node, int *sublevels_up); +static bool contain_vars_returning_old_or_new_walker(Node *node, void *context); static bool locate_var_of_level_walker(Node *node, locate_var_of_level_context *context); static bool pull_var_clause_walker(Node *node, @@ -493,6 +494,49 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up) /* + * contain_vars_returning_old_or_new + * Recursively scan a clause to discover whether it contains any Var nodes + * (of the current query level) whose varreturningtype is VAR_RETURNING_OLD + * or VAR_RETURNING_NEW. + * + * Returns true if any found. + * + * Any ReturningExprs are also detected --- if an OLD/NEW Var was rewritten, + * we still regard this as a clause that returns OLD/NEW values. + * + * Does not examine subqueries, therefore must only be used after reduction + * of sublinks to subplans! + */ +bool +contain_vars_returning_old_or_new(Node *node) +{ + return contain_vars_returning_old_or_new_walker(node, NULL); +} + +static bool +contain_vars_returning_old_or_new_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + if (((Var *) node)->varlevelsup == 0 && + ((Var *) node)->varreturningtype != VAR_RETURNING_DEFAULT) + return true; /* abort the tree traversal and return true */ + return false; + } + if (IsA(node, ReturningExpr)) + { + if (((ReturningExpr *) node)->retlevelsup == 0) + return true; /* abort the tree traversal and return true */ + return false; + } + return expression_tree_walker(node, contain_vars_returning_old_or_new_walker, + context); +} + + +/* * locate_var_of_level * Find the parse location of any Var of the specified query level. * diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 561cf4d6a77..76f58b3aca3 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -641,8 +641,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList, - EXPR_KIND_RETURNING); + transformReturningClause(pstate, qry, stmt->returningClause, + EXPR_KIND_RETURNING); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -1054,7 +1054,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) * contain only the target relation, removing any entries added in a * sub-SELECT or VALUES list. */ - if (stmt->onConflictClause || stmt->returningList) + if (stmt->onConflictClause || stmt->returningClause) { pstate->p_namespace = NIL; addNSItemToQuery(pstate, pstate->p_target_nsitem, @@ -1067,10 +1067,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) stmt->onConflictClause); /* Process RETURNING, if any. */ - if (stmt->returningList) - qry->returningList = transformReturningList(pstate, - stmt->returningList, - EXPR_KIND_RETURNING); + if (stmt->returningClause) + transformReturningClause(pstate, qry, stmt->returningClause, + EXPR_KIND_RETURNING); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -2548,8 +2547,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList, - EXPR_KIND_RETURNING); + transformReturningClause(pstate, qry, stmt->returningClause, + EXPR_KIND_RETURNING); /* * Now we are done with SELECT-like processing, and can get on with @@ -2645,18 +2644,120 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) } /* - * transformReturningList - + * addNSItemForReturning - + * add a ParseNamespaceItem for the OLD or NEW alias in RETURNING. + */ +static void +addNSItemForReturning(ParseState *pstate, const char *aliasname, + VarReturningType returning_type) +{ + List *colnames; + int numattrs; + ParseNamespaceColumn *nscolumns; + ParseNamespaceItem *nsitem; + + /* copy per-column data from the target relation */ + colnames = pstate->p_target_nsitem->p_rte->eref->colnames; + numattrs = list_length(colnames); + + nscolumns = (ParseNamespaceColumn *) + palloc(numattrs * sizeof(ParseNamespaceColumn)); + + memcpy(nscolumns, pstate->p_target_nsitem->p_nscolumns, + numattrs * sizeof(ParseNamespaceColumn)); + + /* mark all columns as returning OLD/NEW */ + for (int i = 0; i < numattrs; i++) + nscolumns[i].p_varreturningtype = returning_type; + + /* build the nsitem, copying most fields from the target relation */ + nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem)); + nsitem->p_names = makeAlias(aliasname, colnames); + nsitem->p_rte = pstate->p_target_nsitem->p_rte; + nsitem->p_rtindex = pstate->p_target_nsitem->p_rtindex; + nsitem->p_perminfo = pstate->p_target_nsitem->p_perminfo; + nsitem->p_nscolumns = nscolumns; + nsitem->p_returning_type = returning_type; + + /* add it to the query namespace as a table-only item */ + addNSItemToQuery(pstate, nsitem, false, true, false); +} + +/* + * transformReturningClause - * handle a RETURNING clause in INSERT/UPDATE/DELETE/MERGE */ -List * -transformReturningList(ParseState *pstate, List *returningList, - ParseExprKind exprKind) +void +transformReturningClause(ParseState *pstate, Query *qry, + ReturningClause *returningClause, + ParseExprKind exprKind) { - List *rlist; + int save_nslen = list_length(pstate->p_namespace); int save_next_resno; - if (returningList == NIL) - return NIL; /* nothing to do */ + if (returningClause == NULL) + return; /* nothing to do */ + + /* + * Scan RETURNING WITH(...) options for OLD/NEW alias names. Complain if + * there is any conflict with existing relations. + */ + foreach_node(ReturningOption, option, returningClause->options) + { + switch (option->option) + { + case RETURNING_OPTION_OLD: + if (qry->returningOldAlias != NULL) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + /* translator: %s is OLD or NEW */ + errmsg("%s cannot be specified multiple times", "OLD"), + parser_errposition(pstate, option->location)); + qry->returningOldAlias = option->value; + break; + + case RETURNING_OPTION_NEW: + if (qry->returningNewAlias != NULL) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + /* translator: %s is OLD or NEW */ + errmsg("%s cannot be specified multiple times", "NEW"), + parser_errposition(pstate, option->location)); + qry->returningNewAlias = option->value; + break; + + default: + elog(ERROR, "unrecognized returning option: %d", option->option); + } + + if (refnameNamespaceItem(pstate, NULL, option->value, -1, NULL) != NULL) + ereport(ERROR, + errcode(ERRCODE_DUPLICATE_ALIAS), + errmsg("table name \"%s\" specified more than once", + option->value), + parser_errposition(pstate, option->location)); + + addNSItemForReturning(pstate, option->value, + option->option == RETURNING_OPTION_OLD ? + VAR_RETURNING_OLD : VAR_RETURNING_NEW); + } + + /* + * If OLD/NEW alias names weren't explicitly specified, use "old"/"new" + * unless masked by existing relations. + */ + if (qry->returningOldAlias == NULL && + refnameNamespaceItem(pstate, NULL, "old", -1, NULL) == NULL) + { + qry->returningOldAlias = "old"; + addNSItemForReturning(pstate, "old", VAR_RETURNING_OLD); + } + if (qry->returningNewAlias == NULL && + refnameNamespaceItem(pstate, NULL, "new", -1, NULL) == NULL) + { + qry->returningNewAlias = "new"; + addNSItemForReturning(pstate, "new", VAR_RETURNING_NEW); + } /* * We need to assign resnos starting at one in the RETURNING list. Save @@ -2666,8 +2767,10 @@ transformReturningList(ParseState *pstate, List *returningList, save_next_resno = pstate->p_next_resno; pstate->p_next_resno = 1; - /* transform RETURNING identically to a SELECT targetlist */ - rlist = transformTargetList(pstate, returningList, exprKind); + /* transform RETURNING expressions identically to a SELECT targetlist */ + qry->returningList = transformTargetList(pstate, + returningClause->exprs, + exprKind); /* * Complain if the nonempty tlist expanded to nothing (which is possible @@ -2675,24 +2778,23 @@ transformReturningList(ParseState *pstate, List *returningList, * allow this, the parsed Query will look like it didn't have RETURNING, * with results that would probably surprise the user. */ - if (rlist == NIL) + if (qry->returningList == NIL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("RETURNING must have at least one column"), parser_errposition(pstate, - exprLocation(linitial(returningList))))); + exprLocation(linitial(returningClause->exprs))))); /* mark column origins */ - markTargetListOrigins(pstate, rlist); + markTargetListOrigins(pstate, qry->returningList); /* resolve any still-unresolved output columns as being type text */ if (pstate->p_resolve_unknowns) - resolveTargetListUnknowns(pstate, rlist); + resolveTargetListUnknowns(pstate, qry->returningList); /* restore state */ + pstate->p_namespace = list_truncate(pstate->p_namespace, save_nslen); pstate->p_next_resno = save_next_resno; - - return rlist; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6079de70e09..d7f9c00c409 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -267,6 +267,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); MergeWhenClause *mergewhen; struct KeyActions *keyactions; struct KeyAction *keyaction; + ReturningClause *retclause; + ReturningOptionKind retoptionkind; } %type <node> stmt toplevel_stmt schema_stmt routine_body_stmt @@ -436,7 +438,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opclass_purpose opt_opfamily transaction_mode_list_or_empty OptTableFuncElementList TableFuncElementList opt_type_modifiers prep_type_clause - execute_param_clause using_clause returning_clause + execute_param_clause using_clause + returning_with_clause returning_options opt_enum_val_list enum_val_list table_func_column_list create_generic_options alter_generic_options relation_expr_list dostmt_opt_list @@ -445,6 +448,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); vacuum_relation_list opt_vacuum_relation_list drop_option_list pub_obj_list +%type <retclause> returning_clause +%type <node> returning_option +%type <retoptionkind> returning_option_kind %type <node> opt_routine_body %type <groupclause> group_clause %type <list> group_by_list @@ -12202,7 +12208,7 @@ InsertStmt: { $5->relation = $4; $5->onConflictClause = $6; - $5->returningList = $7; + $5->returningClause = $7; $5->withClause = $1; $5->stmt_location = @$; $$ = (Node *) $5; @@ -12336,8 +12342,45 @@ opt_conf_expr: ; returning_clause: - RETURNING target_list { $$ = $2; } - | /* EMPTY */ { $$ = NIL; } + RETURNING returning_with_clause target_list + { + ReturningClause *n = makeNode(ReturningClause); + + n->options = $2; + n->exprs = $3; + $$ = n; + } + | /* EMPTY */ + { + $$ = NULL; + } + ; + +returning_with_clause: + WITH '(' returning_options ')' { $$ = $3; } + | /* EMPTY */ { $$ = NIL; } + ; + +returning_options: + returning_option { $$ = list_make1($1); } + | returning_options ',' returning_option { $$ = lappend($1, $3); } + ; + +returning_option: + returning_option_kind AS ColId + { + ReturningOption *n = makeNode(ReturningOption); + + n->option = $1; + n->value = $3; + n->location = @1; + $$ = (Node *) n; + } + ; + +returning_option_kind: + OLD { $$ = RETURNING_OPTION_OLD; } + | NEW { $$ = RETURNING_OPTION_NEW; } ; @@ -12356,7 +12399,7 @@ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias n->relation = $4; n->usingClause = $5; n->whereClause = $6; - n->returningList = $7; + n->returningClause = $7; n->withClause = $1; n->stmt_location = @$; $$ = (Node *) n; @@ -12431,7 +12474,7 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias n->targetList = $5; n->fromClause = $6; n->whereClause = $7; - n->returningList = $8; + n->returningClause = $8; n->withClause = $1; n->stmt_location = @$; $$ = (Node *) n; @@ -12510,7 +12553,7 @@ MergeStmt: m->sourceRelation = $6; m->joinCondition = $8; m->mergeWhenClauses = $9; - m->returningList = $10; + m->returningClause = $10; m->stmt_location = @$; $$ = (Node *) m; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 75a1bbfd896..2e64fcae7b2 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1585,6 +1585,7 @@ transformFromClauseItem(ParseState *pstate, Node *n, jnsitem->p_cols_visible = true; jnsitem->p_lateral_only = false; jnsitem->p_lateral_ok = true; + jnsitem->p_returning_type = VAR_RETURNING_DEFAULT; /* Per SQL, we must check for alias conflicts */ checkNameSpaceConflicts(pstate, list_make1(jnsitem), my_namespace); my_namespace = lappend(my_namespace, jnsitem); @@ -1647,6 +1648,7 @@ buildVarFromNSColumn(ParseState *pstate, ParseNamespaceColumn *nscol) nscol->p_varcollid, 0); /* makeVar doesn't offer parameters for these, so set by hand: */ + var->varreturningtype = nscol->p_varreturningtype; var->varnosyn = nscol->p_varnosyn; var->varattnosyn = nscol->p_varattnosyn; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 285a5c88d58..bad1df732ea 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -2619,6 +2619,13 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem, * point, there seems no harm in expanding it now rather than during * planning. * + * Note that if the nsitem is an OLD/NEW alias for the target RTE (as can + * appear in a RETURNING list), its alias won't match the target RTE's + * alias, but we still want to make a whole-row Var here rather than a + * RowExpr, for consistency with direct references to the target RTE, and + * so that any dropped columns are handled correctly. Thus we also check + * p_returning_type here. + * * Note that if the RTE is a function returning scalar, we create just a * plain reference to the function value, not a composite containing a * single column. This is pretty inconsistent at first sight, but it's @@ -2626,13 +2633,17 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem, * "rel.*" mean the same thing for composite relations, so why not for * scalar functions... */ - if (nsitem->p_names == nsitem->p_rte->eref) + if (nsitem->p_names == nsitem->p_rte->eref || + nsitem->p_returning_type != VAR_RETURNING_DEFAULT) { Var *result; result = makeWholeRowVar(nsitem->p_rte, nsitem->p_rtindex, sublevels_up, true); + /* mark Var for RETURNING OLD/NEW, as necessary */ + result->varreturningtype = nsitem->p_returning_type; + /* location is not filled in by makeWholeRowVar */ result->location = location; @@ -2655,9 +2666,8 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem, * are in the RTE. We needn't worry about marking the RTE for SELECT * access, as the common columns are surely so marked already. */ - expandRTE(nsitem->p_rte, nsitem->p_rtindex, - sublevels_up, location, false, - NULL, &fields); + expandRTE(nsitem->p_rte, nsitem->p_rtindex, sublevels_up, + nsitem->p_returning_type, location, false, NULL, &fields); rowexpr = makeNode(RowExpr); rowexpr->args = list_truncate(fields, list_length(nsitem->p_names->colnames)); diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c index f92bef99d59..51d7703eff7 100644 --- a/src/backend/parser/parse_merge.c +++ b/src/backend/parser/parse_merge.c @@ -247,8 +247,8 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); /* Transform the RETURNING list, if any */ - qry->returningList = transformReturningList(pstate, stmt->returningList, - EXPR_KIND_MERGE_RETURNING); + transformReturningClause(pstate, qry, stmt->returningClause, + EXPR_KIND_MERGE_RETURNING); /* * We now have a good query shape, so now look at the WHEN conditions and diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 92a04e35dff..679bf640c62 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -91,11 +91,13 @@ static void markRTEForSelectPriv(ParseState *pstate, int rtindex, AttrNumber col); static void expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, + VarReturningType returning_type, int location, bool include_dropped, List **colnames, List **colvars); static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, int rtindex, int sublevels_up, + VarReturningType returning_type, int location, bool include_dropped, List **colnames, List **colvars); static int specialAttNum(const char *attname); @@ -763,6 +765,9 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem, } var->location = location; + /* Mark Var for RETURNING OLD/NEW, as necessary */ + var->varreturningtype = nsitem->p_returning_type; + /* Mark Var if it's nulled by any outer joins */ markNullableIfNeeded(pstate, var); @@ -1336,6 +1341,7 @@ buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, nsitem->p_cols_visible = true; nsitem->p_lateral_only = false; nsitem->p_lateral_ok = true; + nsitem->p_returning_type = VAR_RETURNING_DEFAULT; return nsitem; } @@ -1399,6 +1405,7 @@ buildNSItemFromLists(RangeTblEntry *rte, Index rtindex, nsitem->p_cols_visible = true; nsitem->p_lateral_only = false; nsitem->p_lateral_ok = true; + nsitem->p_returning_type = VAR_RETURNING_DEFAULT; return nsitem; } @@ -2300,6 +2307,7 @@ addRangeTableEntryForJoin(ParseState *pstate, nsitem->p_cols_visible = true; nsitem->p_lateral_only = false; nsitem->p_lateral_ok = true; + nsitem->p_returning_type = VAR_RETURNING_DEFAULT; return nsitem; } @@ -2720,9 +2728,10 @@ addNSItemToQuery(ParseState *pstate, ParseNamespaceItem *nsitem, * results. If include_dropped is true then empty strings and NULL constants * (not Vars!) are returned for dropped columns. * - * rtindex, sublevels_up, and location are the varno, varlevelsup, and location - * values to use in the created Vars. Ordinarily rtindex should match the - * actual position of the RTE in its rangetable. + * rtindex, sublevels_up, returning_type, and location are the varno, + * varlevelsup, varreturningtype, and location values to use in the created + * Vars. Ordinarily rtindex should match the actual position of the RTE in + * its rangetable. * * The output lists go into *colnames and *colvars. * If only one of the two kinds of output list is needed, pass NULL for the @@ -2730,6 +2739,7 @@ addNSItemToQuery(ParseState *pstate, ParseNamespaceItem *nsitem, */ void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, + VarReturningType returning_type, int location, bool include_dropped, List **colnames, List **colvars) { @@ -2745,7 +2755,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, case RTE_RELATION: /* Ordinary relation RTE */ expandRelation(rte->relid, rte->eref, - rtindex, sublevels_up, location, + rtindex, sublevels_up, returning_type, location, include_dropped, colnames, colvars); break; case RTE_SUBQUERY: @@ -2792,6 +2802,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, exprTypmod((Node *) te->expr), exprCollation((Node *) te->expr), sublevels_up); + varnode->varreturningtype = returning_type; varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -2829,7 +2840,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, Assert(tupdesc); expandTupleDesc(tupdesc, rte->eref, rtfunc->funccolcount, atts_done, - rtindex, sublevels_up, location, + rtindex, sublevels_up, + returning_type, location, include_dropped, colnames, colvars); } else if (functypclass == TYPEFUNC_SCALAR) @@ -2849,6 +2861,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, exprTypmod(rtfunc->funcexpr), exprCollation(rtfunc->funcexpr), sublevels_up); + varnode->varreturningtype = returning_type; varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -2891,6 +2904,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, attrtypmod, attrcollation, sublevels_up); + varnode->varreturningtype = returning_type; varnode->location = location; *colvars = lappend(*colvars, varnode); } @@ -2920,6 +2934,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, InvalidOid, sublevels_up); + varnode->varreturningtype = returning_type; *colvars = lappend(*colvars, varnode); } } @@ -3002,6 +3017,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, exprTypmod(avar), exprCollation(avar), sublevels_up); + varnode->varreturningtype = returning_type; varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -3057,6 +3073,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, varnode = makeVar(rtindex, varattno, coltype, coltypmod, colcoll, sublevels_up); + varnode->varreturningtype = returning_type; varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -3089,6 +3106,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, */ static void expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, + VarReturningType returning_type, int location, bool include_dropped, List **colnames, List **colvars) { @@ -3097,7 +3115,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, /* Get the tupledesc and turn it over to expandTupleDesc */ rel = relation_open(relid, AccessShareLock); expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0, - rtindex, sublevels_up, + rtindex, sublevels_up, returning_type, location, include_dropped, colnames, colvars); relation_close(rel, AccessShareLock); @@ -3115,6 +3133,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, int rtindex, int sublevels_up, + VarReturningType returning_type, int location, bool include_dropped, List **colnames, List **colvars) { @@ -3175,6 +3194,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset, attr->atttypid, attr->atttypmod, attr->attcollation, sublevels_up); + varnode->varreturningtype = returning_type; varnode->location = location; *colvars = lappend(*colvars, varnode); @@ -3227,6 +3247,7 @@ expandNSItemVars(ParseState *pstate, ParseNamespaceItem *nsitem, nscol->p_varcollid, sublevels_up); /* makeVar doesn't offer parameters for these, so set by hand: */ + var->varreturningtype = nscol->p_varreturningtype; var->varnosyn = nscol->p_varnosyn; var->varattnosyn = nscol->p_varattnosyn; var->location = location; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 93915031be8..4aba0d9d4d5 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1550,8 +1550,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) *lvar; int i; - expandRTE(rte, var->varno, 0, var->location, false, - &names, &vars); + expandRTE(rte, var->varno, 0, var->varreturningtype, + var->location, false, &names, &vars); tupleDesc = CreateTemplateTupleDesc(list_length(vars)); i = 1; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 1a5dfd0aa47..b74f2acc327 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -641,6 +641,7 @@ rewriteRuleAction(Query *parsetree, 0, rt_fetch(new_varno, sub_action->rtable), parsetree->targetList, + sub_action->resultRelation, (event == CMD_UPDATE) ? REPLACEVARS_CHANGE_VARNO : REPLACEVARS_SUBSTITUTE_NULL, @@ -674,10 +675,15 @@ rewriteRuleAction(Query *parsetree, rt_fetch(parsetree->resultRelation, parsetree->rtable), rule_action->returningList, + rule_action->resultRelation, REPLACEVARS_REPORT_ERROR, 0, &rule_action->hasSubLinks); + /* use triggering query's aliases for OLD and NEW in RETURNING list */ + rule_action->returningOldAlias = parsetree->returningOldAlias; + rule_action->returningNewAlias = parsetree->returningNewAlias; + /* * There could have been some SubLinks in parsetree's returningList, * in which case we'd better mark the rule_action correctly. @@ -2358,6 +2364,7 @@ CopyAndAddInvertedQual(Query *parsetree, rt_fetch(rt_index, parsetree->rtable), parsetree->targetList, + parsetree->resultRelation, (event == CMD_UPDATE) ? REPLACEVARS_CHANGE_VARNO : REPLACEVARS_SUBSTITUTE_NULL, @@ -3582,6 +3589,7 @@ rewriteTargetView(Query *parsetree, Relation view) 0, view_rte, view_targetlist, + new_rt_index, REPLACEVARS_REPORT_ERROR, 0, NULL); @@ -3733,6 +3741,7 @@ rewriteTargetView(Query *parsetree, Relation view) 0, view_rte, tmp_tlist, + new_rt_index, REPLACEVARS_REPORT_ERROR, 0, &parsetree->hasSubLinks); diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 047396e390b..bca11500e9e 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -810,6 +810,14 @@ IncrementVarSublevelsUp_walker(Node *node, phv->phlevelsup += context->delta_sublevels_up; /* fall through to recurse into argument */ } + if (IsA(node, ReturningExpr)) + { + ReturningExpr *rexpr = (ReturningExpr *) node; + + if (rexpr->retlevelsup >= context->min_sublevels_up) + rexpr->retlevelsup += context->delta_sublevels_up; + /* fall through to recurse into argument */ + } if (IsA(node, RangeTblEntry)) { RangeTblEntry *rte = (RangeTblEntry *) node; @@ -875,6 +883,67 @@ IncrementVarSublevelsUp_rtable(List *rtable, int delta_sublevels_up, QTW_EXAMINE_RTES_BEFORE); } +/* + * SetVarReturningType - adjust Var nodes for a specified varreturningtype. + * + * Find all Var nodes referring to the specified result relation in the given + * expression and set their varreturningtype to the specified value. + * + * NOTE: although this has the form of a walker, we cheat and modify the + * Var nodes in-place. The given expression tree should have been copied + * earlier to ensure that no unwanted side-effects occur! + */ + +typedef struct +{ + int result_relation; + int sublevels_up; + VarReturningType returning_type; +} SetVarReturningType_context; + +static bool +SetVarReturningType_walker(Node *node, SetVarReturningType_context *context) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varno == context->result_relation && + var->varlevelsup == context->sublevels_up) + var->varreturningtype = context->returning_type; + + return false; + } + + if (IsA(node, Query)) + { + /* Recurse into subselects */ + bool result; + + context->sublevels_up++; + result = query_tree_walker((Query *) node, SetVarReturningType_walker, + context, 0); + context->sublevels_up--; + return result; + } + return expression_tree_walker(node, SetVarReturningType_walker, context); +} + +static void +SetVarReturningType(Node *node, int result_relation, int sublevels_up, + VarReturningType returning_type) +{ + SetVarReturningType_context context; + + context.result_relation = result_relation; + context.sublevels_up = sublevels_up; + context.returning_type = returning_type; + + /* Expect to start with an expression */ + SetVarReturningType_walker(node, &context); +} /* * rangeTableEntry_used - detect whether an RTE is referenced somewhere @@ -1640,6 +1709,15 @@ map_variable_attnos(Node *node, * relation. This is needed to handle whole-row Vars referencing the target. * We expand such Vars into RowExpr constructs. * + * In addition, for INSERT/UPDATE/DELETE/MERGE queries, the caller must + * provide result_relation, the index of the result relation in the rewritten + * query. This is needed to handle OLD/NEW RETURNING list Vars referencing + * target_varno. When such Vars are expanded, their varreturningtype is + * copied onto any replacement Vars referencing result_relation. In addition, + * if the replacement expression from the targetlist is not simply a Var + * referencing result_relation, it is wrapped in a ReturningExpr node (causing + * the executor to return NULL if the OLD/NEW row doesn't exist). + * * outer_hasSubLinks works the same as for replace_rte_variables(). */ @@ -1647,6 +1725,7 @@ typedef struct { RangeTblEntry *target_rte; List *targetlist; + int result_relation; ReplaceVarsNoMatchOption nomatch_option; int nomatch_varno; } ReplaceVarsFromTargetList_context; @@ -1671,10 +1750,13 @@ ReplaceVarsFromTargetList_callback(Var *var, * dropped columns. If the var is RECORD (ie, this is a JOIN), then * omit dropped columns. In the latter case, attach column names to * the RowExpr for use of the executor and ruleutils.c. + * + * The varreturningtype is copied onto each individual field Var, so + * that it is handled correctly when we recurse. */ expandRTE(rcon->target_rte, - var->varno, var->varlevelsup, var->location, - (var->vartype != RECORDOID), + var->varno, var->varlevelsup, var->varreturningtype, + var->location, (var->vartype != RECORDOID), &colnames, &fields); /* Adjust the generated per-field Vars... */ fields = (List *) replace_rte_variables_mutator((Node *) fields, @@ -1686,6 +1768,18 @@ ReplaceVarsFromTargetList_callback(Var *var, rowexpr->colnames = (var->vartype == RECORDOID) ? colnames : NIL; rowexpr->location = var->location; + /* Wrap it in a ReturningExpr, if needed, per comments above */ + if (var->varreturningtype != VAR_RETURNING_DEFAULT) + { + ReturningExpr *rexpr = makeNode(ReturningExpr); + + rexpr->retlevelsup = var->varlevelsup; + rexpr->retold = (var->varreturningtype == VAR_RETURNING_OLD); + rexpr->retexpr = (Expr *) rowexpr; + + return (Node *) rexpr; + } + return (Node *) rowexpr; } @@ -1751,6 +1845,34 @@ ReplaceVarsFromTargetList_callback(Var *var, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("NEW variables in ON UPDATE rules cannot reference columns that are part of a multiple assignment in the subject UPDATE command"))); + /* Handle any OLD/NEW RETURNING list Vars */ + if (var->varreturningtype != VAR_RETURNING_DEFAULT) + { + /* + * Copy varreturningtype onto any Vars in the tlist item that + * refer to result_relation (which had better be non-zero). + */ + if (rcon->result_relation == 0) + elog(ERROR, "variable returning old/new found outside RETURNING list"); + + SetVarReturningType((Node *) newnode, rcon->result_relation, + var->varlevelsup, var->varreturningtype); + + /* Wrap it in a ReturningExpr, if needed, per comments above */ + if (!IsA(newnode, Var) || + ((Var *) newnode)->varno != rcon->result_relation || + ((Var *) newnode)->varlevelsup != var->varlevelsup) + { + ReturningExpr *rexpr = makeNode(ReturningExpr); + + rexpr->retlevelsup = var->varlevelsup; + rexpr->retold = (var->varreturningtype == VAR_RETURNING_OLD); + rexpr->retexpr = newnode; + + newnode = (Expr *) rexpr; + } + } + return (Node *) newnode; } } @@ -1760,6 +1882,7 @@ ReplaceVarsFromTargetList(Node *node, int target_varno, int sublevels_up, RangeTblEntry *target_rte, List *targetlist, + int result_relation, ReplaceVarsNoMatchOption nomatch_option, int nomatch_varno, bool *outer_hasSubLinks) @@ -1768,6 +1891,7 @@ ReplaceVarsFromTargetList(Node *node, context.target_rte = target_rte; context.targetlist = targetlist; + context.result_relation = result_relation; context.nomatch_option = nomatch_option; context.nomatch_varno = nomatch_varno; 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; |
