summaryrefslogtreecommitdiff
path: root/src/backend/executor
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor')
-rw-r--r--src/backend/executor/execExpr.c155
-rw-r--r--src/backend/executor/execExprInterp.c189
-rw-r--r--src/backend/executor/execMain.c1
-rw-r--r--src/backend/executor/execUtils.c28
-rw-r--r--src/backend/executor/nodeModifyTable.c223
5 files changed, 554 insertions, 42 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);