aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/access/heap/heapam.c8
-rw-r--r--src/backend/executor/execExpr.c33
-rw-r--r--src/backend/executor/execExprInterp.c11
-rw-r--r--src/backend/executor/nodeModifyTable.c22
-rw-r--r--src/include/executor/executor.h6
-rw-r--r--src/test/regress/expected/update.out12
-rw-r--r--src/test/regress/sql/update.sql6
7 files changed, 81 insertions, 17 deletions
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0c59ff11e54..25fcd4c5241 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2414,6 +2414,10 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
Buffer vmbuffer = InvalidBuffer;
bool all_visible_cleared = false;
+ /* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+ Assert(HeapTupleHeaderGetNatts(tup->t_data) <=
+ RelationGetNumberOfAttributes(relation));
+
/*
* Fill in tuple header fields, assign an OID, and toast the tuple if
* necessary.
@@ -3515,6 +3519,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
Assert(ItemPointerIsValid(otid));
+ /* Cheap, simplistic check that the tuple matches the rel's rowtype. */
+ Assert(HeapTupleHeaderGetNatts(newtup->t_data) <=
+ RelationGetNumberOfAttributes(relation));
+
/*
* Forbid this during a parallel operation, lest it allocate a combocid.
* Other workers might need that combocid for visibility checks, and we
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 7496189fabf..2a2741352af 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -304,6 +304,32 @@ ExecBuildProjectionInfo(List *targetList,
PlanState *parent,
TupleDesc inputDesc)
{
+ return ExecBuildProjectionInfoExt(targetList,
+ econtext,
+ slot,
+ true,
+ parent,
+ inputDesc);
+}
+
+/*
+ * ExecBuildProjectionInfoExt
+ *
+ * As above, with one additional option.
+ *
+ * If assignJunkEntries is true (the usual case), resjunk entries in the tlist
+ * are not handled specially: they are evaluated and assigned to the proper
+ * column of the result slot. If assignJunkEntries is false, resjunk entries
+ * are evaluated, but their result is discarded without assignment.
+ */
+ProjectionInfo *
+ExecBuildProjectionInfoExt(List *targetList,
+ ExprContext *econtext,
+ TupleTableSlot *slot,
+ bool assignJunkEntries,
+ PlanState *parent,
+ TupleDesc inputDesc)
+{
ProjectionInfo *projInfo = makeNode(ProjectionInfo);
ExprState *state;
ExprEvalStep scratch;
@@ -337,7 +363,8 @@ ExecBuildProjectionInfo(List *targetList,
*/
if (tle->expr != NULL &&
IsA(tle->expr, Var) &&
- ((Var *) tle->expr)->varattno > 0)
+ ((Var *) tle->expr)->varattno > 0 &&
+ (assignJunkEntries || !tle->resjunk))
{
/* Non-system Var, but how safe is it? */
variable = (Var *) tle->expr;
@@ -401,6 +428,10 @@ ExecBuildProjectionInfo(List *targetList,
ExecInitExprRec(tle->expr, parent, state,
&state->resvalue, &state->resnull);
+ /* This makes it easy to discard resjunk results when told to. */
+ if (!assignJunkEntries && tle->resjunk)
+ continue;
+
/*
* Column might be referenced multiple times in upper nodes, so
* force value to R/O - but only if it could be an expanded datum.
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index b5f5b735f81..de515617695 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -564,6 +564,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];
@@ -580,6 +581,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];
@@ -596,6 +598,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];
@@ -606,6 +609,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;
@@ -616,6 +620,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] =
@@ -1747,8 +1752,10 @@ ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, 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;
@@ -1765,6 +1772,7 @@ ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
TupleTableSlot *outslot = state->resultslot;
/* See comments in ExecJustAssignInnerVar */
+ Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
outslot->tts_values[resultnum] =
slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
return 0;
@@ -1781,6 +1789,7 @@ ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
TupleTableSlot *outslot = state->resultslot;
/* See comments in ExecJustAssignInnerVar */
+ Assert(resultnum >= 0 && resultnum < outslot->tts_tupleDescriptor->natts);
outslot->tts_values[resultnum] =
slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
return 0;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2f6a1102cda..54bb2cb5723 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2135,7 +2135,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (node->onConflictAction == ONCONFLICT_UPDATE)
{
ExprContext *econtext;
- TupleDesc tupDesc;
/* insert may only have one plan, inheritance is not expanded */
Assert(nplans == 1);
@@ -2155,16 +2154,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_excludedtlist = node->exclRelTlist;
/* create target slot for UPDATE SET projection */
- tupDesc = ExecTypeFromTL((List *) node->onConflictSet,
- resultRelInfo->ri_RelationDesc->rd_rel->relhasoids);
mtstate->mt_conflproj = ExecInitExtraTupleSlot(mtstate->ps.state);
- ExecSetSlotDescriptor(mtstate->mt_conflproj, tupDesc);
+ ExecSetSlotDescriptor(mtstate->mt_conflproj,
+ resultRelInfo->ri_RelationDesc->rd_att);
+
+ /*
+ * The onConflictSet tlist should already have been adjusted to emit
+ * the table's exact column list. It could also contain resjunk
+ * columns, which should be evaluated but not included in the
+ * projection result.
+ */
+ ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+ node->onConflictSet);
/* build UPDATE SET projection state */
resultRelInfo->ri_onConflictSetProj =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- mtstate->mt_conflproj, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
+ ExecBuildProjectionInfoExt(node->onConflictSet, econtext,
+ mtstate->mt_conflproj, false,
+ &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
/* build DO UPDATE WHERE clause expression */
if (node->onConflictWhere)
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 379e7c77a18..da6ec7cab3d 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -263,6 +263,12 @@ extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
TupleTableSlot *slot,
PlanState *parent,
TupleDesc inputDesc);
+extern ProjectionInfo *ExecBuildProjectionInfoExt(List *targetList,
+ ExprContext *econtext,
+ TupleTableSlot *slot,
+ bool assignJunkEntries,
+ PlanState *parent,
+ TupleDesc inputDesc);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern ExprState *ExecPrepareQual(List *qual, EState *estate);
extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 1778023600e..54166727fba 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -199,7 +199,7 @@ SELECT a, b, char_length(c) FROM update_test;
(4 rows)
-- Test ON CONFLICT DO UPDATE
-INSERT INTO upsert_test VALUES(1, 'Boo');
+INSERT INTO upsert_test VALUES(1, 'Boo'), (3, 'Zoo');
-- uncorrelated sub-select:
WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test
VALUES (1, 'Bar') ON CONFLICT(a)
@@ -210,22 +210,24 @@ WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test
(1 row)
-- correlated sub-select:
-INSERT INTO upsert_test VALUES (1, 'Baz') ON CONFLICT(a)
+INSERT INTO upsert_test VALUES (1, 'Baz'), (3, 'Zaz') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a)
RETURNING *;
a | b
---+-----------------
1 | Foo, Correlated
-(1 row)
+ 3 | Zoo, Correlated
+(2 rows)
-- correlated sub-select (EXCLUDED.* alias):
-INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
+INSERT INTO upsert_test VALUES (1, 'Bat'), (3, 'Zot') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING *;
a | b
---+---------------------------
1 | Foo, Correlated, Excluded
-(1 row)
+ 3 | Zoo, Correlated, Excluded
+(2 rows)
DROP TABLE update_test;
DROP TABLE upsert_test;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 4cfb8ac7bef..d8ff0bc6ff6 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -100,17 +100,17 @@ UPDATE update_test t
SELECT a, b, char_length(c) FROM update_test;
-- Test ON CONFLICT DO UPDATE
-INSERT INTO upsert_test VALUES(1, 'Boo');
+INSERT INTO upsert_test VALUES(1, 'Boo'), (3, 'Zoo');
-- uncorrelated sub-select:
WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test
VALUES (1, 'Bar') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b, a FROM aaa) RETURNING *;
-- correlated sub-select:
-INSERT INTO upsert_test VALUES (1, 'Baz') ON CONFLICT(a)
+INSERT INTO upsert_test VALUES (1, 'Baz'), (3, 'Zaz') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a)
RETURNING *;
-- correlated sub-select (EXCLUDED.* alias):
-INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
+INSERT INTO upsert_test VALUES (1, 'Bat'), (3, 'Zot') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING *;