diff options
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; |
