aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2021-05-10 11:02:29 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2021-05-10 11:02:29 -0400
commit049e1e2edb06854d7cd9460c22516efaa165fbf8 (patch)
tree6c3812862c6d16f0bff46dda76e6e6b6c1ec9337 /src/backend/executor
parentf02b9085ad2f6fefd9c5cdf85579cb9f0ff0f0ea (diff)
downloadpostgresql-049e1e2edb06854d7cd9460c22516efaa165fbf8.tar.gz
postgresql-049e1e2edb06854d7cd9460c22516efaa165fbf8.zip
Fix mishandling of resjunk columns in ON CONFLICT ... UPDATE tlists.
It's unusual to have any resjunk columns in an ON CONFLICT ... UPDATE list, but it can happen when MULTIEXPR_SUBLINK SubPlans are present. If it happens, the ON CONFLICT UPDATE code path would end up storing tuples that include the values of the extra resjunk columns. That's fairly harmless in the short run, but if new columns are added to the table then the values would become accessible, possibly leading to malfunctions if they don't match the datatypes of the new columns. This had escaped notice through a confluence of missing sanity checks, including * There's no cross-check that a tuple presented to heap_insert or heap_update matches the table rowtype. While it's difficult to check that fully at reasonable cost, we can easily add assertions that there aren't too many columns. * The output-column-assignment cases in execExprInterp.c lacked any sanity checks on the output column numbers, which seems like an oversight considering there are plenty of assertion checks on input column numbers. Add assertions there too. * We failed to apply nodeModifyTable's ExecCheckPlanOutput() to the ON CONFLICT UPDATE tlist. That wouldn't have caught this specific error, since that function is chartered to ignore resjunk columns; but it sure seems like a bad omission now that we've seen this bug. In HEAD, the right way to fix this is to make the processing of ON CONFLICT UPDATE tlists work the same as regular UPDATE tlists now do, that is don't add "SET x = x" entries, and use ExecBuildUpdateProjection to evaluate the tlist and combine it with old values of the not-set columns. This adds a little complication to ExecBuildUpdateProjection, but allows removal of a comparable amount of now-dead code from the planner. In the back branches, the most expedient solution seems to be to (a) use an output slot for the ON CONFLICT UPDATE projection that actually matches the target table, and then (b) invent a variant of ExecBuildProjectionInfo that can be told to not store values resulting from resjunk columns, so it doesn't try to store into nonexistent columns of the output slot. (We can't simply ignore the resjunk columns altogether; they have to be evaluated for MULTIEXPR_SUBLINK to work.) This works back to v10. In 9.6, projections work much differently and we can't cheaply give them such an option. The 9.6 version of this patch works by inserting a JunkFilter when it's necessary to get rid of resjunk columns. In addition, v11 and up have the reverse problem when trying to perform ON CONFLICT UPDATE on a partitioned table. Through a further oversight, adjust_partition_tlist() discarded resjunk columns when re-ordering the ON CONFLICT UPDATE tlist to match a partition. This accidentally prevented the storing-bogus-tuples problem, but at the cost that MULTIEXPR_SUBLINK cases didn't work, typically crashing if more than one row has to be updated. Fix by preserving resjunk columns in that routine. (I failed to resist the temptation to add more assertions there too, and to do some minor code beautification.) Per report from Andres Freund. Back-patch to all supported branches. Security: CVE-2021-32028
Diffstat (limited to 'src/backend/executor')
-rw-r--r--src/backend/executor/execExpr.c112
-rw-r--r--src/backend/executor/execExprInterp.c10
-rw-r--r--src/backend/executor/execPartition.c123
-rw-r--r--src/backend/executor/nodeModifyTable.c29
4 files changed, 154 insertions, 120 deletions
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 77c9d785d99..8c9f8a6aeb6 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -485,14 +485,21 @@ ExecBuildProjectionInfo(List *targetList,
* be stored into the given tuple slot. (Caller must have ensured that tuple
* slot has a descriptor matching the target rel!)
*
- * subTargetList is the tlist of the subplan node feeding ModifyTable.
- * We use this mainly to cross-check that the expressions being assigned
- * are of the correct types. The values from this tlist are assumed to be
- * available from the "outer" tuple slot. They are assigned to target columns
- * listed in the corresponding targetColnos elements. (Only non-resjunk tlist
- * entries are assigned.) Columns not listed in targetColnos are filled from
- * the UPDATE's old tuple, which is assumed to be available in the "scan"
- * tuple slot.
+ * When evalTargetList is false, targetList contains the UPDATE ... SET
+ * expressions that have already been computed by a subplan node; the values
+ * from this tlist are assumed to be available in the "outer" tuple slot.
+ * When evalTargetList is true, targetList contains the UPDATE ... SET
+ * expressions that must be computed (which could contain references to
+ * the outer, inner, or scan tuple slots).
+ *
+ * In either case, targetColnos contains a list of the target column numbers
+ * corresponding to the non-resjunk entries of targetList. The tlist values
+ * are assigned into these columns of the result tuple slot. Target columns
+ * not listed in targetColnos are filled from the UPDATE's old tuple, which
+ * is assumed to be available in the "scan" tuple slot.
+ *
+ * targetList can also contain resjunk columns. These must be evaluated
+ * if evalTargetList is true, but their values are discarded.
*
* relDesc must describe the relation we intend to update.
*
@@ -503,7 +510,8 @@ ExecBuildProjectionInfo(List *targetList,
* ExecCheckPlanOutput, so we must do our safety checks here.
*/
ProjectionInfo *
-ExecBuildUpdateProjection(List *subTargetList,
+ExecBuildUpdateProjection(List *targetList,
+ bool evalTargetList,
List *targetColnos,
TupleDesc relDesc,
ExprContext *econtext,
@@ -525,19 +533,22 @@ ExecBuildUpdateProjection(List *subTargetList,
/* We embed ExprState into ProjectionInfo instead of doing extra palloc */
projInfo->pi_state.tag = T_ExprState;
state = &projInfo->pi_state;
- state->expr = NULL; /* not used */
+ if (evalTargetList)
+ state->expr = (Expr *) targetList;
+ else
+ state->expr = NULL; /* not used */
state->parent = parent;
state->ext_params = NULL;
state->resultslot = slot;
/*
- * Examine the subplan tlist to see how many non-junk columns there are,
- * and to verify that the non-junk columns come before the junk ones.
+ * Examine the targetList to see how many non-junk columns there are, and
+ * to verify that the non-junk columns come before the junk ones.
*/
nAssignableCols = 0;
sawJunk = false;
- foreach(lc, subTargetList)
+ foreach(lc, targetList)
{
TargetEntry *tle = lfirst_node(TargetEntry, lc);
@@ -569,12 +580,10 @@ ExecBuildUpdateProjection(List *subTargetList,
}
/*
- * We want to insert EEOP_*_FETCHSOME steps to ensure the outer and scan
- * tuples are sufficiently deconstructed. Outer tuple is easy, but for
- * scan tuple we must find out the last old column we need.
+ * We need to insert EEOP_*_FETCHSOME steps to ensure the input tuples are
+ * sufficiently deconstructed. The scan tuple must be deconstructed at
+ * least as far as the last old column we need.
*/
- deform.last_outer = nAssignableCols;
-
for (int attnum = relDesc->natts; attnum > 0; attnum--)
{
Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
@@ -587,15 +596,26 @@ ExecBuildUpdateProjection(List *subTargetList,
break;
}
+ /*
+ * If we're actually evaluating the tlist, incorporate its input
+ * requirements too; otherwise, we'll just need to fetch the appropriate
+ * number of columns of the "outer" tuple.
+ */
+ if (evalTargetList)
+ get_last_attnums_walker((Node *) targetList, &deform);
+ else
+ deform.last_outer = nAssignableCols;
+
ExecPushExprSlots(state, &deform);
/*
- * Now generate code to fetch data from the outer tuple, incidentally
- * validating that it'll be of the right type. The checks above ensure
- * that the forboth() will iterate over exactly the non-junk columns.
+ * Now generate code to evaluate the tlist's assignable expressions or
+ * fetch them from the outer tuple, incidentally validating that they'll
+ * be of the right data type. The checks above ensure that the forboth()
+ * will iterate over exactly the non-junk columns.
*/
outerattnum = 0;
- forboth(lc, subTargetList, lc2, targetColnos)
+ forboth(lc, targetList, lc2, targetColnos)
{
TargetEntry *tle = lfirst_node(TargetEntry, lc);
AttrNumber targetattnum = lfirst_int(lc2);
@@ -628,13 +648,47 @@ ExecBuildUpdateProjection(List *subTargetList,
targetattnum,
format_type_be(exprType((Node *) tle->expr)))));
- /*
- * OK, build an outer-tuple reference.
- */
- scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
- scratch.d.assign_var.attnum = outerattnum++;
- scratch.d.assign_var.resultnum = targetattnum - 1;
- ExprEvalPushStep(state, &scratch);
+ /* OK, generate code to perform the assignment. */
+ if (evalTargetList)
+ {
+ /*
+ * We must evaluate the TLE's expression and assign it. We do not
+ * bother jumping through hoops for "safe" Vars like
+ * ExecBuildProjectionInfo does; this is a relatively less-used
+ * path and it doesn't seem worth expending code for that.
+ */
+ ExecInitExprRec(tle->expr, state,
+ &state->resvalue, &state->resnull);
+ /* Needn't worry about read-only-ness here, either. */
+ scratch.opcode = EEOP_ASSIGN_TMP;
+ scratch.d.assign_tmp.resultnum = targetattnum - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+ else
+ {
+ /* Just assign from the outer tuple. */
+ scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
+ scratch.d.assign_var.attnum = outerattnum;
+ scratch.d.assign_var.resultnum = targetattnum - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+ outerattnum++;
+ }
+
+ /*
+ * If we're evaluating the tlist, must evaluate any resjunk columns too.
+ * (This matters for things like MULTIEXPR_SUBLINK SubPlans.)
+ */
+ if (evalTargetList)
+ {
+ for_each_cell(lc, targetList, lc)
+ {
+ TargetEntry *tle = lfirst_node(TargetEntry, lc);
+
+ Assert(tle->resjunk);
+ ExecInitExprRec(tle->expr, state,
+ &state->resvalue, &state->resnull);
+ }
}
/*
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index a9ed98ae485..5483dee6507 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -626,6 +626,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
* care of at compilation time. But see EEOP_INNER_VAR comments.
*/
Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
+ Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
@@ -642,6 +643,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
* care of at compilation time. But see EEOP_INNER_VAR comments.
*/
Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
+ Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
@@ -658,6 +660,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
* care of at compilation time. But see EEOP_INNER_VAR comments.
*/
Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
+ Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
@@ -668,6 +671,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
int resultnum = op->d.assign_tmp.resultnum;
+ Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_values[resultnum] = state->resvalue;
resultslot->tts_isnull[resultnum] = state->resnull;
@@ -678,6 +682,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
int resultnum = op->d.assign_tmp.resultnum;
+ Assert(resultnum >= 0 && resultnum < resultslot->tts_tupleDescriptor->natts);
resultslot->tts_isnull[resultnum] = state->resnull;
if (!resultslot->tts_isnull[resultnum])
resultslot->tts_values[resultnum] =
@@ -2091,8 +2096,10 @@ ExecJustAssignVarImpl(ExprState *state, TupleTableSlot *inslot, bool *isnull)
*
* Since we use slot_getattr(), we don't need to implement the FETCHSOME
* step explicitly, and we also needn't Assert that the attnum is in range
- * --- slot_getattr() will take care of any problems.
+ * --- slot_getattr() will take care of any problems. Nonetheless, check
+ * that resultnum is in range.
*/
+ Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
outslot->tts_values[resultnum] =
slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
return 0;
@@ -2224,6 +2231,7 @@ ExecJustAssignVarVirtImpl(ExprState *state, TupleTableSlot *inslot, bool *isnull
Assert(TTS_IS_VIRTUAL(inslot));
Assert(TTS_FIXED(inslot));
Assert(attnum >= 0 && attnum < inslot->tts_nvalid);
+ Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
outslot->tts_values[resultnum] = inslot->tts_values[attnum];
outslot->tts_isnull[resultnum] = inslot->tts_isnull[attnum];
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 8afddca73a0..8e2feafd28c 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -181,7 +181,7 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
Datum *values,
bool *isnull,
int maxfieldlen);
-static List *adjust_partition_tlist(List *tlist, TupleConversionMap *map);
+static List *adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri);
static void ExecInitPruningContext(PartitionPruneContext *context,
List *pruning_steps,
PartitionDesc partdesc,
@@ -714,6 +714,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
if (node->onConflictAction == ONCONFLICT_UPDATE)
{
+ OnConflictSetState *onconfl = makeNode(OnConflictSetState);
TupleConversionMap *map;
map = leaf_part_rri->ri_RootToPartitionMap;
@@ -721,14 +722,14 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
Assert(node->onConflictSet != NIL);
Assert(rootResultRelInfo->ri_onConflict != NULL);
- leaf_part_rri->ri_onConflict = makeNode(OnConflictSetState);
+ leaf_part_rri->ri_onConflict = onconfl;
/*
* Need a separate existing slot for each partition, as the
* partition could be of a different AM, even if the tuple
* descriptors match.
*/
- leaf_part_rri->ri_onConflict->oc_Existing =
+ onconfl->oc_Existing =
table_slot_create(leaf_part_rri->ri_RelationDesc,
&mtstate->ps.state->es_tupleTable);
@@ -748,17 +749,17 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* Projections and where clauses themselves don't store state
* / are independent of the underlying storage.
*/
- leaf_part_rri->ri_onConflict->oc_ProjSlot =
+ onconfl->oc_ProjSlot =
rootResultRelInfo->ri_onConflict->oc_ProjSlot;
- leaf_part_rri->ri_onConflict->oc_ProjInfo =
+ onconfl->oc_ProjInfo =
rootResultRelInfo->ri_onConflict->oc_ProjInfo;
- leaf_part_rri->ri_onConflict->oc_WhereClause =
+ onconfl->oc_WhereClause =
rootResultRelInfo->ri_onConflict->oc_WhereClause;
}
else
{
List *onconflset;
- TupleDesc tupDesc;
+ List *onconflcols;
bool found_whole_row;
/*
@@ -768,7 +769,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* pseudo-relation (INNER_VAR), and second to handle the main
* target relation (firstVarno).
*/
- onconflset = (List *) copyObject((Node *) node->onConflictSet);
+ onconflset = copyObject(node->onConflictSet);
if (part_attmap == NULL)
part_attmap =
build_attrmap_by_name(RelationGetDescr(partrel),
@@ -788,20 +789,24 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
&found_whole_row);
/* We ignore the value of found_whole_row. */
- /* Finally, adjust this tlist to match the partition. */
- onconflset = adjust_partition_tlist(onconflset, map);
+ /* Finally, adjust the target colnos to match the partition. */
+ onconflcols = adjust_partition_colnos(node->onConflictCols,
+ leaf_part_rri);
/* create the tuple slot for the UPDATE SET projection */
- tupDesc = ExecTypeFromTL(onconflset);
- leaf_part_rri->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- &TTSOpsVirtual);
+ onconfl->oc_ProjSlot =
+ table_slot_create(partrel,
+ &mtstate->ps.state->es_tupleTable);
/* build UPDATE SET projection state */
- leaf_part_rri->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(onconflset, econtext,
- leaf_part_rri->ri_onConflict->oc_ProjSlot,
- &mtstate->ps, partrelDesc);
+ onconfl->oc_ProjInfo =
+ ExecBuildUpdateProjection(onconflset,
+ true,
+ onconflcols,
+ partrelDesc,
+ econtext,
+ onconfl->oc_ProjSlot,
+ &mtstate->ps);
/*
* If there is a WHERE clause, initialize state where it will
@@ -828,7 +833,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
RelationGetForm(partrel)->reltype,
&found_whole_row);
/* We ignore the value of found_whole_row. */
- leaf_part_rri->ri_onConflict->oc_WhereClause =
+ onconfl->oc_WhereClause =
ExecInitQual((List *) clause, &mtstate->ps);
}
}
@@ -1421,71 +1426,35 @@ ExecBuildSlotPartitionKeyDescription(Relation rel,
}
/*
- * adjust_partition_tlist
- * Adjust the targetlist entries for a given partition to account for
- * attribute differences between parent and the partition
- *
- * The expressions have already been fixed, but here we fix the list to make
- * target resnos match the partition's attribute numbers. This results in a
- * copy of the original target list in which the entries appear in resno
- * order, including both the existing entries (that may have their resno
- * changed in-place) and the newly added entries for columns that don't exist
- * in the parent.
- *
- * Scribbles on the input tlist, so callers must make sure to make a copy
- * before passing it to us.
+ * adjust_partition_colnos
+ * Adjust the list of UPDATE target column numbers to account for
+ * attribute differences between the parent and the partition.
*/
static List *
-adjust_partition_tlist(List *tlist, TupleConversionMap *map)
+adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
{
- List *new_tlist = NIL;
- TupleDesc tupdesc = map->outdesc;
- AttrMap *attrMap = map->attrMap;
- AttrNumber attrno;
-
- Assert(tupdesc->natts == attrMap->maplen);
- for (attrno = 1; attrno <= tupdesc->natts; attrno++)
- {
- Form_pg_attribute att_tup = TupleDescAttr(tupdesc, attrno - 1);
- TargetEntry *tle;
-
- if (attrMap->attnums[attrno - 1] != InvalidAttrNumber)
- {
- Assert(!att_tup->attisdropped);
-
- /*
- * Use the corresponding entry from the parent's tlist, adjusting
- * the resno the match the partition's attno.
- */
- tle = (TargetEntry *) list_nth(tlist, attrMap->attnums[attrno - 1] - 1);
- tle->resno = attrno;
- }
- else
- {
- Const *expr;
+ List *new_colnos = NIL;
+ TupleConversionMap *map = ExecGetChildToRootMap(leaf_part_rri);
+ AttrMap *attrMap;
+ ListCell *lc;
- /*
- * For a dropped attribute in the partition, generate a dummy
- * entry with resno matching the partition's attno.
- */
- Assert(att_tup->attisdropped);
- expr = makeConst(INT4OID,
- -1,
- InvalidOid,
- sizeof(int32),
- (Datum) 0,
- true, /* isnull */
- true /* byval */ );
- tle = makeTargetEntry((Expr *) expr,
- attrno,
- pstrdup(NameStr(att_tup->attname)),
- false);
- }
+ Assert(map != NULL); /* else we shouldn't be here */
+ attrMap = map->attrMap;
- new_tlist = lappend(new_tlist, tle);
+ foreach(lc, colnos)
+ {
+ AttrNumber parentattrno = lfirst_int(lc);
+
+ if (parentattrno <= 0 ||
+ parentattrno > attrMap->maplen ||
+ attrMap->attnums[parentattrno - 1] == 0)
+ elog(ERROR, "unexpected attno %d in target column list",
+ parentattrno);
+ new_colnos = lappend_int(new_colnos,
+ attrMap->attnums[parentattrno - 1]);
}
- return new_tlist;
+ return new_colnos;
}
/*-------------------------------------------------------------------------
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c5a2a9a054b..a62928ae7ce 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -492,6 +492,7 @@ ExecInitUpdateProjection(ModifyTableState *mtstate,
resultRelInfo->ri_projectNew =
ExecBuildUpdateProjection(subplan->targetlist,
+ false, /* subplan did the evaluation */
updateColnos,
relDesc,
mtstate->ps.ps_ExprContext,
@@ -2972,9 +2973,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
if (node->onConflictAction == ONCONFLICT_UPDATE)
{
+ OnConflictSetState *onconfl = makeNode(OnConflictSetState);
ExprContext *econtext;
TupleDesc relationDesc;
- TupleDesc tupDesc;
/* already exists if created by RETURNING processing above */
if (mtstate->ps.ps_ExprContext == NULL)
@@ -2984,10 +2985,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
/* create state for DO UPDATE SET operation */
- resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+ resultRelInfo->ri_onConflict = onconfl;
/* initialize slot for the existing tuple */
- resultRelInfo->ri_onConflict->oc_Existing =
+ onconfl->oc_Existing =
table_slot_create(resultRelInfo->ri_RelationDesc,
&mtstate->ps.state->es_tupleTable);
@@ -2997,17 +2998,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* into the table, and for RETURNING processing - which may access
* system attributes.
*/
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
- resultRelInfo->ri_onConflict->oc_ProjSlot =
- ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
- table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+ onconfl->oc_ProjSlot =
+ table_slot_create(resultRelInfo->ri_RelationDesc,
+ &mtstate->ps.state->es_tupleTable);
/* build UPDATE SET projection state */
- resultRelInfo->ri_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
+ onconfl->oc_ProjInfo =
+ ExecBuildUpdateProjection(node->onConflictSet,
+ true,
+ node->onConflictCols,
+ relationDesc,
+ econtext,
+ onconfl->oc_ProjSlot,
+ &mtstate->ps);
/* initialize state to evaluate the WHERE clause, if any */
if (node->onConflictWhere)
@@ -3016,7 +3019,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
qualexpr = ExecInitQual((List *) node->onConflictWhere,
&mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ onconfl->oc_WhereClause = qualexpr;
}
}