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 and toast the tuple if necessary.
*
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
TupleTableSlot *slot,
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;
*/
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;
ExecInitExprRec(tle->expr, 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.
* 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];
* 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];
* 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];
{
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;
{
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] =
*
* 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;
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];
*/
if (node->onConflictAction == ONCONFLICT_UPDATE)
{
+ OnConflictSetState *onconfl = makeNode(OnConflictSetState);
TupleConversionMap *map;
map = leaf_part_rri->ri_PartitionInfo->pi_RootToPartitionMap;
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);
* 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;
bool found_whole_row;
/*
* 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),
&found_whole_row);
/* We ignore the value of found_whole_row. */
- /* Finally, adjust this tlist to match the partition. */
+ /* Finally, reorder the tlist to match the partition. */
onconflset = adjust_partition_tlist(onconflset, map);
/* 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 =
+ ExecBuildProjectionInfoExt(onconflset, econtext,
+ onconfl->oc_ProjSlot, false,
+ &mtstate->ps, partrelDesc);
/*
* If there is a WHERE clause, initialize state where it will
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);
}
}
/*
* adjust_partition_tlist
- * Adjust the targetlist entries for a given partition to account for
- * attribute differences between parent and the partition
+ * Re-order the targetlist entries for a given partition to account for
+ * column position differences between the 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.
+ * The expressions have already been fixed, but we must now re-order the
+ * entries in case the partition has different column order, and possibly
+ * add or remove dummy entries for dropped columns.
*
- * Scribbles on the input tlist, so callers must make sure to make a copy
- * before passing it to us.
+ * Although a new List is returned, this feels free to scribble on resno
+ * fields of the given tlist, so that should be a working copy.
*/
static List *
adjust_partition_tlist(List *tlist, TupleConversionMap *map)
TupleDesc tupdesc = map->outdesc;
AttrMap *attrMap = map->attrMap;
AttrNumber attrno;
+ ListCell *lc;
Assert(tupdesc->natts == attrMap->maplen);
for (attrno = 1; attrno <= tupdesc->natts; attrno++)
{
Form_pg_attribute att_tup = TupleDescAttr(tupdesc, attrno - 1);
+ AttrNumber parentattrno = attrMap->attnums[attrno - 1];
TargetEntry *tle;
- if (attrMap->attnums[attrno - 1] != InvalidAttrNumber)
+ if (parentattrno != InvalidAttrNumber)
{
- Assert(!att_tup->attisdropped);
-
/*
* Use the corresponding entry from the parent's tlist, adjusting
- * the resno the match the partition's attno.
+ * the resno to match the partition's attno.
*/
- tle = (TargetEntry *) list_nth(tlist, attrMap->attnums[attrno - 1] - 1);
+ Assert(!att_tup->attisdropped);
+ tle = (TargetEntry *) list_nth(tlist, parentattrno - 1);
+ Assert(!tle->resjunk);
+ Assert(tle->resno == parentattrno);
tle->resno = attrno;
}
else
{
- Const *expr;
-
/*
* For a dropped attribute in the partition, generate a dummy
- * entry with resno matching the partition's attno.
+ * entry with resno matching the partition's attno. This should
+ * match what expand_targetlist() does.
*/
+ Const *expr;
+
Assert(att_tup->attisdropped);
expr = makeConst(INT4OID,
-1,
new_tlist = lappend(new_tlist, tle);
}
+ /* Finally, attach any resjunk entries to the end of the new tlist */
+ foreach(lc, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (tle->resjunk)
+ {
+ tle->resno = list_length(new_tlist) + 1;
+ new_tlist = lappend(new_tlist, tle);
+ }
+ }
+
return new_tlist;
}
*/
if (node->onConflictAction == ONCONFLICT_UPDATE)
{
+ OnConflictSetState *onconfl = makeNode(OnConflictSetState);
ExprContext *econtext;
TupleDesc relationDesc;
- TupleDesc tupDesc;
/* insert may only have one plan, inheritance is not expanded */
Assert(nplans == 1);
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);
* 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);
+
+ /*
+ * 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_onConflict->oc_ProjInfo =
- ExecBuildProjectionInfo(node->onConflictSet, econtext,
- resultRelInfo->ri_onConflict->oc_ProjSlot,
- &mtstate->ps,
- relationDesc);
+ onconfl->oc_ProjInfo =
+ ExecBuildProjectionInfoExt(node->onConflictSet, econtext,
+ onconfl->oc_ProjSlot, false,
+ &mtstate->ps,
+ relationDesc);
/* initialize state to evaluate the WHERE clause, if any */
if (node->onConflictWhere)
qualexpr = ExecInitQual((List *) node->onConflictWhere,
&mtstate->ps);
- resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+ onconfl->oc_WhereClause = qualexpr;
}
}
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);
(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)
(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)
-- ON CONFLICT using system attributes in RETURNING, testing both the
-- inserting and updating paths. See bug report at:
(1 row)
DROP TABLE update_test;
+DROP TABLE upsert_test;
+-- Test ON CONFLICT DO UPDATE with partitioned table and non-identical children
+CREATE TABLE upsert_test (
+ a INT PRIMARY KEY,
+ b TEXT
+) PARTITION BY LIST (a);
+CREATE TABLE upsert_test_1 PARTITION OF upsert_test FOR VALUES IN (1);
+CREATE TABLE upsert_test_2 (b TEXT, a INT PRIMARY KEY);
+ALTER TABLE upsert_test ATTACH PARTITION upsert_test_2 FOR VALUES IN (2);
+INSERT INTO upsert_test VALUES(1, 'Boo'), (2, '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 *;
+ a | b
+---+-----
+ 1 | Foo
+(1 row)
+
+-- correlated sub-select:
+WITH aaa AS (SELECT 1 AS ctea, ' Foo' AS cteb) INSERT INTO upsert_test
+ VALUES (1, 'Bar'), (2, 'Baz') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT upsert_test.b||cteb, upsert_test.a FROM aaa) RETURNING *;
+ a | b
+---+---------
+ 1 | Foo Foo
+ 2 | Zoo Foo
+(2 rows)
+
DROP TABLE upsert_test;
---------------------------
-- UPDATE with row movement
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 *;
DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = pg_current_xact_id()::xid AS xmax_correct;
-
DROP TABLE update_test;
DROP TABLE upsert_test;
+-- Test ON CONFLICT DO UPDATE with partitioned table and non-identical children
+
+CREATE TABLE upsert_test (
+ a INT PRIMARY KEY,
+ b TEXT
+) PARTITION BY LIST (a);
+
+CREATE TABLE upsert_test_1 PARTITION OF upsert_test FOR VALUES IN (1);
+CREATE TABLE upsert_test_2 (b TEXT, a INT PRIMARY KEY);
+ALTER TABLE upsert_test ATTACH PARTITION upsert_test_2 FOR VALUES IN (2);
+
+INSERT INTO upsert_test VALUES(1, 'Boo'), (2, '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:
+WITH aaa AS (SELECT 1 AS ctea, ' Foo' AS cteb) INSERT INTO upsert_test
+ VALUES (1, 'Bar'), (2, 'Baz') ON CONFLICT(a)
+ DO UPDATE SET (b, a) = (SELECT upsert_test.b||cteb, upsert_test.a FROM aaa) RETURNING *;
+
+DROP TABLE upsert_test;
+
---------------------------
-- UPDATE with row movement