Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeModifyTable.c
4 : : * routines to handle ModifyTable nodes.
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/executor/nodeModifyTable.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : /*
16 : : * INTERFACE ROUTINES
17 : : * ExecInitModifyTable - initialize the ModifyTable node
18 : : * ExecModifyTable - retrieve the next tuple from the node
19 : : * ExecEndModifyTable - shut down the ModifyTable node
20 : : * ExecReScanModifyTable - rescan the ModifyTable node
21 : : *
22 : : * NOTES
23 : : * The ModifyTable node receives input from its outerPlan, which is
24 : : * the data to insert for INSERT cases, the changed columns' new
25 : : * values plus row-locating info for UPDATE and MERGE cases, or just the
26 : : * row-locating info for DELETE cases.
27 : : *
28 : : * The relation to modify can be an ordinary table, a foreign table, or a
29 : : * view. If it's a view, either it has sufficient INSTEAD OF triggers or
30 : : * this node executes only MERGE ... DO NOTHING. If the original MERGE
31 : : * targeted a view not in one of those two categories, earlier processing
32 : : * already pointed the ModifyTable result relation to an underlying
33 : : * relation of that other view. This node does process
34 : : * ri_WithCheckOptions, which may have expressions from those other,
35 : : * automatically updatable views.
36 : : *
37 : : * MERGE runs a join between the source relation and the target table.
38 : : * If any WHEN NOT MATCHED [BY TARGET] clauses are present, then the join
39 : : * is an outer join that might output tuples without a matching target
40 : : * tuple. In this case, any unmatched target tuples will have NULL
41 : : * row-locating info, and only INSERT can be run. But for matched target
42 : : * tuples, the row-locating info is used to determine the tuple to UPDATE
43 : : * or DELETE. When all clauses are WHEN MATCHED or WHEN NOT MATCHED BY
44 : : * SOURCE, all tuples produced by the join will include a matching target
45 : : * tuple, so all tuples contain row-locating info.
46 : : *
47 : : * If the query specifies RETURNING, then the ModifyTable returns a
48 : : * RETURNING tuple after completing each row insert, update, or delete.
49 : : * It must be called again to continue the operation. Without RETURNING,
50 : : * we just loop within the node until all the work is done, then
51 : : * return NULL. This avoids useless call/return overhead.
52 : : */
53 : :
54 : : #include "postgres.h"
55 : :
56 : : #include "access/htup_details.h"
57 : : #include "access/tableam.h"
58 : : #include "access/tupconvert.h"
59 : : #include "access/xact.h"
60 : : #include "commands/trigger.h"
61 : : #include "executor/execPartition.h"
62 : : #include "executor/executor.h"
63 : : #include "executor/instrument.h"
64 : : #include "executor/nodeModifyTable.h"
65 : : #include "foreign/fdwapi.h"
66 : : #include "miscadmin.h"
67 : : #include "nodes/nodeFuncs.h"
68 : : #include "optimizer/optimizer.h"
69 : : #include "pgstat.h"
70 : : #include "rewrite/rewriteHandler.h"
71 : : #include "rewrite/rewriteManip.h"
72 : : #include "storage/lmgr.h"
73 : : #include "utils/builtins.h"
74 : : #include "utils/datum.h"
75 : : #include "utils/injection_point.h"
76 : : #include "utils/rel.h"
77 : : #include "utils/snapmgr.h"
78 : :
79 : :
80 : : typedef struct MTTargetRelLookup
81 : : {
82 : : Oid relationOid; /* hash key, must be first */
83 : : int relationIndex; /* rel's index in resultRelInfo[] array */
84 : : } MTTargetRelLookup;
85 : :
86 : : /*
87 : : * Context struct for a ModifyTable operation, containing basic execution
88 : : * state and some output variables populated by ExecUpdateAct() and
89 : : * ExecDeleteAct() to report the result of their actions to callers.
90 : : */
91 : : typedef struct ModifyTableContext
92 : : {
93 : : /* Operation state */
94 : : ModifyTableState *mtstate;
95 : : EPQState *epqstate;
96 : : EState *estate;
97 : :
98 : : /*
99 : : * Slot containing tuple obtained from ModifyTable's subplan. Used to
100 : : * access "junk" columns that are not going to be stored.
101 : : */
102 : : TupleTableSlot *planSlot;
103 : :
104 : : /*
105 : : * Information about the changes that were made concurrently to a tuple
106 : : * being updated or deleted
107 : : */
108 : : TM_FailureData tmfd;
109 : :
110 : : /*
111 : : * The tuple deleted when doing a cross-partition UPDATE with a RETURNING
112 : : * clause that refers to OLD columns (converted to the root's tuple
113 : : * descriptor).
114 : : */
115 : : TupleTableSlot *cpDeletedSlot;
116 : :
117 : : /*
118 : : * The tuple projected by the INSERT's RETURNING clause, when doing a
119 : : * cross-partition UPDATE
120 : : */
121 : : TupleTableSlot *cpUpdateReturningSlot;
122 : : } ModifyTableContext;
123 : :
124 : : /*
125 : : * Context struct containing output data specific to UPDATE operations.
126 : : */
127 : : typedef struct UpdateContext
128 : : {
129 : : bool crossPartUpdate; /* was it a cross-partition update? */
130 : : TU_UpdateIndexes updateIndexes; /* Which index updates are required? */
131 : :
132 : : /*
133 : : * Lock mode to acquire on the latest tuple version before performing
134 : : * EvalPlanQual on it
135 : : */
136 : : LockTupleMode lockmode;
137 : : } UpdateContext;
138 : :
139 : :
140 : : static void ExecBatchInsert(ModifyTableState *mtstate,
141 : : ResultRelInfo *resultRelInfo,
142 : : TupleTableSlot **slots,
143 : : TupleTableSlot **planSlots,
144 : : int numSlots,
145 : : EState *estate,
146 : : bool canSetTag);
147 : : static void ExecPendingInserts(EState *estate);
148 : : static void ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
149 : : ResultRelInfo *sourcePartInfo,
150 : : ResultRelInfo *destPartInfo,
151 : : ItemPointer tupleid,
152 : : TupleTableSlot *oldslot,
153 : : TupleTableSlot *newslot);
154 : : static bool ExecOnConflictLockRow(ModifyTableContext *context,
155 : : TupleTableSlot *existing,
156 : : ItemPointer conflictTid,
157 : : Relation relation,
158 : : LockTupleMode lockmode,
159 : : bool isUpdate);
160 : : static bool ExecOnConflictUpdate(ModifyTableContext *context,
161 : : ResultRelInfo *resultRelInfo,
162 : : ItemPointer conflictTid,
163 : : TupleTableSlot *excludedSlot,
164 : : bool canSetTag,
165 : : TupleTableSlot **returning);
166 : : static bool ExecOnConflictSelect(ModifyTableContext *context,
167 : : ResultRelInfo *resultRelInfo,
168 : : ItemPointer conflictTid,
169 : : TupleTableSlot *excludedSlot,
170 : : bool canSetTag,
171 : : TupleTableSlot **returning);
172 : : static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
173 : : EState *estate,
174 : : PartitionTupleRouting *proute,
175 : : ResultRelInfo *targetRelInfo,
176 : : TupleTableSlot *slot,
177 : : ResultRelInfo **partRelInfo);
178 : :
179 : : static TupleTableSlot *ExecMerge(ModifyTableContext *context,
180 : : ResultRelInfo *resultRelInfo,
181 : : ItemPointer tupleid,
182 : : HeapTuple oldtuple,
183 : : bool canSetTag);
184 : : static void ExecInitMerge(ModifyTableState *mtstate, EState *estate);
185 : : static TupleTableSlot *ExecMergeMatched(ModifyTableContext *context,
186 : : ResultRelInfo *resultRelInfo,
187 : : ItemPointer tupleid,
188 : : HeapTuple oldtuple,
189 : : bool canSetTag,
190 : : bool *matched);
191 : : static TupleTableSlot *ExecMergeNotMatched(ModifyTableContext *context,
192 : : ResultRelInfo *resultRelInfo,
193 : : bool canSetTag);
194 : :
195 : :
196 : : /*
197 : : * Verify that the tuples to be produced by INSERT match the
198 : : * target relation's rowtype
199 : : *
200 : : * We do this to guard against stale plans. If plan invalidation is
201 : : * functioning properly then we should never get a failure here, but better
202 : : * safe than sorry. Note that this is called after we have obtained lock
203 : : * on the target rel, so the rowtype can't change underneath us.
204 : : *
205 : : * The plan output is represented by its targetlist, because that makes
206 : : * handling the dropped-column case easier.
207 : : *
208 : : * We used to use this for UPDATE as well, but now the equivalent checks
209 : : * are done in ExecBuildUpdateProjection.
210 : : */
211 : : static void
6211 tgl@sss.pgh.pa.us 212 :CBC 64905 : ExecCheckPlanOutput(Relation resultRel, List *targetList)
213 : : {
214 : 64905 : TupleDesc resultDesc = RelationGetDescr(resultRel);
215 : 64905 : int attno = 0;
216 : : ListCell *lc;
217 : :
218 [ + + + + : 200706 : foreach(lc, targetList)
+ + ]
219 : : {
220 : 135801 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
221 : : Form_pg_attribute attr;
222 : :
2021 223 [ - + ]: 135801 : Assert(!tle->resjunk); /* caller removed junk items already */
224 : :
6211 225 [ - + ]: 135801 : if (attno >= resultDesc->natts)
6211 tgl@sss.pgh.pa.us 226 [ # # ]:UBC 0 : ereport(ERROR,
227 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
228 : : errmsg("table row type and query-specified row type do not match"),
229 : : errdetail("Query has too many columns.")));
3340 andres@anarazel.de 230 :CBC 135801 : attr = TupleDescAttr(resultDesc, attno);
231 : 135801 : attno++;
232 : :
233 : : /*
234 : : * Special cases here should match planner's expand_insert_targetlist.
235 : : */
545 tgl@sss.pgh.pa.us 236 [ + + ]: 135801 : if (attr->attisdropped)
237 : : {
238 : : /*
239 : : * For a dropped column, we can't check atttypid (it's likely 0).
240 : : * In any case the planner has most likely inserted an INT4 null.
241 : : * What we insist on is just *some* NULL constant.
242 : : */
243 [ + - ]: 456 : if (!IsA(tle->expr, Const) ||
244 [ - + ]: 456 : !((Const *) tle->expr)->constisnull)
6211 tgl@sss.pgh.pa.us 245 [ # # ]:UBC 0 : ereport(ERROR,
246 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
247 : : errmsg("table row type and query-specified row type do not match"),
248 : : errdetail("Query provides a value for a dropped column at ordinal position %d.",
249 : : attno)));
250 : : }
545 tgl@sss.pgh.pa.us 251 [ + + ]:CBC 135345 : else if (attr->attgenerated)
252 : : {
253 : : /*
254 : : * For a generated column, the planner will have inserted a null
255 : : * of the column's base type (to avoid possibly failing on domain
256 : : * not-null constraints). It doesn't seem worth insisting on that
257 : : * exact type though, since a null value is type-independent. As
258 : : * above, just insist on *some* NULL constant.
259 : : */
6211 260 [ + - ]: 835 : if (!IsA(tle->expr, Const) ||
261 [ - + ]: 835 : !((Const *) tle->expr)->constisnull)
6211 tgl@sss.pgh.pa.us 262 [ # # ]:UBC 0 : ereport(ERROR,
263 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
264 : : errmsg("table row type and query-specified row type do not match"),
265 : : errdetail("Query provides a value for a generated column at ordinal position %d.",
266 : : attno)));
267 : : }
268 : : else
269 : : {
270 : : /* Normal case: demand type match */
545 tgl@sss.pgh.pa.us 271 [ - + ]:CBC 134510 : if (exprType((Node *) tle->expr) != attr->atttypid)
545 tgl@sss.pgh.pa.us 272 [ # # ]:UBC 0 : ereport(ERROR,
273 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
274 : : errmsg("table row type and query-specified row type do not match"),
275 : : errdetail("Table has type %s at ordinal position %d, but query expects %s.",
276 : : format_type_be(attr->atttypid),
277 : : attno,
278 : : format_type_be(exprType((Node *) tle->expr)))));
279 : : }
280 : : }
6211 tgl@sss.pgh.pa.us 281 [ - + ]:CBC 64905 : if (attno != resultDesc->natts)
6211 tgl@sss.pgh.pa.us 282 [ # # ]:UBC 0 : ereport(ERROR,
283 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
284 : : errmsg("table row type and query-specified row type do not match"),
285 : : errdetail("Query has too few columns.")));
6211 tgl@sss.pgh.pa.us 286 :CBC 64905 : }
287 : :
288 : : /*
289 : : * ExecProcessReturning --- evaluate a RETURNING list
290 : : *
291 : : * context: context for the ModifyTable operation
292 : : * resultRelInfo: current result rel
293 : : * isDelete: true if the operation/merge action is a DELETE
294 : : * oldSlot: slot holding old tuple deleted or updated
295 : : * newSlot: slot holding new tuple inserted or updated
296 : : * planSlot: slot holding tuple returned by top subplan node
297 : : *
298 : : * Note: If oldSlot and newSlot are NULL, the FDW should have already provided
299 : : * econtext's scan tuple and its old & new tuples are not needed (FDW direct-
300 : : * modify is disabled if the RETURNING list refers to any OLD/NEW values).
301 : : *
302 : : * Note: For the SELECT path of INSERT ... ON CONFLICT DO SELECT, oldSlot and
303 : : * newSlot are both the existing tuple, since it's not changed.
304 : : *
305 : : * Returns a slot holding the result tuple
306 : : */
307 : : static TupleTableSlot *
634 dean.a.rasheed@gmail 308 : 5661 : ExecProcessReturning(ModifyTableContext *context,
309 : : ResultRelInfo *resultRelInfo,
310 : : bool isDelete,
311 : : TupleTableSlot *oldSlot,
312 : : TupleTableSlot *newSlot,
313 : : TupleTableSlot *planSlot)
314 : : {
315 : 5661 : EState *estate = context->estate;
3860 rhaas@postgresql.org 316 : 5661 : ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
6211 tgl@sss.pgh.pa.us 317 : 5661 : ExprContext *econtext = projectReturning->pi_exprContext;
318 : :
319 : : /* Make tuple and any needed join variables available to ExecProject */
242 dean.a.rasheed@gmail 320 [ + + ]: 5661 : if (isDelete)
321 : : {
322 : : /* return old tuple by default */
323 [ + + ]: 880 : if (oldSlot)
324 : 761 : econtext->ecxt_scantuple = oldSlot;
325 : : }
326 : : else
327 : : {
328 : : /* return new tuple by default */
329 [ + + ]: 4781 : if (newSlot)
330 : 4551 : econtext->ecxt_scantuple = newSlot;
331 : : }
6211 tgl@sss.pgh.pa.us 332 : 5661 : econtext->ecxt_outertuple = planSlot;
333 : :
334 : : /* Make old/new tuples available to ExecProject, if required */
634 dean.a.rasheed@gmail 335 [ + + ]: 5661 : if (oldSlot)
336 : 2593 : econtext->ecxt_oldtuple = oldSlot;
337 [ + + ]: 3068 : else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD)
338 : 138 : econtext->ecxt_oldtuple = ExecGetAllNullSlot(estate, resultRelInfo);
339 : : else
340 : 2930 : econtext->ecxt_oldtuple = NULL; /* No references to OLD columns */
341 : :
342 [ + + ]: 5661 : if (newSlot)
343 : 4551 : econtext->ecxt_newtuple = newSlot;
344 [ + + ]: 1110 : else if (projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW)
345 : 103 : econtext->ecxt_newtuple = ExecGetAllNullSlot(estate, resultRelInfo);
346 : : else
347 : 1007 : econtext->ecxt_newtuple = NULL; /* No references to NEW columns */
348 : :
349 : : /*
350 : : * Tell ExecProject whether or not the OLD/NEW rows actually exist. This
351 : : * information is required to evaluate ReturningExpr nodes and also in
352 : : * ExecEvalSysVar() and ExecEvalWholeRowVar().
353 : : */
354 [ + + ]: 5661 : if (oldSlot == NULL)
355 : 3068 : projectReturning->pi_state.flags |= EEO_FLAG_OLD_IS_NULL;
356 : : else
357 : 2593 : projectReturning->pi_state.flags &= ~EEO_FLAG_OLD_IS_NULL;
358 : :
359 [ + + ]: 5661 : if (newSlot == NULL)
360 : 1110 : projectReturning->pi_state.flags |= EEO_FLAG_NEW_IS_NULL;
361 : : else
362 : 4551 : projectReturning->pi_state.flags &= ~EEO_FLAG_NEW_IS_NULL;
363 : :
364 : : /* Compute the RETURNING expressions */
3553 andres@anarazel.de 365 : 5661 : return ExecProject(projectReturning);
366 : : }
367 : :
368 : : /*
369 : : * ExecCheckTupleVisible -- verify tuple is visible
370 : : *
371 : : * It would not be consistent with guarantees of the higher isolation levels to
372 : : * proceed with avoiding insertion (taking speculative insertion's alternative
373 : : * path) on the basis of another tuple that is not visible to MVCC snapshot.
374 : : * Check for the need to raise a serialization failure, and do so as necessary.
375 : : */
376 : : static void
2760 377 : 2982 : ExecCheckTupleVisible(EState *estate,
378 : : Relation rel,
379 : : TupleTableSlot *slot)
380 : : {
4175 381 [ + + ]: 2982 : if (!IsolationUsesXactSnapshot())
382 : 2930 : return;
383 : :
2760 384 [ + + ]: 52 : if (!table_tuple_satisfies_snapshot(rel, slot, estate->es_snapshot))
385 : : {
386 : : Datum xminDatum;
387 : : TransactionId xmin;
388 : : bool isnull;
389 : :
390 : 31 : xminDatum = slot_getsysattr(slot, MinTransactionIdAttributeNumber, &isnull);
391 [ - + ]: 31 : Assert(!isnull);
392 : 31 : xmin = DatumGetTransactionId(xminDatum);
393 : :
394 : : /*
395 : : * We should not raise a serialization failure if the conflict is
396 : : * against a tuple inserted by our own transaction, even if it's not
397 : : * visible to our snapshot. (This would happen, for example, if
398 : : * conflicting keys are proposed for insertion in a single command.)
399 : : */
400 [ + + ]: 31 : if (!TransactionIdIsCurrentTransactionId(xmin))
3641 tgl@sss.pgh.pa.us 401 [ + - ]: 11 : ereport(ERROR,
402 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
403 : : errmsg("could not serialize access due to concurrent update")));
404 : : }
405 : : }
406 : :
407 : : /*
408 : : * ExecCheckTIDVisible -- convenience variant of ExecCheckTupleVisible()
409 : : */
410 : : static void
4175 andres@anarazel.de 411 : 142 : ExecCheckTIDVisible(EState *estate,
412 : : ResultRelInfo *relinfo,
413 : : ItemPointer tid,
414 : : TupleTableSlot *tempSlot)
415 : : {
416 : 142 : Relation rel = relinfo->ri_RelationDesc;
417 : :
418 : : /* Redundantly check isolation level */
419 [ + + ]: 142 : if (!IsolationUsesXactSnapshot())
420 : 105 : return;
421 : :
2699 422 [ - + ]: 37 : if (!table_tuple_fetch_row_version(rel, tid, SnapshotAny, tempSlot))
4175 andres@anarazel.de 423 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
2760 andres@anarazel.de 424 :CBC 37 : ExecCheckTupleVisible(estate, rel, tempSlot);
425 : 27 : ExecClearTuple(tempSlot);
426 : : }
427 : :
428 : : /*
429 : : * Initialize generated columns handling for a tuple
430 : : *
431 : : * This fills the resultRelInfo's ri_GeneratedExprsI/ri_NumGeneratedNeededI or
432 : : * ri_GeneratedExprsU/ri_NumGeneratedNeededU fields, depending on cmdtype.
433 : : * This is used only for stored generated columns.
434 : : *
435 : : * If cmdType == CMD_UPDATE, the ri_extraUpdatedCols field is filled too.
436 : : * This is used by both stored and virtual generated columns.
437 : : *
438 : : * Note: usually, a given query would need only one of ri_GeneratedExprsI and
439 : : * ri_GeneratedExprsU per result rel; but MERGE can need both, and so can
440 : : * cross-partition UPDATEs, since a partition might be the target of both
441 : : * UPDATE and INSERT actions.
442 : : */
443 : : void
612 peter@eisentraut.org 444 : 31534 : ExecInitGenerated(ResultRelInfo *resultRelInfo,
445 : : EState *estate,
446 : : CmdType cmdtype)
447 : : {
2753 448 : 31534 : Relation rel = resultRelInfo->ri_RelationDesc;
449 : 31534 : TupleDesc tupdesc = RelationGetDescr(rel);
450 : 31534 : int natts = tupdesc->natts;
451 : : ExprState **ri_GeneratedExprs;
452 : : int ri_NumGeneratedNeeded;
453 : : Bitmapset *updatedCols;
454 : : MemoryContext oldContext;
455 : :
456 : : /* Nothing to do if no generated columns */
612 457 [ + + + + : 31534 : if (!(tupdesc->constr && (tupdesc->constr->has_generated_stored || tupdesc->constr->has_generated_virtual)))
+ + ]
1376 tgl@sss.pgh.pa.us 458 : 30800 : return;
459 : :
460 : : /*
461 : : * In an UPDATE, we can skip computing any generated columns that do not
462 : : * depend on any UPDATE target column. But if there is a BEFORE ROW
463 : : * UPDATE trigger, we cannot skip because the trigger might change more
464 : : * columns.
465 : : */
466 [ + + ]: 734 : if (cmdtype == CMD_UPDATE &&
467 [ + + - + ]: 188 : !(rel->trigdesc && rel->trigdesc->trig_update_before_row))
468 : 160 : updatedCols = ExecGetUpdatedCols(resultRelInfo, estate);
469 : : else
470 : 574 : updatedCols = NULL;
471 : :
472 : : /*
473 : : * Make sure these data structures are built in the per-query memory
474 : : * context so they'll survive throughout the query.
475 : : */
476 : 734 : oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
477 : :
56 michael@paquier.xyz 478 :GNC 734 : ri_GeneratedExprs = palloc0_array(ExprState *, natts);
1316 tgl@sss.pgh.pa.us 479 :CBC 734 : ri_NumGeneratedNeeded = 0;
480 : :
1376 481 [ + + ]: 2788 : for (int i = 0; i < natts; i++)
482 : : {
612 peter@eisentraut.org 483 : 2058 : char attgenerated = TupleDescAttr(tupdesc, i)->attgenerated;
484 : :
485 [ + + ]: 2058 : if (attgenerated)
486 : : {
487 : : Expr *expr;
488 : :
489 : : /* Fetch the GENERATED AS expression tree */
1376 tgl@sss.pgh.pa.us 490 : 782 : expr = (Expr *) build_column_default(rel, i + 1);
491 [ - + ]: 782 : if (expr == NULL)
1376 tgl@sss.pgh.pa.us 492 [ # # ]:UBC 0 : elog(ERROR, "no generation expression found for column number %d of table \"%s\"",
493 : : i + 1, RelationGetRelationName(rel));
494 : :
495 : : /*
496 : : * If it's an update with a known set of update target columns,
497 : : * see if we can skip the computation.
498 : : */
1376 tgl@sss.pgh.pa.us 499 [ + + ]:CBC 782 : if (updatedCols)
500 : : {
501 : 168 : Bitmapset *attrs_used = NULL;
502 : :
503 : 168 : pull_varattnos((Node *) expr, 1, &attrs_used);
504 : :
505 [ + + ]: 168 : if (!bms_overlap(updatedCols, attrs_used))
506 : 21 : continue; /* need not update this column */
507 : : }
508 : :
509 : : /* No luck, so prepare the expression for execution */
612 peter@eisentraut.org 510 [ + + ]: 761 : if (attgenerated == ATTRIBUTE_GENERATED_STORED)
511 : : {
512 : 692 : ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate);
513 : 688 : ri_NumGeneratedNeeded++;
514 : : }
515 : :
516 : : /* If UPDATE, mark column in resultRelInfo->ri_extraUpdatedCols */
1316 tgl@sss.pgh.pa.us 517 [ + + ]: 757 : if (cmdtype == CMD_UPDATE)
518 : 179 : resultRelInfo->ri_extraUpdatedCols =
519 : 179 : bms_add_member(resultRelInfo->ri_extraUpdatedCols,
520 : : i + 1 - FirstLowInvalidHeapAttributeNumber);
521 : : }
522 : : }
523 : :
612 peter@eisentraut.org 524 [ + + ]: 730 : if (ri_NumGeneratedNeeded == 0)
525 : : {
526 : : /* didn't need it after all */
527 : 53 : pfree(ri_GeneratedExprs);
528 : 53 : ri_GeneratedExprs = NULL;
529 : : }
530 : :
531 : : /* Save in appropriate set of fields */
1316 tgl@sss.pgh.pa.us 532 [ + + ]: 730 : if (cmdtype == CMD_UPDATE)
533 : : {
534 : : /* Don't call twice */
535 [ - + ]: 188 : Assert(resultRelInfo->ri_GeneratedExprsU == NULL);
536 : :
537 : 188 : resultRelInfo->ri_GeneratedExprsU = ri_GeneratedExprs;
538 : 188 : resultRelInfo->ri_NumGeneratedNeededU = ri_NumGeneratedNeeded;
539 : :
612 peter@eisentraut.org 540 : 188 : resultRelInfo->ri_extraUpdatedCols_valid = true;
541 : : }
542 : : else
543 : : {
544 : : /* Don't call twice */
1316 tgl@sss.pgh.pa.us 545 [ - + ]: 542 : Assert(resultRelInfo->ri_GeneratedExprsI == NULL);
546 : :
547 : 542 : resultRelInfo->ri_GeneratedExprsI = ri_GeneratedExprs;
548 : 542 : resultRelInfo->ri_NumGeneratedNeededI = ri_NumGeneratedNeeded;
549 : : }
550 : :
1376 551 : 730 : MemoryContextSwitchTo(oldContext);
552 : : }
553 : :
554 : : /*
555 : : * Compute stored generated columns for a tuple
556 : : */
557 : : void
558 : 981 : ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
559 : : EState *estate, TupleTableSlot *slot,
560 : : CmdType cmdtype)
561 : : {
562 : 981 : Relation rel = resultRelInfo->ri_RelationDesc;
563 : 981 : TupleDesc tupdesc = RelationGetDescr(rel);
564 : 981 : int natts = tupdesc->natts;
565 [ + + ]: 981 : ExprContext *econtext = GetPerTupleExprContext(estate);
566 : : ExprState **ri_GeneratedExprs;
567 : : MemoryContext oldContext;
568 : : Datum *values;
569 : : bool *nulls;
570 : :
571 : : /* We should not be called unless this is true */
572 [ + - - + ]: 981 : Assert(tupdesc->constr && tupdesc->constr->has_generated_stored);
573 : :
574 : : /*
575 : : * Initialize the expressions if we didn't already, and check whether we
576 : : * can exit early because nothing needs to be computed.
577 : : */
1316 578 [ + + ]: 981 : if (cmdtype == CMD_UPDATE)
579 : : {
580 [ + + ]: 176 : if (resultRelInfo->ri_GeneratedExprsU == NULL)
612 peter@eisentraut.org 581 : 135 : ExecInitGenerated(resultRelInfo, estate, cmdtype);
1316 tgl@sss.pgh.pa.us 582 [ + + ]: 176 : if (resultRelInfo->ri_NumGeneratedNeededU == 0)
583 : 17 : return;
584 : 159 : ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsU;
585 : : }
586 : : else
587 : : {
588 [ + + ]: 805 : if (resultRelInfo->ri_GeneratedExprsI == NULL)
612 peter@eisentraut.org 589 : 546 : ExecInitGenerated(resultRelInfo, estate, cmdtype);
590 : : /* Early exit is impossible given the prior Assert */
1316 tgl@sss.pgh.pa.us 591 [ - + ]: 801 : Assert(resultRelInfo->ri_NumGeneratedNeededI > 0);
592 : 801 : ri_GeneratedExprs = resultRelInfo->ri_GeneratedExprsI;
593 : : }
594 : :
2753 peter@eisentraut.org 595 [ + - ]: 960 : oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
596 : :
306 michael@paquier.xyz 597 : 960 : values = palloc_array(Datum, natts);
598 : 960 : nulls = palloc_array(bool, natts);
599 : :
2707 peter@eisentraut.org 600 : 960 : slot_getallattrs(slot);
601 : 960 : memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
602 : :
2753 603 [ + + ]: 3563 : for (int i = 0; i < natts; i++)
604 : : {
661 drowley@postgresql.o 605 : 2619 : CompactAttribute *attr = TupleDescCompactAttr(tupdesc, i);
606 : :
1316 tgl@sss.pgh.pa.us 607 [ + + ]: 2619 : if (ri_GeneratedExprs[i])
608 : : {
609 : : Datum val;
610 : : bool isnull;
611 : :
661 drowley@postgresql.o 612 [ - + ]: 973 : Assert(TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED);
613 : :
2753 peter@eisentraut.org 614 : 973 : econtext->ecxt_scantuple = slot;
615 : :
1316 tgl@sss.pgh.pa.us 616 : 973 : val = ExecEvalExpr(ri_GeneratedExprs[i], econtext, &isnull);
617 : :
618 : : /*
619 : : * We must make a copy of val as we have no guarantees about where
620 : : * memory for a pass-by-reference Datum is located.
621 : : */
2368 drowley@postgresql.o 622 [ + + ]: 957 : if (!isnull)
623 : 901 : val = datumCopy(val, attr->attbyval, attr->attlen);
624 : :
2753 peter@eisentraut.org 625 : 957 : values[i] = val;
626 : 957 : nulls[i] = isnull;
627 : : }
628 : : else
629 : : {
2707 630 [ + + ]: 1646 : if (!nulls[i])
631 : 1550 : values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
632 : : }
633 : : }
634 : :
635 : 944 : ExecClearTuple(slot);
636 : 944 : memcpy(slot->tts_values, values, sizeof(*values) * natts);
637 : 944 : memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
638 : 944 : ExecStoreVirtualTuple(slot);
639 : 944 : ExecMaterializeSlot(slot);
640 : :
2753 641 : 944 : MemoryContextSwitchTo(oldContext);
642 : : }
643 : :
644 : : /*
645 : : * ExecInitInsertProjection
646 : : * Do one-time initialization of projection data for INSERT tuples.
647 : : *
648 : : * INSERT queries may need a projection to filter out junk attrs in the tlist.
649 : : *
650 : : * This is also a convenient place to verify that the
651 : : * output of an INSERT matches the target table.
652 : : */
653 : : static void
2015 tgl@sss.pgh.pa.us 654 : 64193 : ExecInitInsertProjection(ModifyTableState *mtstate,
655 : : ResultRelInfo *resultRelInfo)
656 : : {
657 : 64193 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
658 : 64193 : Plan *subplan = outerPlan(node);
659 : 64193 : EState *estate = mtstate->ps.state;
660 : 64193 : List *insertTargetList = NIL;
661 : 64193 : bool need_projection = false;
662 : : ListCell *l;
663 : :
664 : : /* Extract non-junk columns of the subplan's result tlist. */
665 [ + + + + : 198145 : foreach(l, subplan->targetlist)
+ + ]
666 : : {
667 : 133952 : TargetEntry *tle = (TargetEntry *) lfirst(l);
668 : :
669 [ + - ]: 133952 : if (!tle->resjunk)
670 : 133952 : insertTargetList = lappend(insertTargetList, tle);
671 : : else
2015 tgl@sss.pgh.pa.us 672 :UBC 0 : need_projection = true;
673 : : }
674 : :
675 : : /*
676 : : * The junk-free list must produce a tuple suitable for the result
677 : : * relation.
678 : : */
2015 tgl@sss.pgh.pa.us 679 :CBC 64193 : ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, insertTargetList);
680 : :
681 : : /* We'll need a slot matching the table's format. */
682 : 64193 : resultRelInfo->ri_newTupleSlot =
683 : 64193 : table_slot_create(resultRelInfo->ri_RelationDesc,
684 : : &estate->es_tupleTable);
685 : :
686 : : /* Build ProjectionInfo if needed (it probably isn't). */
687 [ - + ]: 64193 : if (need_projection)
688 : : {
2015 tgl@sss.pgh.pa.us 689 :UBC 0 : TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
690 : :
691 : : /* need an expression context to do the projection */
692 [ # # ]: 0 : if (mtstate->ps.ps_ExprContext == NULL)
693 : 0 : ExecAssignExprContext(estate, &mtstate->ps);
694 : :
695 : 0 : resultRelInfo->ri_projectNew =
696 : 0 : ExecBuildProjectionInfo(insertTargetList,
697 : : mtstate->ps.ps_ExprContext,
698 : : resultRelInfo->ri_newTupleSlot,
699 : : &mtstate->ps,
700 : : relDesc);
701 : : }
702 : :
2015 tgl@sss.pgh.pa.us 703 :CBC 64193 : resultRelInfo->ri_projectNewInfoValid = true;
704 : 64193 : }
705 : :
706 : : /*
707 : : * ExecInitUpdateProjection
708 : : * Do one-time initialization of projection data for UPDATE tuples.
709 : : *
710 : : * UPDATE always needs a projection, because (1) there's always some junk
711 : : * attrs, and (2) we may need to merge values of not-updated columns from
712 : : * the old tuple into the final tuple. In UPDATE, the tuple arriving from
713 : : * the subplan contains only new values for the changed columns, plus row
714 : : * identity info in the junk attrs.
715 : : *
716 : : * This is "one-time" for any given result rel, but we might touch more than
717 : : * one result rel in the course of an inherited UPDATE, and each one needs
718 : : * its own projection due to possible column order variation.
719 : : *
720 : : * This is also a convenient place to verify that the output of an UPDATE
721 : : * matches the target table (ExecBuildUpdateProjection does that).
722 : : */
723 : : static void
724 : 8322 : ExecInitUpdateProjection(ModifyTableState *mtstate,
725 : : ResultRelInfo *resultRelInfo)
726 : : {
727 : 8322 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
728 : 8322 : Plan *subplan = outerPlan(node);
729 : 8322 : EState *estate = mtstate->ps.state;
730 : 8322 : TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
731 : : int whichrel;
732 : : List *updateColnos;
733 : :
734 : : /*
735 : : * Usually, mt_lastResultIndex matches the target rel. If it happens not
736 : : * to, we can get the index the hard way with an integer division.
737 : : */
738 : 8322 : whichrel = mtstate->mt_lastResultIndex;
739 [ - + ]: 8322 : if (resultRelInfo != mtstate->resultRelInfo + whichrel)
740 : : {
2015 tgl@sss.pgh.pa.us 741 :UBC 0 : whichrel = resultRelInfo - mtstate->resultRelInfo;
742 [ # # # # ]: 0 : Assert(whichrel >= 0 && whichrel < mtstate->mt_nrels);
743 : : }
744 : :
612 amitlan@postgresql.o 745 :CBC 8322 : updateColnos = (List *) list_nth(mtstate->mt_updateColnosLists, whichrel);
746 : :
747 : : /*
748 : : * For UPDATE, we use the old tuple to fill up missing values in the tuple
749 : : * produced by the subplan to get the new tuple. We need two slots, both
750 : : * matching the table's desired format.
751 : : */
2015 tgl@sss.pgh.pa.us 752 : 8322 : resultRelInfo->ri_oldTupleSlot =
753 : 8322 : table_slot_create(resultRelInfo->ri_RelationDesc,
754 : : &estate->es_tupleTable);
755 : 8322 : resultRelInfo->ri_newTupleSlot =
756 : 8322 : table_slot_create(resultRelInfo->ri_RelationDesc,
757 : : &estate->es_tupleTable);
758 : :
759 : : /* need an expression context to do the projection */
760 [ + + ]: 8322 : if (mtstate->ps.ps_ExprContext == NULL)
761 : 7436 : ExecAssignExprContext(estate, &mtstate->ps);
762 : :
763 : 8322 : resultRelInfo->ri_projectNew =
764 : 8322 : ExecBuildUpdateProjection(subplan->targetlist,
765 : : false, /* subplan did the evaluation */
766 : : updateColnos,
767 : : relDesc,
768 : : mtstate->ps.ps_ExprContext,
769 : : resultRelInfo->ri_newTupleSlot,
770 : : &mtstate->ps);
771 : :
772 : 8322 : resultRelInfo->ri_projectNewInfoValid = true;
773 : 8322 : }
774 : :
775 : : /*
776 : : * ExecGetInsertNewTuple
777 : : * This prepares a "new" tuple ready to be inserted into given result
778 : : * relation, by removing any junk columns of the plan's output tuple
779 : : * and (if necessary) coercing the tuple to the right tuple format.
780 : : */
781 : : static TupleTableSlot *
2021 782 : 8032084 : ExecGetInsertNewTuple(ResultRelInfo *relinfo,
783 : : TupleTableSlot *planSlot)
784 : : {
785 : 8032084 : ProjectionInfo *newProj = relinfo->ri_projectNew;
786 : : ExprContext *econtext;
787 : :
788 : : /*
789 : : * If there's no projection to be done, just make sure the slot is of the
790 : : * right type for the target rel. If the planSlot is the right type we
791 : : * can use it as-is, else copy the data into ri_newTupleSlot.
792 : : */
793 [ + - ]: 8032084 : if (newProj == NULL)
794 : : {
795 [ + + ]: 8032084 : if (relinfo->ri_newTupleSlot->tts_ops != planSlot->tts_ops)
796 : : {
797 : 7514070 : ExecCopySlot(relinfo->ri_newTupleSlot, planSlot);
798 : 7514070 : return relinfo->ri_newTupleSlot;
799 : : }
800 : : else
801 : 518014 : return planSlot;
802 : : }
803 : :
804 : : /*
805 : : * Else project; since the projection output slot is ri_newTupleSlot, this
806 : : * will also fix any slot-type problem.
807 : : *
808 : : * Note: currently, this is dead code, because INSERT cases don't receive
809 : : * any junk columns so there's never a projection to be done.
810 : : */
2021 tgl@sss.pgh.pa.us 811 :UBC 0 : econtext = newProj->pi_exprContext;
812 : 0 : econtext->ecxt_outertuple = planSlot;
813 : 0 : return ExecProject(newProj);
814 : : }
815 : :
816 : : /*
817 : : * ExecGetUpdateNewTuple
818 : : * This prepares a "new" tuple by combining an UPDATE subplan's output
819 : : * tuple (which contains values of changed columns) with unchanged
820 : : * columns taken from the old tuple.
821 : : *
822 : : * The subplan tuple might also contain junk columns, which are ignored.
823 : : * Note that the projection also ensures we have a slot of the right type.
824 : : */
825 : : TupleTableSlot *
2021 tgl@sss.pgh.pa.us 826 :CBC 2222988 : ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
827 : : TupleTableSlot *planSlot,
828 : : TupleTableSlot *oldSlot)
829 : : {
1309 dean.a.rasheed@gmail 830 : 2222988 : ProjectionInfo *newProj = relinfo->ri_projectNew;
831 : : ExprContext *econtext;
832 : :
833 : : /* Use a few extra Asserts to protect against outside callers */
2015 tgl@sss.pgh.pa.us 834 [ - + ]: 2222988 : Assert(relinfo->ri_projectNewInfoValid);
2021 835 [ + - - + ]: 2222988 : Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
836 [ + - - + ]: 2222988 : Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
837 : :
838 : 2222988 : econtext = newProj->pi_exprContext;
839 : 2222988 : econtext->ecxt_outertuple = planSlot;
840 : 2222988 : econtext->ecxt_scantuple = oldSlot;
841 : 2222988 : return ExecProject(newProj);
842 : : }
843 : :
844 : : /* ----------------------------------------------------------------
845 : : * ExecInsert
846 : : *
847 : : * For INSERT, we have to insert the tuple into the target relation
848 : : * (or partition thereof) and insert appropriate tuples into the index
849 : : * relations.
850 : : *
851 : : * slot contains the new tuple value to be stored.
852 : : *
853 : : * Returns RETURNING result if any, otherwise NULL.
854 : : * *inserted_tuple is the tuple that's effectively inserted;
855 : : * *insert_destrel is the relation where it was inserted.
856 : : * These are only set on success.
857 : : *
858 : : * This may change the currently active tuple conversion map in
859 : : * mtstate->mt_transition_capture, so the callers must take care to
860 : : * save the previous value to avoid losing track of it.
861 : : * ----------------------------------------------------------------
862 : : */
863 : : static TupleTableSlot *
1670 alvherre@alvh.no-ip. 864 : 8033945 : ExecInsert(ModifyTableContext *context,
865 : : ResultRelInfo *resultRelInfo,
866 : : TupleTableSlot *slot,
867 : : bool canSetTag,
868 : : TupleTableSlot **inserted_tuple,
869 : : ResultRelInfo **insert_destrel)
870 : : {
871 : 8033945 : ModifyTableState *mtstate = context->mtstate;
872 : 8033945 : EState *estate = context->estate;
873 : : Relation resultRelationDesc;
6211 tgl@sss.pgh.pa.us 874 : 8033945 : List *recheckIndexes = NIL;
1670 alvherre@alvh.no-ip. 875 : 8033945 : TupleTableSlot *planSlot = context->planSlot;
3472 rhaas@postgresql.org 876 : 8033945 : TupleTableSlot *result = NULL;
877 : : TransitionCaptureState *ar_insert_trig_tcs;
3129 alvherre@alvh.no-ip. 878 : 8033945 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
879 : 8033945 : OnConflictAction onconflict = node->onConflictAction;
2189 heikki.linnakangas@i 880 : 8033945 : PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
881 : : MemoryContext oldContext;
882 : :
883 : : /*
884 : : * If the input result relation is a partitioned table, find the leaf
885 : : * partition to insert the tuple into.
886 : : */
887 [ + + ]: 8033945 : if (proute)
888 : : {
889 : : ResultRelInfo *partRelInfo;
890 : :
891 : 485841 : slot = ExecPrepareTupleRouting(mtstate, estate, proute,
892 : : resultRelInfo, slot,
893 : : &partRelInfo);
894 : 485697 : resultRelInfo = partRelInfo;
895 : : }
896 : :
897 : 8033801 : ExecMaterializeSlot(slot);
898 : :
6211 tgl@sss.pgh.pa.us 899 : 8033801 : resultRelationDesc = resultRelInfo->ri_RelationDesc;
900 : :
901 : : /*
902 : : * Open the table's indexes, if we have not done so already, so that we
903 : : * can add new index entries for the inserted tuple.
904 : : */
2015 905 [ + + ]: 8033801 : if (resultRelationDesc->rd_rel->relhasindex &&
906 [ + + ]: 2437070 : resultRelInfo->ri_IndexRelationDescs == NULL)
907 : 31629 : ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
908 : :
909 : : /*
910 : : * BEFORE ROW INSERT Triggers.
911 : : *
912 : : * Note: We fire BEFORE ROW TRIGGERS for every attempted insertion in an
913 : : * INSERT ... ON CONFLICT statement. We cannot check for constraint
914 : : * violations before firing these triggers, because they can change the
915 : : * values to insert. Also, they can run arbitrary user-defined code with
916 : : * side-effects that we can't cancel by just not inserting the tuple.
917 : : */
6211 918 [ + + ]: 8033801 : if (resultRelInfo->ri_TrigDesc &&
5846 919 [ + + ]: 452681 : resultRelInfo->ri_TrigDesc->trig_insert_before_row)
920 : : {
921 : : /* Flush any pending inserts, so rows are visible to the triggers */
1417 efujita@postgresql.o 922 [ + + ]: 1389 : if (estate->es_insert_pending_result_relations != NIL)
923 : 3 : ExecPendingInserts(estate);
924 : :
2785 andres@anarazel.de 925 [ + + ]: 1389 : if (!ExecBRInsertTriggers(estate, resultRelInfo, slot))
926 : 131 : return NULL; /* "do nothing" */
927 : : }
928 : :
929 : : /* INSTEAD OF ROW INSERT Triggers */
5846 tgl@sss.pgh.pa.us 930 [ + + ]: 8033608 : if (resultRelInfo->ri_TrigDesc &&
931 [ + + ]: 452488 : resultRelInfo->ri_TrigDesc->trig_insert_instead_row)
932 : : {
2785 andres@anarazel.de 933 [ + + ]: 111 : if (!ExecIRInsertTriggers(estate, resultRelInfo, slot))
934 : 4 : return NULL; /* "do nothing" */
935 : : }
4964 tgl@sss.pgh.pa.us 936 [ + + ]: 8033497 : else if (resultRelInfo->ri_FdwRoutine)
937 : : {
938 : : /*
939 : : * GENERATED expressions might reference the tableoid column, so
940 : : * (re-)initialize tts_tableOid before evaluating them.
941 : : */
1970 942 : 1011 : slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
943 : :
944 : : /*
945 : : * Compute stored generated columns
946 : : */
2753 peter@eisentraut.org 947 [ + + ]: 1011 : if (resultRelationDesc->rd_att->constr &&
948 [ + + ]: 179 : resultRelationDesc->rd_att->constr->has_generated_stored)
2189 heikki.linnakangas@i 949 : 4 : ExecComputeStoredGenerated(resultRelInfo, estate, slot,
950 : : CMD_INSERT);
951 : :
952 : : /*
953 : : * If the FDW supports batching, and batching is requested, accumulate
954 : : * rows and insert them in batches. Otherwise use the per-row inserts.
955 : : */
2091 tomas.vondra@postgre 956 [ + + ]: 1011 : if (resultRelInfo->ri_BatchSize > 1)
957 : : {
1417 efujita@postgresql.o 958 : 145 : bool flushed = false;
959 : :
960 : : /*
961 : : * When we've reached the desired batch size, perform the
962 : : * insertion.
963 : : */
2091 tomas.vondra@postgre 964 [ + + ]: 145 : if (resultRelInfo->ri_NumSlots == resultRelInfo->ri_BatchSize)
965 : : {
966 : 10 : ExecBatchInsert(mtstate, resultRelInfo,
967 : : resultRelInfo->ri_Slots,
968 : : resultRelInfo->ri_PlanSlots,
969 : : resultRelInfo->ri_NumSlots,
970 : : estate, canSetTag);
1417 efujita@postgresql.o 971 : 10 : flushed = true;
972 : : }
973 : :
2091 tomas.vondra@postgre 974 : 145 : oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
975 : :
976 [ + + ]: 145 : if (resultRelInfo->ri_Slots == NULL)
977 : : {
306 michael@paquier.xyz 978 : 15 : resultRelInfo->ri_Slots = palloc_array(TupleTableSlot *, resultRelInfo->ri_BatchSize);
979 : 15 : resultRelInfo->ri_PlanSlots = palloc_array(TupleTableSlot *, resultRelInfo->ri_BatchSize);
980 : : }
981 : :
982 : : /*
983 : : * Initialize the batch slots. We don't know how many slots will
984 : : * be needed, so we initialize them as the batch grows, and we
985 : : * keep them across batches. To mitigate an inefficiency in how
986 : : * resource owner handles objects with many references (as with
987 : : * many slots all referencing the same tuple descriptor) we copy
988 : : * the appropriate tuple descriptor for each slot.
989 : : */
1949 tomas.vondra@postgre 990 [ + + ]: 145 : if (resultRelInfo->ri_NumSlots >= resultRelInfo->ri_NumSlotsInitialized)
991 : : {
1932 andrew@dunslane.net 992 : 72 : TupleDesc tdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
993 : : TupleDesc plan_tdesc =
1242 tgl@sss.pgh.pa.us 994 : 72 : CreateTupleDescCopy(planSlot->tts_tupleDescriptor);
995 : :
1949 tomas.vondra@postgre 996 : 144 : resultRelInfo->ri_Slots[resultRelInfo->ri_NumSlots] =
997 : 72 : MakeSingleTupleTableSlot(tdesc, slot->tts_ops);
998 : :
999 : 144 : resultRelInfo->ri_PlanSlots[resultRelInfo->ri_NumSlots] =
1887 1000 : 72 : MakeSingleTupleTableSlot(plan_tdesc, planSlot->tts_ops);
1001 : :
1002 : : /* remember how many batch slots we initialized */
1949 1003 : 72 : resultRelInfo->ri_NumSlotsInitialized++;
1004 : : }
1005 : :
1944 1006 : 145 : ExecCopySlot(resultRelInfo->ri_Slots[resultRelInfo->ri_NumSlots],
1007 : : slot);
1008 : :
1009 : 145 : ExecCopySlot(resultRelInfo->ri_PlanSlots[resultRelInfo->ri_NumSlots],
1010 : : planSlot);
1011 : :
1012 : : /*
1013 : : * If these are the first tuples stored in the buffers, add the
1014 : : * target rel and the mtstate to the
1015 : : * es_insert_pending_result_relations and
1016 : : * es_insert_pending_modifytables lists respectively, except in
1017 : : * the case where flushing was done above, in which case they
1018 : : * would already have been added to the lists, so no need to do
1019 : : * this.
1020 : : */
1417 efujita@postgresql.o 1021 [ + + + + ]: 145 : if (resultRelInfo->ri_NumSlots == 0 && !flushed)
1022 : : {
1023 [ - + ]: 19 : Assert(!list_member_ptr(estate->es_insert_pending_result_relations,
1024 : : resultRelInfo));
1025 : 19 : estate->es_insert_pending_result_relations =
1026 : 19 : lappend(estate->es_insert_pending_result_relations,
1027 : : resultRelInfo);
1404 1028 : 19 : estate->es_insert_pending_modifytables =
1029 : 19 : lappend(estate->es_insert_pending_modifytables, mtstate);
1030 : : }
1417 1031 [ - + ]: 145 : Assert(list_member_ptr(estate->es_insert_pending_result_relations,
1032 : : resultRelInfo));
1033 : :
2091 tomas.vondra@postgre 1034 : 145 : resultRelInfo->ri_NumSlots++;
1035 : :
1036 : 145 : MemoryContextSwitchTo(oldContext);
1037 : :
1038 : 145 : return NULL;
1039 : : }
1040 : :
1041 : : /*
1042 : : * insert into foreign table: let the FDW do it
1043 : : */
4964 tgl@sss.pgh.pa.us 1044 : 866 : slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
1045 : : resultRelInfo,
1046 : : slot,
1047 : : planSlot);
1048 : :
1049 [ + + ]: 863 : if (slot == NULL) /* "do nothing" */
1050 : 2 : return NULL;
1051 : :
1052 : : /*
1053 : : * AFTER ROW Triggers or RETURNING expressions might reference the
1054 : : * tableoid column, so (re-)initialize tts_tableOid before evaluating
1055 : : * them. (This covers the case where the FDW replaced the slot.)
1056 : : */
2785 andres@anarazel.de 1057 : 861 : slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
1058 : : }
1059 : : else
1060 : : {
1061 : : WCOKind wco_kind;
1062 : :
1063 : : /*
1064 : : * Constraints and GENERATED expressions might reference the tableoid
1065 : : * column, so (re-)initialize tts_tableOid before evaluating them.
1066 : : */
1067 : 8032486 : slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
1068 : :
1069 : : /*
1070 : : * Compute stored generated columns
1071 : : */
2753 peter@eisentraut.org 1072 [ + + ]: 8032486 : if (resultRelationDesc->rd_att->constr &&
1073 [ + + ]: 2429106 : resultRelationDesc->rd_att->constr->has_generated_stored)
2189 heikki.linnakangas@i 1074 : 776 : ExecComputeStoredGenerated(resultRelInfo, estate, slot,
1075 : : CMD_INSERT);
1076 : :
1077 : : /*
1078 : : * Check any RLS WITH CHECK policies.
1079 : : *
1080 : : * Normally we should check INSERT policies. But if the insert is the
1081 : : * result of a partition key update that moved the tuple to a new
1082 : : * partition, we should instead check UPDATE policies, because we are
1083 : : * executing policies defined on the target table, and not those
1084 : : * defined on the child partitions.
1085 : : *
1086 : : * If we're running MERGE, we refer to the action that we're executing
1087 : : * to know if we're doing an INSERT or UPDATE to a partition table.
1088 : : */
1659 alvherre@alvh.no-ip. 1089 [ + + ]: 8032466 : if (mtstate->operation == CMD_UPDATE)
1090 : 518 : wco_kind = WCO_RLS_UPDATE_CHECK;
1091 [ + + ]: 8031948 : else if (mtstate->operation == CMD_MERGE)
939 dean.a.rasheed@gmail 1092 : 1187 : wco_kind = (mtstate->mt_merge_action->mas_action->commandType == CMD_UPDATE) ?
1659 alvherre@alvh.no-ip. 1093 [ + + ]: 1187 : WCO_RLS_UPDATE_CHECK : WCO_RLS_INSERT_CHECK;
1094 : : else
1095 : 8030761 : wco_kind = WCO_RLS_INSERT_CHECK;
1096 : :
1097 : : /*
1098 : : * ExecWithCheckOptions() will skip any WCOs which are not of the kind
1099 : : * we are looking for at this point.
1100 : : */
4189 sfrost@snowman.net 1101 [ + + ]: 8032466 : if (resultRelInfo->ri_WithCheckOptions != NIL)
3188 rhaas@postgresql.org 1102 : 474 : ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
1103 : :
1104 : : /*
1105 : : * Check the constraints of the tuple.
1106 : : */
3045 alvherre@alvh.no-ip. 1107 [ + + ]: 8032338 : if (resultRelationDesc->rd_att->constr)
1108 : 2429014 : ExecConstraints(resultRelInfo, slot, estate);
1109 : :
1110 : : /*
1111 : : * Also check the tuple against the partition constraint, if there is
1112 : : * one; except that if we got here via tuple-routing, we don't need to
1113 : : * if there's no BR trigger defined on the partition.
1114 : : */
2217 tgl@sss.pgh.pa.us 1115 [ + + ]: 8031845 : if (resultRelationDesc->rd_rel->relispartition &&
2072 heikki.linnakangas@i 1116 [ + + ]: 487370 : (resultRelInfo->ri_RootResultRelInfo == NULL ||
3045 alvherre@alvh.no-ip. 1117 [ + + ]: 485345 : (resultRelInfo->ri_TrigDesc &&
1118 [ + + ]: 1065 : resultRelInfo->ri_TrigDesc->trig_insert_before_row)))
1119 : 2155 : ExecPartitionCheck(resultRelInfo, slot, estate, true);
1120 : :
4175 andres@anarazel.de 1121 [ + + + - ]: 8031733 : if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
1122 : 2235 : {
1123 : : /* Perform a speculative insertion. */
1124 : : uint32 specToken;
1125 : : ItemPointerData conflictTid;
1126 : : ItemPointerData invalidItemPtr;
1127 : : bool specConflict;
1128 : : List *arbiterIndexes;
1129 : :
783 akapila@postgresql.o 1130 : 5386 : ItemPointerSetInvalid(&invalidItemPtr);
3122 alvherre@alvh.no-ip. 1131 : 5386 : arbiterIndexes = resultRelInfo->ri_onConflictArbiterIndexes;
1132 : :
1133 : : /*
1134 : : * Do a non-conclusive check for conflicts first.
1135 : : *
1136 : : * We're not holding any locks yet, so this doesn't guarantee that
1137 : : * the later insert won't conflict. But it avoids leaving behind
1138 : : * a lot of canceled speculative insertions, if you run a lot of
1139 : : * INSERT ON CONFLICT statements that do conflict.
1140 : : *
1141 : : * We loop back here if we find a conflict below, either during
1142 : : * the pre-check, or when we re-check after inserting the tuple
1143 : : * speculatively. Better allow interrupts in case some bug makes
1144 : : * this an infinite loop.
1145 : : */
4175 andres@anarazel.de 1146 : 13 : vlock:
1530 tgl@sss.pgh.pa.us 1147 [ - + ]: 5399 : CHECK_FOR_INTERRUPTS();
4175 andres@anarazel.de 1148 : 5399 : specConflict = false;
2189 heikki.linnakangas@i 1149 [ + + ]: 5399 : if (!ExecCheckIndexConstraints(resultRelInfo, slot, estate,
1150 : : &conflictTid, &invalidItemPtr,
1151 : : arbiterIndexes))
1152 : : {
1153 : : /* committed conflict tuple found */
4175 andres@anarazel.de 1154 [ + + ]: 3118 : if (onconflict == ONCONFLICT_UPDATE)
1155 : : {
1156 : : /*
1157 : : * In case of ON CONFLICT DO UPDATE, execute the UPDATE
1158 : : * part. Be prepared to retry if the UPDATE fails because
1159 : : * of another concurrent UPDATE/DELETE to the conflict
1160 : : * tuple.
1161 : : */
1162 : 2774 : TupleTableSlot *returning = NULL;
1163 : :
1670 alvherre@alvh.no-ip. 1164 [ + + ]: 2774 : if (ExecOnConflictUpdate(context, resultRelInfo,
1165 : : &conflictTid, slot, canSetTag,
1166 : : &returning))
1167 : : {
3107 1168 [ - + ]: 2719 : InstrCountTuples2(&mtstate->ps, 1);
4175 andres@anarazel.de 1169 : 2719 : return returning;
1170 : : }
1171 : : else
1172 : 3 : goto vlock;
1173 : : }
242 dean.a.rasheed@gmail 1174 [ + + ]: 344 : else if (onconflict == ONCONFLICT_SELECT)
1175 : : {
1176 : : /*
1177 : : * In case of ON CONFLICT DO SELECT, optionally lock the
1178 : : * conflicting tuple, fetch it and project RETURNING on
1179 : : * it. Be prepared to retry if locking fails because of a
1180 : : * concurrent UPDATE/DELETE to the conflict tuple.
1181 : : */
1182 : 202 : TupleTableSlot *returning = NULL;
1183 : :
1184 [ + - ]: 202 : if (ExecOnConflictSelect(context, resultRelInfo,
1185 : : &conflictTid, slot, canSetTag,
1186 : : &returning))
1187 : : {
1188 [ - + ]: 185 : InstrCountTuples2(&mtstate->ps, 1);
1189 : 185 : return returning;
1190 : : }
1191 : : else
242 dean.a.rasheed@gmail 1192 :UBC 0 : goto vlock;
1193 : : }
1194 : : else
1195 : : {
1196 : : /*
1197 : : * In case of ON CONFLICT DO NOTHING, do nothing. However,
1198 : : * verify that the tuple is visible to the executor's MVCC
1199 : : * snapshot at higher isolation levels.
1200 : : *
1201 : : * Using ExecGetReturningSlot() to store the tuple for the
1202 : : * recheck isn't that pretty, but we can't trivially use
1203 : : * the input slot, because it might not be of a compatible
1204 : : * type. As there's no conflicting usage of
1205 : : * ExecGetReturningSlot() in the DO NOTHING case...
1206 : : */
4175 andres@anarazel.de 1207 [ - + ]:CBC 142 : Assert(onconflict == ONCONFLICT_NOTHING);
2760 1208 : 142 : ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid,
1209 : : ExecGetReturningSlot(estate, resultRelInfo));
3107 alvherre@alvh.no-ip. 1210 [ - + ]: 132 : InstrCountTuples2(&mtstate->ps, 1);
4175 andres@anarazel.de 1211 : 132 : return NULL;
1212 : : }
1213 : : }
1214 : :
1215 : : /*
1216 : : * Before we start insertion proper, acquire our "speculative
1217 : : * insertion lock". Others can use that to wait for us to decide
1218 : : * if we're going to go ahead with the insertion, instead of
1219 : : * waiting for the whole transaction to complete.
1220 : : */
322 alvherre@kurilemu.de 1221 : 2277 : INJECTION_POINT("exec-insert-before-insert-speculative", NULL);
4175 andres@anarazel.de 1222 : 2277 : specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
1223 : :
1224 : : /* insert the tuple, with the speculative token */
2699 1225 : 2277 : table_tuple_insert_speculative(resultRelationDesc, slot,
1226 : : estate->es_output_cid,
1227 : : 0,
1228 : : NULL,
1229 : : specToken);
1230 : :
1231 : : /* insert index entries for tuple */
2189 heikki.linnakangas@i 1232 : 2277 : recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
1233 : : estate, EIIT_NO_DUPE_ERROR,
1234 : : slot, arbiterIndexes,
1235 : : &specConflict);
1236 : :
1237 : : /* adjust the tuple's state accordingly */
2699 andres@anarazel.de 1238 : 2245 : table_tuple_complete_speculative(resultRelationDesc, slot,
1239 : 2245 : specToken, !specConflict);
1240 : :
1241 : : /*
1242 : : * Wake up anyone waiting for our decision. They will re-check
1243 : : * the tuple, see that it's no longer speculative, and wait on our
1244 : : * XID as if this was a regularly inserted tuple all along. Or if
1245 : : * we killed the tuple, they will see it's dead, and proceed as if
1246 : : * the tuple never existed.
1247 : : */
4175 1248 : 2245 : SpeculativeInsertionLockRelease(GetCurrentTransactionId());
1249 : :
1250 : : /*
1251 : : * If there was a conflict, start from the beginning. We'll do
1252 : : * the pre-check again, which will now find the conflicting tuple
1253 : : * (unless it aborts before we get there).
1254 : : */
1255 [ + + ]: 2245 : if (specConflict)
1256 : : {
1257 : 10 : list_free(recheckIndexes);
1258 : 10 : goto vlock;
1259 : : }
1260 : :
1261 : : /* Since there was no insertion conflict, we're done */
1262 : : }
1263 : : else
1264 : : {
1265 : : /* insert the tuple normally */
914 akorotkov@postgresql 1266 : 8026347 : table_tuple_insert(resultRelationDesc, slot,
1267 : : estate->es_output_cid,
1268 : : 0, NULL);
1269 : :
1270 : : /* insert index entries for tuple */
1271 [ + + ]: 8026328 : if (resultRelInfo->ri_NumIndices > 0)
237 alvherre@kurilemu.de 1272 : 2431298 : recheckIndexes = ExecInsertIndexTuples(resultRelInfo, estate,
1273 : : 0, slot, NIL,
1274 : : NULL);
1275 : : }
1276 : : }
1277 : :
5708 tgl@sss.pgh.pa.us 1278 [ + + ]: 8029155 : if (canSetTag)
1279 : 8028335 : (estate->es_processed)++;
1280 : :
1281 : : /*
1282 : : * If this insert is the result of a partition key update that moved the
1283 : : * tuple to a new partition, put this row into the transition NEW TABLE,
1284 : : * if there is one. We need to do this separately for DELETE and INSERT
1285 : : * because they happen on different tables.
1286 : : */
3188 rhaas@postgresql.org 1287 : 8029155 : ar_insert_trig_tcs = mtstate->mt_transition_capture;
1288 [ + + + + ]: 8029155 : if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
1289 [ + + ]: 36 : && mtstate->mt_transition_capture->tcs_update_new_table)
1290 : : {
1667 alvherre@alvh.no-ip. 1291 : 32 : ExecARUpdateTriggers(estate, resultRelInfo,
1292 : : NULL, NULL,
1293 : : NULL,
1294 : : NULL,
1295 : : slot,
1296 : : NULL,
1297 : 32 : mtstate->mt_transition_capture,
1298 : : false);
1299 : :
1300 : : /*
1301 : : * We've already captured the NEW TABLE row, so make sure any AR
1302 : : * INSERT trigger fired below doesn't capture it again.
1303 : : */
3188 rhaas@postgresql.org 1304 : 32 : ar_insert_trig_tcs = NULL;
1305 : : }
1306 : :
1307 : : /* AFTER ROW INSERT Triggers */
2785 andres@anarazel.de 1308 : 8029155 : ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
1309 : : ar_insert_trig_tcs);
1310 : :
6098 tgl@sss.pgh.pa.us 1311 : 8029154 : list_free(recheckIndexes);
1312 : :
1313 : : /*
1314 : : * Check any WITH CHECK OPTION constraints from parent views. We are
1315 : : * required to do this after testing all constraints and uniqueness
1316 : : * violations per the SQL spec, so we do it after actually inserting the
1317 : : * record into the heap and all indexes.
1318 : : *
1319 : : * ExecWithCheckOptions will elog(ERROR) if a violation is found, so the
1320 : : * tuple will never be seen, if it violates the WITH CHECK OPTION.
1321 : : *
1322 : : * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
1323 : : * are looking for at this point.
1324 : : */
4834 sfrost@snowman.net 1325 [ + + ]: 8029154 : if (resultRelInfo->ri_WithCheckOptions != NIL)
4189 1326 : 294 : ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
1327 : :
1328 : : /* Process RETURNING if present */
27 peter@eisentraut.org 1329 [ + + ]: 8029058 : if (resultRelInfo->ri_projectReturning)
1330 : : {
634 dean.a.rasheed@gmail 1331 : 2749 : TupleTableSlot *oldSlot = NULL;
1332 : :
1333 : : /*
1334 : : * If this is part of a cross-partition UPDATE, and the RETURNING list
1335 : : * refers to any OLD columns, ExecDelete() will have saved the tuple
1336 : : * deleted from the original partition, which we must use here to
1337 : : * compute the OLD column values. Otherwise, all OLD column values
1338 : : * will be NULL.
1339 : : */
1340 [ + + ]: 2749 : if (context->cpDeletedSlot)
1341 : : {
1342 : : TupleConversionMap *tupconv_map;
1343 : :
1344 : : /*
1345 : : * Convert the OLD tuple to the new partition's format/slot, if
1346 : : * needed. Note that ExecDelete() already converted it to the
1347 : : * root's partition's format/slot.
1348 : : */
1349 : 30 : oldSlot = context->cpDeletedSlot;
1350 : 30 : tupconv_map = ExecGetRootToChildMap(resultRelInfo, estate);
1351 [ + + ]: 30 : if (tupconv_map != NULL)
1352 : : {
1353 : 10 : oldSlot = execute_attr_map_slot(tupconv_map->attrMap,
1354 : : oldSlot,
1355 : : ExecGetReturningSlot(estate,
1356 : : resultRelInfo));
1357 : :
1358 : 10 : oldSlot->tts_tableOid = context->cpDeletedSlot->tts_tableOid;
1359 : 10 : ItemPointerCopy(&context->cpDeletedSlot->tts_tid, &oldSlot->tts_tid);
1360 : : }
1361 : : }
1362 : :
242 1363 : 2749 : result = ExecProcessReturning(context, resultRelInfo, false,
1364 : : oldSlot, slot, planSlot);
1365 : :
1366 : : /*
1367 : : * For a cross-partition UPDATE, release the old tuple, first making
1368 : : * sure that the result slot has a local copy of any pass-by-reference
1369 : : * values.
1370 : : */
634 1371 [ + + ]: 2741 : if (context->cpDeletedSlot)
1372 : : {
1373 : 30 : ExecMaterializeSlot(result);
1374 : 30 : ExecClearTuple(oldSlot);
1375 [ + + ]: 30 : if (context->cpDeletedSlot != oldSlot)
1376 : 10 : ExecClearTuple(context->cpDeletedSlot);
1377 : 30 : context->cpDeletedSlot = NULL;
1378 : : }
1379 : : }
1380 : :
1667 alvherre@alvh.no-ip. 1381 [ + + ]: 8029050 : if (inserted_tuple)
1382 : 534 : *inserted_tuple = slot;
1383 [ + + ]: 8029050 : if (insert_destrel)
1384 : 534 : *insert_destrel = resultRelInfo;
1385 : :
3553 rhaas@postgresql.org 1386 : 8029050 : return result;
1387 : : }
1388 : :
1389 : : /* ----------------------------------------------------------------
1390 : : * ExecBatchInsert
1391 : : *
1392 : : * Insert multiple tuples in an efficient way.
1393 : : * Currently, this handles inserting into a foreign table without
1394 : : * RETURNING clause.
1395 : : * ----------------------------------------------------------------
1396 : : */
1397 : : static void
2091 tomas.vondra@postgre 1398 : 29 : ExecBatchInsert(ModifyTableState *mtstate,
1399 : : ResultRelInfo *resultRelInfo,
1400 : : TupleTableSlot **slots,
1401 : : TupleTableSlot **planSlots,
1402 : : int numSlots,
1403 : : EState *estate,
1404 : : bool canSetTag)
1405 : : {
1406 : : int i;
1407 : 29 : int numInserted = numSlots;
1408 : 29 : TupleTableSlot *slot = NULL;
1409 : : TupleTableSlot **rslots;
1410 : :
1411 : : /*
1412 : : * insert into foreign table: let the FDW do it
1413 : : */
1414 : 29 : rslots = resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert(estate,
1415 : : resultRelInfo,
1416 : : slots,
1417 : : planSlots,
1418 : : &numInserted);
1419 : :
1420 [ + + ]: 173 : for (i = 0; i < numInserted; i++)
1421 : : {
1422 : 145 : slot = rslots[i];
1423 : :
1424 : : /*
1425 : : * AFTER ROW Triggers might reference the tableoid column, so
1426 : : * (re-)initialize tts_tableOid before evaluating them.
1427 : : */
1428 : 145 : slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
1429 : :
1430 : : /* AFTER ROW INSERT Triggers */
1431 : 145 : ExecARInsertTriggers(estate, resultRelInfo, slot, NIL,
1432 : 145 : mtstate->mt_transition_capture);
1433 : :
1434 : : /*
1435 : : * Check any WITH CHECK OPTION constraints from parent views. See the
1436 : : * comment in ExecInsert.
1437 : : */
1438 [ - + ]: 144 : if (resultRelInfo->ri_WithCheckOptions != NIL)
2091 tomas.vondra@postgre 1439 :UBC 0 : ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
1440 : : }
1441 : :
2091 tomas.vondra@postgre 1442 [ + - + - ]:CBC 28 : if (canSetTag && numInserted > 0)
1443 : 28 : estate->es_processed += numInserted;
1444 : :
1445 : : /* Clean up all the slots, ready for the next batch */
1266 michael@paquier.xyz 1446 [ + + ]: 172 : for (i = 0; i < numSlots; i++)
1447 : : {
1448 : 144 : ExecClearTuple(slots[i]);
1449 : 144 : ExecClearTuple(planSlots[i]);
1450 : : }
1451 : 28 : resultRelInfo->ri_NumSlots = 0;
2091 tomas.vondra@postgre 1452 : 28 : }
1453 : :
1454 : : /*
1455 : : * ExecPendingInserts -- flushes all pending inserts to the foreign tables
1456 : : */
1457 : : static void
1417 efujita@postgresql.o 1458 : 18 : ExecPendingInserts(EState *estate)
1459 : : {
1460 : : ListCell *l1,
1461 : : *l2;
1462 : :
1404 1463 [ + - + + : 36 : forboth(l1, estate->es_insert_pending_result_relations,
+ - + + +
+ + - +
+ ]
1464 : : l2, estate->es_insert_pending_modifytables)
1465 : : {
1466 : 19 : ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l1);
1467 : 19 : ModifyTableState *mtstate = (ModifyTableState *) lfirst(l2);
1468 : :
1417 1469 [ - + ]: 19 : Assert(mtstate);
1470 : 19 : ExecBatchInsert(mtstate, resultRelInfo,
1471 : : resultRelInfo->ri_Slots,
1472 : : resultRelInfo->ri_PlanSlots,
1473 : : resultRelInfo->ri_NumSlots,
1474 : 19 : estate, mtstate->canSetTag);
1475 : : }
1476 : :
1477 : 17 : list_free(estate->es_insert_pending_result_relations);
1404 1478 : 17 : list_free(estate->es_insert_pending_modifytables);
1417 1479 : 17 : estate->es_insert_pending_result_relations = NIL;
1404 1480 : 17 : estate->es_insert_pending_modifytables = NIL;
1417 1481 : 17 : }
1482 : :
1483 : : /*
1484 : : * ExecDeletePrologue -- subroutine for ExecDelete
1485 : : *
1486 : : * Prepare executor state for DELETE. Actually, the only thing we have to do
1487 : : * here is execute BEFORE ROW triggers. We return false if one of them makes
1488 : : * the delete a no-op; otherwise, return true.
1489 : : */
1490 : : static bool
1670 alvherre@alvh.no-ip. 1491 : 1012245 : ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
1492 : : ItemPointer tupleid, HeapTuple oldtuple,
1493 : : TupleTableSlot **epqreturnslot, TM_Result *result)
1494 : : {
1309 dean.a.rasheed@gmail 1495 [ + + ]: 1012245 : if (result)
1496 : 1041 : *result = TM_Ok;
1497 : :
1498 : : /* BEFORE ROW DELETE triggers */
1670 alvherre@alvh.no-ip. 1499 [ + + ]: 1012245 : if (resultRelInfo->ri_TrigDesc &&
1500 [ + + ]: 4658 : resultRelInfo->ri_TrigDesc->trig_delete_before_row)
1501 : : {
1502 : : /* Flush any pending inserts, so rows are visible to the triggers */
1417 efujita@postgresql.o 1503 [ + + ]: 212 : if (context->estate->es_insert_pending_result_relations != NIL)
1504 : 1 : ExecPendingInserts(context->estate);
1505 : :
1670 alvherre@alvh.no-ip. 1506 : 202 : return ExecBRDeleteTriggers(context->estate, context->epqstate,
1507 : : resultRelInfo, tupleid, oldtuple,
1508 : : epqreturnslot, result, &context->tmfd,
451 dean.a.rasheed@gmail 1509 : 212 : context->mtstate->operation == CMD_MERGE);
1510 : : }
1511 : :
1670 alvherre@alvh.no-ip. 1512 : 1012033 : return true;
1513 : : }
1514 : :
1515 : : /*
1516 : : * ExecDeleteAct -- subroutine for ExecDelete
1517 : : *
1518 : : * Actually delete the tuple from a plain table.
1519 : : *
1520 : : * Caller is in charge of doing EvalPlanQual as necessary
1521 : : */
1522 : : static TM_Result
1523 : 1012137 : ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
1524 : : ItemPointer tupleid, bool changingPart)
1525 : : {
1526 : 1012137 : EState *estate = context->estate;
194 alvherre@kurilemu.de 1527 : 1012137 : uint32 options = 0;
1528 : :
1529 [ + + ]: 1012137 : if (changingPart)
1530 : 668 : options |= TABLE_DELETE_CHANGING_PARTITION;
1531 : :
1670 alvherre@alvh.no-ip. 1532 : 1012137 : return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
1533 : : estate->es_output_cid,
1534 : : options,
1535 : : estate->es_snapshot,
1536 : : estate->es_crosscheck_snapshot,
1537 : : true /* wait for commit */ ,
1538 : : &context->tmfd);
1539 : : }
1540 : :
1541 : : /*
1542 : : * ExecDeleteEpilogue -- subroutine for ExecDelete
1543 : : *
1544 : : * Closing steps of tuple deletion; this invokes AFTER FOR EACH ROW triggers,
1545 : : * including the UPDATE triggers if the deletion is being done as part of a
1546 : : * cross-partition tuple move.
1547 : : */
1548 : : static void
1549 : 1012105 : ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
1550 : : ItemPointer tupleid, HeapTuple oldtuple, bool changingPart)
1551 : : {
1552 : 1012105 : ModifyTableState *mtstate = context->mtstate;
1553 : 1012105 : EState *estate = context->estate;
1554 : : TransitionCaptureState *ar_delete_trig_tcs;
1555 : :
1556 : : /*
1557 : : * If this delete is the result of a partition key update that moved the
1558 : : * tuple to a new partition, put this row into the transition OLD TABLE,
1559 : : * if there is one. We need to do this separately for DELETE and INSERT
1560 : : * because they happen on different tables.
1561 : : */
1562 : 1012105 : ar_delete_trig_tcs = mtstate->mt_transition_capture;
1563 [ + + + + ]: 1012105 : if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture &&
1564 [ + + ]: 36 : mtstate->mt_transition_capture->tcs_update_old_table)
1565 : : {
1667 1566 : 32 : ExecARUpdateTriggers(estate, resultRelInfo,
1567 : : NULL, NULL,
1568 : : tupleid, oldtuple,
914 akorotkov@postgresql 1569 : 32 : NULL, NULL, mtstate->mt_transition_capture,
1570 : : false);
1571 : :
1572 : : /*
1573 : : * We've already captured the OLD TABLE row, so make sure any AR
1574 : : * DELETE trigger fired below doesn't capture it again.
1575 : : */
1670 alvherre@alvh.no-ip. 1576 : 32 : ar_delete_trig_tcs = NULL;
1577 : : }
1578 : :
1579 : : /* AFTER ROW DELETE Triggers */
914 akorotkov@postgresql 1580 : 1012105 : ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
1581 : : ar_delete_trig_tcs, changingPart);
1670 alvherre@alvh.no-ip. 1582 : 1012103 : }
1583 : :
1584 : : /* ----------------------------------------------------------------
1585 : : * ExecDelete
1586 : : *
1587 : : * DELETE is like UPDATE, except that we delete the tuple and no
1588 : : * index modifications are needed.
1589 : : *
1590 : : * When deleting from a table, tupleid identifies the tuple to delete and
1591 : : * oldtuple is NULL. When deleting through a view INSTEAD OF trigger,
1592 : : * oldtuple is passed to the triggers and identifies what to delete, and
1593 : : * tupleid is invalid. When deleting from a foreign table, tupleid is
1594 : : * invalid; the FDW has to figure out which row to delete using data from
1595 : : * the planSlot. oldtuple is passed to foreign table triggers; it is
1596 : : * NULL when the foreign table has no relevant triggers. We use
1597 : : * tupleDeleted to indicate whether the tuple is actually deleted,
1598 : : * callers can use it to decide whether to continue the operation. When
1599 : : * this DELETE is a part of an UPDATE of partition-key, then the slot
1600 : : * returned by EvalPlanQual() is passed back using output parameter
1601 : : * epqreturnslot.
1602 : : *
1603 : : * Returns RETURNING result if any, otherwise NULL.
1604 : : * ----------------------------------------------------------------
1605 : : */
1606 : : static TupleTableSlot *
1607 : 1011894 : ExecDelete(ModifyTableContext *context,
1608 : : ResultRelInfo *resultRelInfo,
1609 : : ItemPointer tupleid,
1610 : : HeapTuple oldtuple,
1611 : : bool processReturning,
1612 : : bool changingPart,
1613 : : bool canSetTag,
1614 : : TM_Result *tmresult,
1615 : : bool *tupleDeleted,
1616 : : TupleTableSlot **epqreturnslot)
1617 : : {
1618 : 1011894 : EState *estate = context->estate;
2189 heikki.linnakangas@i 1619 : 1011894 : Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
4964 tgl@sss.pgh.pa.us 1620 : 1011894 : TupleTableSlot *slot = NULL;
1621 : : TM_Result result;
1622 : : bool saveOld;
1623 : :
3188 rhaas@postgresql.org 1624 [ + + ]: 1011894 : if (tupleDeleted)
1625 : 690 : *tupleDeleted = false;
1626 : :
1627 : : /*
1628 : : * Prepare for the delete. This includes BEFORE ROW triggers, so we're
1629 : : * done if it says we are.
1630 : : */
1670 alvherre@alvh.no-ip. 1631 [ + + ]: 1011894 : if (!ExecDeletePrologue(context, resultRelInfo, tupleid, oldtuple,
1632 : : epqreturnslot, tmresult))
1633 : 33 : return NULL;
1634 : :
1635 : : /* INSTEAD OF ROW DELETE Triggers */
5846 tgl@sss.pgh.pa.us 1636 [ + + ]: 1011851 : if (resultRelInfo->ri_TrigDesc &&
1637 [ + + ]: 4570 : resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
6211 1638 : 31 : {
1639 : : bool dodelete;
1640 : :
5846 1641 [ - + ]: 35 : Assert(oldtuple != NULL);
4586 noah@leadboat.com 1642 : 35 : dodelete = ExecIRDeleteTriggers(estate, resultRelInfo, oldtuple);
1643 : :
5846 tgl@sss.pgh.pa.us 1644 [ + + ]: 35 : if (!dodelete) /* "do nothing" */
6211 1645 : 4 : return NULL;
1646 : : }
4964 1647 [ + + ]: 1011816 : else if (resultRelInfo->ri_FdwRoutine)
1648 : : {
1649 : : /*
1650 : : * delete from foreign table: let the FDW do it
1651 : : *
1652 : : * We offer the returning slot as a place to store RETURNING data,
1653 : : * although the FDW can return some other slot if it wants.
1654 : : */
2785 andres@anarazel.de 1655 : 23 : slot = ExecGetReturningSlot(estate, resultRelInfo);
4964 tgl@sss.pgh.pa.us 1656 : 23 : slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
1657 : : resultRelInfo,
1658 : : slot,
1659 : : context->planSlot);
1660 : :
1661 [ - + ]: 23 : if (slot == NULL) /* "do nothing" */
4964 tgl@sss.pgh.pa.us 1662 :UBC 0 : return NULL;
1663 : :
1664 : : /*
1665 : : * RETURNING expressions might reference the tableoid column, so
1666 : : * (re)initialize tts_tableOid before evaluating them.
1667 : : */
2919 andres@anarazel.de 1668 [ + + ]:CBC 23 : if (TTS_EMPTY(slot))
3903 rhaas@postgresql.org 1669 : 5 : ExecStoreAllNullTuple(slot);
1670 : :
2785 andres@anarazel.de 1671 : 23 : slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
1672 : : }
1673 : : else
1674 : : {
1675 : : /*
1676 : : * delete the tuple
1677 : : *
1678 : : * Note: if context->estate->es_crosscheck_snapshot isn't
1679 : : * InvalidSnapshot, we check that the row to be deleted is visible to
1680 : : * that snapshot, and throw a can't-serialize error if not. This is a
1681 : : * special-case behavior needed for referential integrity updates in
1682 : : * transaction-snapshot mode transactions.
1683 : : */
1463 john.naylor@postgres 1684 : 1011793 : ldelete:
914 akorotkov@postgresql 1685 : 1011797 : result = ExecDeleteAct(context, resultRelInfo, tupleid, changingPart);
1686 : :
1026 dean.a.rasheed@gmail 1687 [ + + ]: 1011779 : if (tmresult)
1688 : 668 : *tmresult = result;
1689 : :
5846 tgl@sss.pgh.pa.us 1690 [ + + + + : 1011779 : switch (result)
- ]
1691 : : {
2760 andres@anarazel.de 1692 : 20 : case TM_SelfModified:
1693 : :
1694 : : /*
1695 : : * The target tuple was already updated or deleted by the
1696 : : * current command, or by a later command in the current
1697 : : * transaction. The former case is possible in a join DELETE
1698 : : * where multiple tuples join to the same target tuple. This
1699 : : * is somewhat questionable, but Postgres has always allowed
1700 : : * it: we just ignore additional deletion attempts.
1701 : : *
1702 : : * The latter case arises if the tuple is modified by a
1703 : : * command in a BEFORE trigger, or perhaps by a command in a
1704 : : * volatile function used in the query. In such situations we
1705 : : * should not ignore the deletion, but it is equally unsafe to
1706 : : * proceed. We don't want to discard the original DELETE
1707 : : * while keeping the triggered actions based on its deletion;
1708 : : * and it would be no better to allow the original DELETE
1709 : : * while discarding updates that it triggered. The row update
1710 : : * carries some information that might be important according
1711 : : * to business rules; so throwing an error is the only safe
1712 : : * course.
1713 : : *
1714 : : * If a trigger actually intends this type of interaction, it
1715 : : * can re-execute the DELETE and then return NULL to cancel
1716 : : * the outer delete.
1717 : : */
1670 alvherre@alvh.no-ip. 1718 [ + + ]: 20 : if (context->tmfd.cmax != estate->es_output_cid)
5099 kgrittn@postgresql.o 1719 [ + - ]: 4 : ereport(ERROR,
1720 : : (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
1721 : : errmsg("tuple to be deleted was already modified by an operation triggered by the current command"),
1722 : : errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
1723 : :
1724 : : /* Else, already deleted by self; nothing to do */
5846 tgl@sss.pgh.pa.us 1725 : 16 : return NULL;
1726 : :
2760 andres@anarazel.de 1727 : 1011718 : case TM_Ok:
5846 tgl@sss.pgh.pa.us 1728 : 1011718 : break;
1729 : :
2760 andres@anarazel.de 1730 : 37 : case TM_Updated:
1731 : : {
1732 : : TupleTableSlot *inputslot;
1733 : : TupleTableSlot *epqslot;
1734 : :
1735 [ + + ]: 37 : if (IsolationUsesXactSnapshot())
1736 [ + - ]: 1 : ereport(ERROR,
1737 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
1738 : : errmsg("could not serialize access due to concurrent update")));
1739 : :
1740 : : /*
1741 : : * Already know that we're going to need to do EPQ, so
1742 : : * fetch tuple directly into the right slot.
1743 : : */
914 akorotkov@postgresql 1744 : 36 : EvalPlanQualBegin(context->epqstate);
1745 : 36 : inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
1746 : : resultRelInfo->ri_RangeTableIndex);
1747 : :
1748 : 36 : result = table_tuple_lock(resultRelationDesc, tupleid,
1749 : : estate->es_snapshot,
1750 : : inputslot, estate->es_output_cid,
1751 : : LockTupleExclusive, LockWaitBlock,
1752 : : TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
1753 : : &context->tmfd);
1754 : :
1755 [ + + + - ]: 32 : switch (result)
1756 : : {
1757 : 28 : case TM_Ok:
1758 [ - + ]: 28 : Assert(context->tmfd.traversed);
1759 : 28 : epqslot = EvalPlanQual(context->epqstate,
1760 : : resultRelationDesc,
1761 : : resultRelInfo->ri_RangeTableIndex,
1762 : : inputslot);
1763 [ + - + + ]: 28 : if (TupIsNull(epqslot))
1764 : : /* Tuple not passing quals anymore, exiting... */
1765 : 15 : return NULL;
1766 : :
1767 : : /*
1768 : : * If requested, skip delete and pass back the
1769 : : * updated row.
1770 : : */
1771 [ + + ]: 13 : if (epqreturnslot)
1772 : : {
1773 : 9 : *epqreturnslot = epqslot;
1774 : 9 : return NULL;
1775 : : }
1776 : : else
1777 : 4 : goto ldelete;
1778 : :
1779 : 2 : case TM_SelfModified:
1780 : :
1781 : : /*
1782 : : * This can be reached when following an update
1783 : : * chain from a tuple updated by another session,
1784 : : * reaching a tuple that was already updated in
1785 : : * this transaction. If previously updated by this
1786 : : * command, ignore the delete, otherwise error
1787 : : * out.
1788 : : *
1789 : : * See also TM_SelfModified response to
1790 : : * table_tuple_delete() above.
1791 : : */
1792 [ + + ]: 2 : if (context->tmfd.cmax != estate->es_output_cid)
1793 [ + - ]: 1 : ereport(ERROR,
1794 : : (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
1795 : : errmsg("tuple to be deleted was already modified by an operation triggered by the current command"),
1796 : : errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
1797 : 1 : return NULL;
1798 : :
1799 : 2 : case TM_Deleted:
1800 : : /* tuple already deleted; nothing to do */
1801 : 2 : return NULL;
1802 : :
914 akorotkov@postgresql 1803 :UBC 0 : default:
1804 : :
1805 : : /*
1806 : : * TM_Invisible should be impossible because we're
1807 : : * waiting for updated row versions, and would
1808 : : * already have errored out if the first version
1809 : : * is invisible.
1810 : : *
1811 : : * TM_Updated should be impossible, because we're
1812 : : * locking the latest version via
1813 : : * TUPLE_LOCK_FLAG_FIND_LAST_VERSION.
1814 : : */
1815 [ # # ]: 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
1816 : : result);
1817 : : return NULL;
1818 : : }
1819 : :
1820 : : Assert(false);
1821 : : break;
1822 : : }
1823 : :
2760 andres@anarazel.de 1824 :CBC 4 : case TM_Deleted:
1825 [ - + ]: 4 : if (IsolationUsesXactSnapshot())
2760 andres@anarazel.de 1826 [ # # ]:UBC 0 : ereport(ERROR,
1827 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
1828 : : errmsg("could not serialize access due to concurrent delete")));
1829 : : /* tuple already deleted; nothing to do */
5846 tgl@sss.pgh.pa.us 1830 :CBC 4 : return NULL;
1831 : :
5846 tgl@sss.pgh.pa.us 1832 :UBC 0 : default:
2699 andres@anarazel.de 1833 [ # # ]: 0 : elog(ERROR, "unrecognized table_tuple_delete status: %u",
1834 : : result);
1835 : : return NULL;
1836 : : }
1837 : :
1838 : : /*
1839 : : * Note: Normally one would think that we have to delete index tuples
1840 : : * associated with the heap tuple now...
1841 : : *
1842 : : * ... but in POSTGRES, we have no need to do this because VACUUM will
1843 : : * take care of it later. We can't delete index tuples immediately
1844 : : * anyway, since the tuple is still visible to other transactions.
1845 : : */
1846 : : }
1847 : :
5708 tgl@sss.pgh.pa.us 1848 [ + + ]:CBC 1011772 : if (canSetTag)
1849 : 1010986 : (estate->es_processed)++;
1850 : :
1851 : : /* Tell caller that the delete actually happened. */
3188 rhaas@postgresql.org 1852 [ + + ]: 1011772 : if (tupleDeleted)
1853 : 635 : *tupleDeleted = true;
1854 : :
914 akorotkov@postgresql 1855 : 1011772 : ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart);
1856 : :
1857 : : /*
1858 : : * Process RETURNING if present and if requested.
1859 : : *
1860 : : * If this is part of a cross-partition UPDATE, and the RETURNING list
1861 : : * refers to any OLD column values, save the old tuple here for later
1862 : : * processing of the RETURNING list by ExecInsert().
1863 : : */
634 dean.a.rasheed@gmail 1864 [ + + + + ]: 1011865 : saveOld = changingPart && resultRelInfo->ri_projectReturning &&
1865 [ + + ]: 95 : resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD;
1866 : :
1867 [ + + + + : 1011770 : if (resultRelInfo->ri_projectReturning && (processReturning || saveOld))
+ + ]
1868 : : {
1869 : : /*
1870 : : * We have to put the target tuple into a slot, which means first we
1871 : : * gotta fetch it. We can use the trigger tuple slot.
1872 : : */
1873 : : TupleTableSlot *rslot;
1874 : :
4964 tgl@sss.pgh.pa.us 1875 [ + + ]: 624 : if (resultRelInfo->ri_FdwRoutine)
1876 : : {
1877 : : /* FDW must have provided a slot containing the deleted row */
1878 [ + - - + ]: 7 : Assert(!TupIsNull(slot));
1879 : : }
1880 : : else
1881 : : {
2785 andres@anarazel.de 1882 : 617 : slot = ExecGetReturningSlot(estate, resultRelInfo);
4964 tgl@sss.pgh.pa.us 1883 [ + + ]: 617 : if (oldtuple != NULL)
1884 : : {
2733 andres@anarazel.de 1885 : 16 : ExecForceStoreHeapTuple(oldtuple, slot, false);
1886 : : }
1887 : : else
1888 : : {
914 akorotkov@postgresql 1889 [ - + ]: 601 : if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,
1890 : : SnapshotAny, slot))
914 akorotkov@postgresql 1891 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
1892 : : }
1893 : : }
1894 : :
1895 : : /*
1896 : : * If required, save the old tuple for later processing of the
1897 : : * RETURNING list by ExecInsert().
1898 : : */
634 dean.a.rasheed@gmail 1899 [ + + ]:CBC 624 : if (saveOld)
1900 : : {
1901 : : TupleConversionMap *tupconv_map;
1902 : :
1903 : : /*
1904 : : * Convert the tuple into the root partition's format/slot, if
1905 : : * needed. ExecInsert() will then convert it to the new
1906 : : * partition's format/slot, if necessary.
1907 : : */
1908 : 30 : tupconv_map = ExecGetChildToRootMap(resultRelInfo);
1909 [ + + ]: 30 : if (tupconv_map != NULL)
1910 : : {
1911 : 12 : ResultRelInfo *rootRelInfo = context->mtstate->rootResultRelInfo;
1912 : 12 : TupleTableSlot *oldSlot = slot;
1913 : :
1914 : 12 : slot = execute_attr_map_slot(tupconv_map->attrMap,
1915 : : slot,
1916 : : ExecGetReturningSlot(estate,
1917 : : rootRelInfo));
1918 : :
1919 : 12 : slot->tts_tableOid = oldSlot->tts_tableOid;
1920 : 12 : ItemPointerCopy(&oldSlot->tts_tid, &slot->tts_tid);
1921 : : }
1922 : :
1923 : 30 : context->cpDeletedSlot = slot;
1924 : :
1925 : 30 : return NULL;
1926 : : }
1927 : :
242 1928 : 594 : rslot = ExecProcessReturning(context, resultRelInfo, true,
1929 : : slot, NULL, context->planSlot);
1930 : :
1931 : : /*
1932 : : * Before releasing the target tuple again, make sure rslot has a
1933 : : * local copy of any pass-by-reference values.
1934 : : */
4964 tgl@sss.pgh.pa.us 1935 : 594 : ExecMaterializeSlot(rslot);
1936 : :
6211 1937 : 594 : ExecClearTuple(slot);
1938 : :
1939 : 594 : return rslot;
1940 : : }
1941 : :
1942 : 1011146 : return NULL;
1943 : : }
1944 : :
1945 : : /*
1946 : : * ExecCrossPartitionUpdate --- Move an updated tuple to another partition.
1947 : : *
1948 : : * This works by first deleting the old tuple from the current partition,
1949 : : * followed by inserting the new tuple into the root parent table, that is,
1950 : : * mtstate->rootResultRelInfo. It will be re-routed from there to the
1951 : : * correct partition.
1952 : : *
1953 : : * Returns true if the tuple has been successfully moved, or if it's found
1954 : : * that the tuple was concurrently deleted so there's nothing more to do
1955 : : * for the caller.
1956 : : *
1957 : : * False is returned if the tuple we're trying to move is found to have been
1958 : : * concurrently updated. In that case, the caller must check if the updated
1959 : : * tuple that's returned in *retry_slot still needs to be re-routed, and call
1960 : : * this function again or perform a regular update accordingly. For MERGE,
1961 : : * the updated tuple is not returned in *retry_slot; it has its own retry
1962 : : * logic.
1963 : : */
1964 : : static bool
1670 alvherre@alvh.no-ip. 1965 : 722 : ExecCrossPartitionUpdate(ModifyTableContext *context,
1966 : : ResultRelInfo *resultRelInfo,
1967 : : ItemPointer tupleid, HeapTuple oldtuple,
1968 : : TupleTableSlot *slot,
1969 : : bool canSetTag,
1970 : : UpdateContext *updateCxt,
1971 : : TM_Result *tmresult,
1972 : : TupleTableSlot **retry_slot,
1973 : : TupleTableSlot **inserted_tuple,
1974 : : ResultRelInfo **insert_destrel)
1975 : : {
1976 : 722 : ModifyTableState *mtstate = context->mtstate;
2188 heikki.linnakangas@i 1977 : 722 : EState *estate = mtstate->ps.state;
1978 : : TupleConversionMap *tupconv_map;
1979 : : bool tuple_deleted;
1980 : 722 : TupleTableSlot *epqslot = NULL;
1981 : :
634 dean.a.rasheed@gmail 1982 : 722 : context->cpDeletedSlot = NULL;
1670 alvherre@alvh.no-ip. 1983 : 722 : context->cpUpdateReturningSlot = NULL;
1309 dean.a.rasheed@gmail 1984 : 722 : *retry_slot = NULL;
1985 : :
1986 : : /*
1987 : : * Disallow an INSERT ON CONFLICT DO UPDATE that causes the original row
1988 : : * to migrate to a different partition. Maybe this can be implemented
1989 : : * some day, but it seems a fringe feature with little redeeming value.
1990 : : */
2188 heikki.linnakangas@i 1991 [ - + ]: 722 : if (((ModifyTable *) mtstate->ps.plan)->onConflictAction == ONCONFLICT_UPDATE)
2188 heikki.linnakangas@i 1992 [ # # ]:UBC 0 : ereport(ERROR,
1993 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1994 : : errmsg("invalid ON UPDATE specification"),
1995 : : errdetail("The result tuple would appear in a different partition than the original tuple.")));
1996 : :
1997 : : /*
1998 : : * When an UPDATE is run directly on a leaf partition, simply fail with a
1999 : : * partition constraint violation error.
2000 : : */
2015 tgl@sss.pgh.pa.us 2001 [ + + ]:CBC 722 : if (resultRelInfo == mtstate->rootResultRelInfo)
2188 heikki.linnakangas@i 2002 : 32 : ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
2003 : :
2004 : : /* Initialize tuple routing info if not already done. */
2015 tgl@sss.pgh.pa.us 2005 [ + + ]: 690 : if (mtstate->mt_partition_tuple_routing == NULL)
2006 : : {
2007 : 437 : Relation rootRel = mtstate->rootResultRelInfo->ri_RelationDesc;
2008 : : MemoryContext oldcxt;
2009 : :
2010 : : /* Things built here have to last for the query duration. */
2011 : 437 : oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
2012 : :
2013 : 437 : mtstate->mt_partition_tuple_routing =
2014 : 437 : ExecSetupPartitionTupleRouting(estate, rootRel);
2015 : :
2016 : : /*
2017 : : * Before a partition's tuple can be re-routed, it must first be
2018 : : * converted to the root's format, so we'll need a slot for storing
2019 : : * such tuples.
2020 : : */
2021 [ - + ]: 437 : Assert(mtstate->mt_root_tuple_slot == NULL);
2022 : 437 : mtstate->mt_root_tuple_slot = table_slot_create(rootRel, NULL);
2023 : :
2024 : 437 : MemoryContextSwitchTo(oldcxt);
2025 : : }
2026 : :
2027 : : /*
2028 : : * Row movement, part 1. Delete the tuple, but skip RETURNING processing.
2029 : : * We want to return rows from INSERT.
2030 : : */
1670 alvherre@alvh.no-ip. 2031 : 690 : ExecDelete(context, resultRelInfo,
2032 : : tupleid, oldtuple,
2033 : : false, /* processReturning */
2034 : : true, /* changingPart */
2035 : : false, /* canSetTag */
2036 : : tmresult, &tuple_deleted, &epqslot);
2037 : :
2038 : : /*
2039 : : * For some reason if DELETE didn't happen (e.g. trigger prevented it, or
2040 : : * it was already deleted by self, or it was concurrently deleted by
2041 : : * another transaction), then we should skip the insert as well;
2042 : : * otherwise, an UPDATE could cause an increase in the total number of
2043 : : * rows across all partitions, which is clearly wrong.
2044 : : *
2045 : : * For a normal UPDATE, the case where the tuple has been the subject of a
2046 : : * concurrent UPDATE or DELETE would be handled by the EvalPlanQual
2047 : : * machinery, but for an UPDATE that we've translated into a DELETE from
2048 : : * this partition and an INSERT into some other partition, that's not
2049 : : * available, because CTID chains can't span relation boundaries. We
2050 : : * mimic the semantics to a limited extent by skipping the INSERT if the
2051 : : * DELETE fails to find a tuple. This ensures that two concurrent
2052 : : * attempts to UPDATE the same tuple at the same time can't turn one tuple
2053 : : * into two, and that an UPDATE of a just-deleted tuple can't resurrect
2054 : : * it.
2055 : : */
2188 heikki.linnakangas@i 2056 [ + + ]: 687 : if (!tuple_deleted)
2057 : : {
2058 : : /*
2059 : : * epqslot will be typically NULL. But when ExecDelete() finds that
2060 : : * another transaction has concurrently updated the same row, it
2061 : : * re-fetches the row, skips the delete, and epqslot is set to the
2062 : : * re-fetched tuple slot. In that case, we need to do all the checks
2063 : : * again. For MERGE, we leave everything to the caller (it must do
2064 : : * additional rechecking, and might end up executing a different
2065 : : * action entirely).
2066 : : */
939 dean.a.rasheed@gmail 2067 [ + + ]: 52 : if (mtstate->operation == CMD_MERGE)
1026 2068 : 26 : return *tmresult == TM_Ok;
1309 2069 [ + + - + ]: 26 : else if (TupIsNull(epqslot))
2188 heikki.linnakangas@i 2070 : 23 : return true;
2071 : : else
2072 : : {
2073 : : /* Fetch the most recent version of old tuple. */
2074 : : TupleTableSlot *oldSlot;
2075 : :
2076 : : /* ... but first, make sure ri_oldTupleSlot is initialized. */
914 akorotkov@postgresql 2077 [ - + ]: 3 : if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
914 akorotkov@postgresql 2078 :UBC 0 : ExecInitUpdateProjection(mtstate, resultRelInfo);
914 akorotkov@postgresql 2079 :CBC 3 : oldSlot = resultRelInfo->ri_oldTupleSlot;
2080 [ - + ]: 3 : if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
2081 : : tupleid,
2082 : : SnapshotAny,
2083 : : oldSlot))
914 akorotkov@postgresql 2084 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch tuple being updated");
2085 : : /* and project the new tuple to retry the UPDATE with */
1309 dean.a.rasheed@gmail 2086 :CBC 3 : *retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
2087 : : oldSlot);
2188 heikki.linnakangas@i 2088 : 3 : return false;
2089 : : }
2090 : : }
2091 : :
2092 : : /*
2093 : : * resultRelInfo is one of the per-relation resultRelInfos. So we should
2094 : : * convert the tuple into root's tuple descriptor if needed, since
2095 : : * ExecInsert() starts the search from root.
2096 : : */
2015 tgl@sss.pgh.pa.us 2097 : 635 : tupconv_map = ExecGetChildToRootMap(resultRelInfo);
2188 heikki.linnakangas@i 2098 [ + + ]: 635 : if (tupconv_map != NULL)
2099 : 209 : slot = execute_attr_map_slot(tupconv_map->attrMap,
2100 : : slot,
2101 : : mtstate->mt_root_tuple_slot);
2102 : :
2103 : : /* Tuple routing starts from the root table. */
1670 alvherre@alvh.no-ip. 2104 : 552 : context->cpUpdateReturningSlot =
1667 2105 : 635 : ExecInsert(context, mtstate->rootResultRelInfo, slot, canSetTag,
2106 : : inserted_tuple, insert_destrel);
2107 : :
2108 : : /*
2109 : : * Reset the transition state that may possibly have been written by
2110 : : * INSERT.
2111 : : */
2188 heikki.linnakangas@i 2112 [ + + ]: 552 : if (mtstate->mt_transition_capture)
2113 : 36 : mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
2114 : :
2115 : : /* We're done moving. */
2116 : 552 : return true;
2117 : : }
2118 : :
2119 : : /*
2120 : : * ExecUpdatePrologue -- subroutine for ExecUpdate
2121 : : *
2122 : : * Prepare executor state for UPDATE. This includes running BEFORE ROW
2123 : : * triggers. We return false if one of them makes the update a no-op;
2124 : : * otherwise, return true.
2125 : : */
2126 : : static bool
1670 alvherre@alvh.no-ip. 2127 : 2227071 : ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
2128 : : ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
2129 : : TM_Result *result)
2130 : : {
2131 : 2227071 : Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
2132 : :
1309 dean.a.rasheed@gmail 2133 [ + + ]: 2227071 : if (result)
2134 : 1422 : *result = TM_Ok;
2135 : :
1670 alvherre@alvh.no-ip. 2136 : 2227071 : ExecMaterializeSlot(slot);
2137 : :
2138 : : /*
2139 : : * Open the table's indexes, if we have not done so already, so that we
2140 : : * can add new index entries for the updated tuple.
2141 : : */
2142 [ + + ]: 2227071 : if (resultRelationDesc->rd_rel->relhasindex &&
2143 [ + + ]: 155620 : resultRelInfo->ri_IndexRelationDescs == NULL)
2144 : 5486 : ExecOpenIndices(resultRelInfo, false);
2145 : :
2146 : : /* BEFORE ROW UPDATE triggers */
2147 [ + + ]: 2227071 : if (resultRelInfo->ri_TrigDesc &&
2148 [ + + ]: 4003 : resultRelInfo->ri_TrigDesc->trig_update_before_row)
2149 : : {
2150 : : /* Flush any pending inserts, so rows are visible to the triggers */
1417 efujita@postgresql.o 2151 [ + + ]: 1567 : if (context->estate->es_insert_pending_result_relations != NIL)
2152 : 1 : ExecPendingInserts(context->estate);
2153 : :
1670 alvherre@alvh.no-ip. 2154 : 1555 : return ExecBRUpdateTriggers(context->estate, context->epqstate,
2155 : : resultRelInfo, tupleid, oldtuple, slot,
2156 : : result, &context->tmfd,
451 dean.a.rasheed@gmail 2157 : 1567 : context->mtstate->operation == CMD_MERGE);
2158 : : }
2159 : :
1670 alvherre@alvh.no-ip. 2160 : 2225504 : return true;
2161 : : }
2162 : :
2163 : : /*
2164 : : * ExecUpdatePrepareSlot -- subroutine for ExecUpdateAct
2165 : : *
2166 : : * Apply the final modifications to the tuple slot before the update.
2167 : : * (This is split out because we also need it in the foreign-table code path.)
2168 : : */
2169 : : static void
2170 : 2226878 : ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
2171 : : TupleTableSlot *slot,
2172 : : EState *estate)
2173 : : {
2174 : 2226878 : Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
2175 : :
2176 : : /*
2177 : : * Constraints and GENERATED expressions might reference the tableoid
2178 : : * column, so (re-)initialize tts_tableOid before evaluating them.
2179 : : */
2180 : 2226878 : slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
2181 : :
2182 : : /*
2183 : : * Compute stored generated columns
2184 : : */
2185 [ + + ]: 2226878 : if (resultRelationDesc->rd_att->constr &&
2186 [ + + ]: 132640 : resultRelationDesc->rd_att->constr->has_generated_stored)
2187 : 174 : ExecComputeStoredGenerated(resultRelInfo, estate, slot,
2188 : : CMD_UPDATE);
2189 : 2226878 : }
2190 : :
2191 : : /*
2192 : : * ExecUpdateAct -- subroutine for ExecUpdate
2193 : : *
2194 : : * Actually update the tuple, when operating on a plain table. If the
2195 : : * table is a partition, and the command was called referencing an ancestor
2196 : : * partitioned table, this routine migrates the resulting tuple to another
2197 : : * partition.
2198 : : *
2199 : : * The caller is in charge of keeping indexes current as necessary. The
2200 : : * caller is also in charge of doing EvalPlanQual if the tuple is found to
2201 : : * be concurrently updated. However, in case of a cross-partition update,
2202 : : * this routine does it.
2203 : : */
2204 : : static TM_Result
2205 : 2226778 : ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
2206 : : ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
2207 : : bool canSetTag, UpdateContext *updateCxt)
2208 : : {
2209 : 2226778 : EState *estate = context->estate;
2210 : 2226778 : Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
2211 : : bool partition_constraint_failed;
2212 : : TM_Result result;
2213 : :
2214 : 2226778 : updateCxt->crossPartUpdate = false;
2215 : :
2216 : : /*
2217 : : * If we move the tuple to a new partition, we loop back here to recompute
2218 : : * GENERATED values (which are allowed to be different across partitions)
2219 : : * and recheck any RLS policies and constraints. We do not fire any
2220 : : * BEFORE triggers of the new partition, however.
2221 : : */
1463 john.naylor@postgres 2222 : 2226781 : lreplace:
2223 : : /* Fill in GENERATEd columns */
1316 tgl@sss.pgh.pa.us 2224 : 2226781 : ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
2225 : :
2226 : : /* ensure slot is independent, consider e.g. EPQ */
1670 alvherre@alvh.no-ip. 2227 : 2226781 : ExecMaterializeSlot(slot);
2228 : :
2229 : : /*
2230 : : * If partition constraint fails, this row might get moved to another
2231 : : * partition, in which case we should check the RLS CHECK policy just
2232 : : * before inserting into the new partition, rather than doing it here.
2233 : : * This is because a trigger on that partition might again change the row.
2234 : : * So skip the WCO checks if the partition constraint fails.
2235 : : */
2236 : 2226781 : partition_constraint_failed =
2237 [ + + ]: 2228562 : resultRelationDesc->rd_rel->relispartition &&
2238 [ + + ]: 1781 : !ExecPartitionCheck(resultRelInfo, slot, estate, false);
2239 : :
2240 : : /* Check any RLS UPDATE WITH CHECK policies */
2241 [ + + ]: 2226781 : if (!partition_constraint_failed &&
2242 [ + + ]: 2226059 : resultRelInfo->ri_WithCheckOptions != NIL)
2243 : : {
2244 : : /*
2245 : : * ExecWithCheckOptions() will skip any WCOs which are not of the kind
2246 : : * we are looking for at this point.
2247 : : */
2248 : 356 : ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
2249 : : resultRelInfo, slot, estate);
2250 : : }
2251 : :
2252 : : /*
2253 : : * If a partition check failed, try to move the row into the right
2254 : : * partition.
2255 : : */
2256 [ + + ]: 2226745 : if (partition_constraint_failed)
2257 : : {
2258 : : TupleTableSlot *inserted_tuple,
2259 : : *retry_slot;
1667 2260 : 722 : ResultRelInfo *insert_destrel = NULL;
2261 : :
2262 : : /*
2263 : : * ExecCrossPartitionUpdate will first DELETE the row from the
2264 : : * partition it's currently in and then insert it back into the root
2265 : : * table, which will re-route it to the correct partition. However,
2266 : : * if the tuple has been concurrently updated, a retry is needed.
2267 : : */
1670 2268 [ + + ]: 722 : if (ExecCrossPartitionUpdate(context, resultRelInfo,
2269 : : tupleid, oldtuple, slot,
2270 : : canSetTag, updateCxt,
2271 : : &result,
2272 : : &retry_slot,
2273 : : &inserted_tuple,
2274 : : &insert_destrel))
2275 : : {
2276 : : /* success! */
2277 : 591 : updateCxt->crossPartUpdate = true;
2278 : :
2279 : : /*
2280 : : * If the partitioned table being updated is referenced in foreign
2281 : : * keys, queue up trigger events to check that none of them were
2282 : : * violated. No special treatment is needed in
2283 : : * non-cross-partition update situations, because the leaf
2284 : : * partition's AR update triggers will take care of that. During
2285 : : * cross-partition updates implemented as delete on the source
2286 : : * partition followed by insert on the destination partition,
2287 : : * AR-UPDATE triggers of the root table (that is, the table
2288 : : * mentioned in the query) must be fired.
2289 : : *
2290 : : * NULL insert_destrel means that the move failed to occur, that
2291 : : * is, the update failed, so no need to anything in that case.
2292 : : */
1667 2293 [ + + ]: 591 : if (insert_destrel &&
2294 [ + + ]: 534 : resultRelInfo->ri_TrigDesc &&
2295 [ + + ]: 242 : resultRelInfo->ri_TrigDesc->trig_update_after_row)
2296 : 202 : ExecCrossPartitionUpdateForeignKey(context,
2297 : : resultRelInfo,
2298 : : insert_destrel,
2299 : : tupleid, slot,
2300 : : inserted_tuple);
2301 : :
1670 2302 : 597 : return TM_Ok;
2303 : : }
2304 : :
2305 : : /*
2306 : : * No luck, a retry is needed. If running MERGE, we do not do so
2307 : : * here; instead let it handle that on its own rules.
2308 : : */
939 dean.a.rasheed@gmail 2309 [ + + ]: 13 : if (context->mtstate->operation == CMD_MERGE)
1026 2310 : 10 : return result;
2311 : :
2312 : : /*
2313 : : * ExecCrossPartitionUpdate installed an updated version of the new
2314 : : * tuple in the retry slot; start over.
2315 : : */
1309 2316 : 3 : slot = retry_slot;
1670 alvherre@alvh.no-ip. 2317 : 3 : goto lreplace;
2318 : : }
2319 : :
2320 : : /*
2321 : : * Check the constraints of the tuple. We've already checked the
2322 : : * partition constraint above; however, we must still ensure the tuple
2323 : : * passes all other constraints, so we will call ExecConstraints() and
2324 : : * have it validate all remaining checks.
2325 : : */
2326 [ + + ]: 2226023 : if (resultRelationDesc->rd_att->constr)
2327 : 132252 : ExecConstraints(resultRelInfo, slot, estate);
2328 : :
2329 : : /*
2330 : : * replace the heap tuple
2331 : : *
2332 : : * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
2333 : : * the row to be updated is visible to that snapshot, and throw a
2334 : : * can't-serialize error if not. This is a special-case behavior needed
2335 : : * for referential integrity updates in transaction-snapshot mode
2336 : : * transactions.
2337 : : */
2338 : 2225975 : result = table_tuple_update(resultRelationDesc, tupleid, slot,
2339 : : estate->es_output_cid,
2340 : : 0,
2341 : : estate->es_snapshot,
2342 : : estate->es_crosscheck_snapshot,
2343 : : true /* wait for commit */ ,
2344 : : &context->tmfd, &updateCxt->lockmode,
2345 : : &updateCxt->updateIndexes);
2346 : :
2347 : 2225962 : return result;
2348 : : }
2349 : :
2350 : : /*
2351 : : * ExecUpdateEpilogue -- subroutine for ExecUpdate
2352 : : *
2353 : : * Closing steps of updating a tuple. Must be called if ExecUpdateAct
2354 : : * returns indicating that the tuple was updated.
2355 : : */
2356 : : static void
2357 : 2225992 : ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
2358 : : ResultRelInfo *resultRelInfo, ItemPointer tupleid,
2359 : : HeapTuple oldtuple, TupleTableSlot *slot)
2360 : : {
2361 : 2225992 : ModifyTableState *mtstate = context->mtstate;
1309 dean.a.rasheed@gmail 2362 : 2225992 : List *recheckIndexes = NIL;
2363 : :
2364 : : /* insert index entries for tuple if necessary */
1302 tomas.vondra@postgre 2365 [ + + + + ]: 2225992 : if (resultRelInfo->ri_NumIndices > 0 && (updateCxt->updateIndexes != TU_None))
2366 : : {
196 nathan@postgresql.or 2367 : 125814 : uint32 flags = EIIT_IS_UPDATE;
2368 : :
237 alvherre@kurilemu.de 2369 [ + + ]: 125814 : if (updateCxt->updateIndexes == TU_Summarizing)
2370 : 2188 : flags |= EIIT_ONLY_SUMMARIZING;
2371 : 125814 : recheckIndexes = ExecInsertIndexTuples(resultRelInfo, context->estate,
2372 : : flags, slot, NIL,
2373 : : NULL);
2374 : : }
2375 : :
2376 : : /* AFTER ROW UPDATE Triggers */
1670 alvherre@alvh.no-ip. 2377 : 2225932 : ExecARUpdateTriggers(context->estate, resultRelInfo,
2378 : : NULL, NULL,
2379 : : tupleid, oldtuple, slot,
2380 : : recheckIndexes,
2381 [ + + ]: 2225932 : mtstate->operation == CMD_INSERT ?
2382 : : mtstate->mt_oc_transition_capture :
2383 : : mtstate->mt_transition_capture,
2384 : : false);
2385 : :
1309 dean.a.rasheed@gmail 2386 : 2225930 : list_free(recheckIndexes);
2387 : :
2388 : : /*
2389 : : * Check any WITH CHECK OPTION constraints from parent views. We are
2390 : : * required to do this after testing all constraints and uniqueness
2391 : : * violations per the SQL spec, so we do it after actually updating the
2392 : : * record in the heap and all indexes.
2393 : : *
2394 : : * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
2395 : : * are looking for at this point.
2396 : : */
1670 alvherre@alvh.no-ip. 2397 [ + + ]: 2225930 : if (resultRelInfo->ri_WithCheckOptions != NIL)
2398 : 337 : ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo,
2399 : : slot, context->estate);
2400 : 2225876 : }
2401 : :
2402 : : /*
2403 : : * Queues up an update event using the target root partitioned table's
2404 : : * trigger to check that a cross-partition update hasn't broken any foreign
2405 : : * keys pointing into it.
2406 : : */
2407 : : static void
1667 2408 : 202 : ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
2409 : : ResultRelInfo *sourcePartInfo,
2410 : : ResultRelInfo *destPartInfo,
2411 : : ItemPointer tupleid,
2412 : : TupleTableSlot *oldslot,
2413 : : TupleTableSlot *newslot)
2414 : : {
2415 : : ListCell *lc;
2416 : : ResultRelInfo *rootRelInfo;
2417 : : List *ancestorRels;
2418 : :
2419 : 202 : rootRelInfo = sourcePartInfo->ri_RootResultRelInfo;
2420 : 202 : ancestorRels = ExecGetAncestorResultRels(context->estate, sourcePartInfo);
2421 : :
2422 : : /*
2423 : : * For any foreign keys that point directly into a non-root ancestors of
2424 : : * the source partition, we can in theory fire an update event to enforce
2425 : : * those constraints using their triggers, if we could tell that both the
2426 : : * source and the destination partitions are under the same ancestor. But
2427 : : * for now, we simply report an error that those cannot be enforced.
2428 : : */
2429 [ + - + + : 440 : foreach(lc, ancestorRels)
+ + ]
2430 : : {
2431 : 242 : ResultRelInfo *rInfo = lfirst(lc);
2432 : 242 : TriggerDesc *trigdesc = rInfo->ri_TrigDesc;
2433 : 242 : bool has_noncloned_fkey = false;
2434 : :
2435 : : /* Root ancestor's triggers will be processed. */
2436 [ + + ]: 242 : if (rInfo == rootRelInfo)
2437 : 198 : continue;
2438 : :
2439 [ + - + - ]: 44 : if (trigdesc && trigdesc->trig_update_after_row)
2440 : : {
2441 [ + + ]: 152 : for (int i = 0; i < trigdesc->numtriggers; i++)
2442 : : {
2443 : 112 : Trigger *trig = &trigdesc->triggers[i];
2444 : :
2445 [ + + + - ]: 116 : if (!trig->tgisclone &&
2446 : 4 : RI_FKey_trigger_type(trig->tgfoid) == RI_TRIGGER_PK)
2447 : : {
2448 : 4 : has_noncloned_fkey = true;
2449 : 4 : break;
2450 : : }
2451 : : }
2452 : : }
2453 : :
2454 [ + + ]: 44 : if (has_noncloned_fkey)
2455 [ + - ]: 4 : ereport(ERROR,
2456 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2457 : : errmsg("cannot move tuple across partitions when a non-root ancestor of the source partition is directly referenced in a foreign key"),
2458 : : errdetail("A foreign key points to ancestor \"%s\" but not the root ancestor \"%s\".",
2459 : : RelationGetRelationName(rInfo->ri_RelationDesc),
2460 : : RelationGetRelationName(rootRelInfo->ri_RelationDesc)),
2461 : : errhint("Consider defining the foreign key on table \"%s\".",
2462 : : RelationGetRelationName(rootRelInfo->ri_RelationDesc))));
2463 : : }
2464 : :
2465 : : /* Perform the root table's triggers. */
2466 : 198 : ExecARUpdateTriggers(context->estate,
2467 : : rootRelInfo, sourcePartInfo, destPartInfo,
2468 : : tupleid, NULL, newslot, NIL, NULL, true);
2469 : 198 : }
2470 : :
2471 : : /* ----------------------------------------------------------------
2472 : : * ExecUpdate
2473 : : *
2474 : : * note: we can't run UPDATE queries with transactions
2475 : : * off because UPDATEs are actually INSERTs and our
2476 : : * scan will mistakenly loop forever, updating the tuple
2477 : : * it just inserted.. This should be fixed but until it
2478 : : * is, we don't want to get stuck in an infinite loop
2479 : : * which corrupts your database..
2480 : : *
2481 : : * When updating a table, tupleid identifies the tuple to update and
2482 : : * oldtuple is NULL. When updating through a view INSTEAD OF trigger,
2483 : : * oldtuple is passed to the triggers and identifies what to update, and
2484 : : * tupleid is invalid. When updating a foreign table, tupleid is
2485 : : * invalid; the FDW has to figure out which row to update using data from
2486 : : * the planSlot. oldtuple is passed to foreign table triggers; it is
2487 : : * NULL when the foreign table has no relevant triggers.
2488 : : *
2489 : : * oldSlot contains the old tuple value.
2490 : : * slot contains the new tuple value to be stored.
2491 : : * planSlot is the output of the ModifyTable's subplan; we use it
2492 : : * to access values from other input tables (for RETURNING),
2493 : : * row-ID junk columns, etc.
2494 : : *
2495 : : * Returns RETURNING result if any, otherwise NULL. On exit, if tupleid
2496 : : * had identified the tuple to update, it will identify the tuple
2497 : : * actually updated after EvalPlanQual.
2498 : : * ----------------------------------------------------------------
2499 : : */
2500 : : static TupleTableSlot *
1670 2501 : 2225649 : ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
2502 : : ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *oldSlot,
2503 : : TupleTableSlot *slot, bool canSetTag)
2504 : : {
2505 : 2225649 : EState *estate = context->estate;
2189 heikki.linnakangas@i 2506 : 2225649 : Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
1670 alvherre@alvh.no-ip. 2507 : 2225649 : UpdateContext updateCxt = {0};
2508 : : TM_Result result;
2509 : :
2510 : : /*
2511 : : * abort the operation if not running transactions
2512 : : */
6211 tgl@sss.pgh.pa.us 2513 [ - + ]: 2225649 : if (IsBootstrapProcessingMode())
6211 tgl@sss.pgh.pa.us 2514 [ # # ]:UBC 0 : elog(ERROR, "cannot UPDATE during bootstrap");
2515 : :
2516 : : /*
2517 : : * Prepare for the update. This includes BEFORE ROW triggers, so we're
2518 : : * done if it says we are.
2519 : : */
96 dean.a.rasheed@gmail 2520 :CBC 2225649 : context->tmfd.traversed = false;
1309 2521 [ + + ]: 2225649 : if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot, NULL))
1670 alvherre@alvh.no-ip. 2522 : 85 : return NULL;
2523 : :
2524 : : /*
2525 : : * If the target tuple was concurrently updated, the trigger code will
2526 : : * have done EPQ and updated tupleid, following the update chain. In this
2527 : : * case, we must fetch the most recent version of old tuple for the
2528 : : * benefit of RETURNING. Technically, we could get away with not doing
2529 : : * this, if there is no RETURNING clause, or it doesn't refer to OLD, but
2530 : : * it seems preferable to always ensure that the contents of oldSlot are
2531 : : * correct.
2532 : : */
96 dean.a.rasheed@gmail 2533 [ + + ]: 2225552 : if (context->tmfd.traversed)
2534 : : {
2535 [ - + ]: 3 : if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
2536 : : tupleid,
2537 : : SnapshotAny,
2538 : : oldSlot))
96 dean.a.rasheed@gmail 2539 [ # # ]:UBC 0 : elog(ERROR, "failed to re-fetch tuple updated during trigger execution");
2540 : : }
2541 : :
2542 : : /* INSTEAD OF ROW UPDATE Triggers */
5846 tgl@sss.pgh.pa.us 2543 [ + + ]:CBC 2225552 : if (resultRelInfo->ri_TrigDesc &&
2544 [ + + ]: 3662 : resultRelInfo->ri_TrigDesc->trig_update_instead_row)
2545 : : {
2785 andres@anarazel.de 2546 [ + + ]: 83 : if (!ExecIRUpdateTriggers(estate, resultRelInfo,
2547 : : oldtuple, slot))
2700 tgl@sss.pgh.pa.us 2548 : 12 : return NULL; /* "do nothing" */
2549 : : }
4964 2550 [ + + ]: 2225469 : else if (resultRelInfo->ri_FdwRoutine)
2551 : : {
2552 : : /* Fill in GENERATEd columns */
1670 alvherre@alvh.no-ip. 2553 : 97 : ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
2554 : :
2555 : : /*
2556 : : * update in foreign table: let the FDW do it
2557 : : */
4964 tgl@sss.pgh.pa.us 2558 : 97 : slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
2559 : : resultRelInfo,
2560 : : slot,
2561 : : context->planSlot);
2562 : :
2563 [ + + ]: 97 : if (slot == NULL) /* "do nothing" */
2564 : 1 : return NULL;
2565 : :
2566 : : /*
2567 : : * AFTER ROW Triggers or RETURNING expressions might reference the
2568 : : * tableoid column, so (re-)initialize tts_tableOid before evaluating
2569 : : * them. (This covers the case where the FDW replaced the slot.)
2570 : : */
2785 andres@anarazel.de 2571 : 96 : slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
2572 : : }
2573 : : else
2574 : : {
2575 : : ItemPointerData lockedtid;
2576 : :
2577 : : /*
2578 : : * If we generate a new candidate tuple after EvalPlanQual testing, we
2579 : : * must loop back here to try again. (We don't need to redo triggers,
2580 : : * however. If there are any BEFORE triggers then trigger.c will have
2581 : : * done table_tuple_lock to lock the correct tuple, so there's no need
2582 : : * to do them again.)
2583 : : */
1670 alvherre@alvh.no-ip. 2584 : 2225372 : redo_act:
748 noah@leadboat.com 2585 : 2225422 : lockedtid = *tupleid;
1670 alvherre@alvh.no-ip. 2586 : 2225422 : result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
2587 : : canSetTag, &updateCxt);
2588 : :
2589 : : /*
2590 : : * If ExecUpdateAct reports that a cross-partition update was done,
2591 : : * then the RETURNING tuple (if any) has been projected and there's
2592 : : * nothing else for us to do.
2593 : : */
2594 [ + + ]: 2225218 : if (updateCxt.crossPartUpdate)
2595 : 577 : return context->cpUpdateReturningSlot;
2596 : :
5846 tgl@sss.pgh.pa.us 2597 [ + + + + : 2224720 : switch (result)
- ]
2598 : : {
2760 andres@anarazel.de 2599 : 56 : case TM_SelfModified:
2600 : :
2601 : : /*
2602 : : * The target tuple was already updated or deleted by the
2603 : : * current command, or by a later command in the current
2604 : : * transaction. The former case is possible in a join UPDATE
2605 : : * where multiple tuples join to the same target tuple. This
2606 : : * is pretty questionable, but Postgres has always allowed it:
2607 : : * we just execute the first update action and ignore
2608 : : * additional update attempts.
2609 : : *
2610 : : * The latter case arises if the tuple is modified by a
2611 : : * command in a BEFORE trigger, or perhaps by a command in a
2612 : : * volatile function used in the query. In such situations we
2613 : : * should not ignore the update, but it is equally unsafe to
2614 : : * proceed. We don't want to discard the original UPDATE
2615 : : * while keeping the triggered actions based on it; and we
2616 : : * have no principled way to merge this update with the
2617 : : * previous ones. So throwing an error is the only safe
2618 : : * course.
2619 : : *
2620 : : * If a trigger actually intends this type of interaction, it
2621 : : * can re-execute the UPDATE (assuming it can figure out how)
2622 : : * and then return NULL to cancel the outer update.
2623 : : */
1670 alvherre@alvh.no-ip. 2624 [ + + ]: 56 : if (context->tmfd.cmax != estate->es_output_cid)
5099 kgrittn@postgresql.o 2625 [ + - ]: 4 : ereport(ERROR,
2626 : : (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
2627 : : errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
2628 : : errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
2629 : :
2630 : : /* Else, already updated by self; nothing to do */
5846 tgl@sss.pgh.pa.us 2631 : 52 : return NULL;
2632 : :
2760 andres@anarazel.de 2633 : 2224582 : case TM_Ok:
5846 tgl@sss.pgh.pa.us 2634 : 2224582 : break;
2635 : :
2760 andres@anarazel.de 2636 : 78 : case TM_Updated:
2637 : : {
2638 : : TupleTableSlot *inputslot;
2639 : : TupleTableSlot *epqslot;
2640 : :
2641 [ + + ]: 78 : if (IsolationUsesXactSnapshot())
2642 [ + - ]: 2 : ereport(ERROR,
2643 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
2644 : : errmsg("could not serialize access due to concurrent update")));
2645 : :
2646 : : /*
2647 : : * Already know that we're going to need to do EPQ, so
2648 : : * fetch tuple directly into the right slot.
2649 : : */
914 akorotkov@postgresql 2650 : 76 : inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
2651 : : resultRelInfo->ri_RangeTableIndex);
2652 : :
2653 : 76 : result = table_tuple_lock(resultRelationDesc, tupleid,
2654 : : estate->es_snapshot,
2655 : : inputslot, estate->es_output_cid,
2656 : : updateCxt.lockmode, LockWaitBlock,
2657 : : TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
2658 : : &context->tmfd);
2659 : :
2660 [ + + + - ]: 74 : switch (result)
2661 : : {
2662 : 69 : case TM_Ok:
2663 [ - + ]: 69 : Assert(context->tmfd.traversed);
2664 : :
2665 : 69 : epqslot = EvalPlanQual(context->epqstate,
2666 : : resultRelationDesc,
2667 : : resultRelInfo->ri_RangeTableIndex,
2668 : : inputslot);
2669 [ + + + + ]: 69 : if (TupIsNull(epqslot))
2670 : : /* Tuple not passing quals anymore, exiting... */
2671 : 19 : return NULL;
2672 : :
2673 : : /* Make sure ri_oldTupleSlot is initialized. */
2674 [ - + ]: 50 : if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
914 akorotkov@postgresql 2675 :UBC 0 : ExecInitUpdateProjection(context->mtstate,
2676 : : resultRelInfo);
2677 : :
748 noah@leadboat.com 2678 [ + + ]:CBC 50 : if (resultRelInfo->ri_needLockTagTuple)
2679 : : {
2680 : 1 : UnlockTuple(resultRelationDesc,
2681 : : &lockedtid, InplaceUpdateTupleLock);
2682 : 1 : LockTuple(resultRelationDesc,
2683 : : tupleid, InplaceUpdateTupleLock);
2684 : : }
2685 : :
2686 : : /* Fetch the most recent version of old tuple. */
914 akorotkov@postgresql 2687 : 50 : oldSlot = resultRelInfo->ri_oldTupleSlot;
2688 [ - + ]: 50 : if (!table_tuple_fetch_row_version(resultRelationDesc,
2689 : : tupleid,
2690 : : SnapshotAny,
2691 : : oldSlot))
914 akorotkov@postgresql 2692 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch tuple being updated");
914 akorotkov@postgresql 2693 :CBC 50 : slot = ExecGetUpdateNewTuple(resultRelInfo,
2694 : : epqslot, oldSlot);
2695 : 50 : goto redo_act;
2696 : :
2697 : 1 : case TM_Deleted:
2698 : : /* tuple already deleted; nothing to do */
2699 : 1 : return NULL;
2700 : :
2701 : 4 : case TM_SelfModified:
2702 : :
2703 : : /*
2704 : : * This can be reached when following an update
2705 : : * chain from a tuple updated by another session,
2706 : : * reaching a tuple that was already updated in
2707 : : * this transaction. If previously modified by
2708 : : * this command, ignore the redundant update,
2709 : : * otherwise error out.
2710 : : *
2711 : : * See also TM_SelfModified response to
2712 : : * table_tuple_update() above.
2713 : : */
2714 [ + + ]: 4 : if (context->tmfd.cmax != estate->es_output_cid)
2715 [ + - ]: 1 : ereport(ERROR,
2716 : : (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
2717 : : errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
2718 : : errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
2719 : 3 : return NULL;
2720 : :
914 akorotkov@postgresql 2721 :UBC 0 : default:
2722 : : /* see table_tuple_lock call in ExecDelete() */
2723 [ # # ]: 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
2724 : : result);
2725 : : return NULL;
2726 : : }
2727 : : }
2728 : :
2729 : : break;
2730 : :
2760 andres@anarazel.de 2731 :CBC 4 : case TM_Deleted:
2732 [ - + ]: 4 : if (IsolationUsesXactSnapshot())
2760 andres@anarazel.de 2733 [ # # ]:UBC 0 : ereport(ERROR,
2734 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
2735 : : errmsg("could not serialize access due to concurrent delete")));
2736 : : /* tuple already deleted; nothing to do */
5846 tgl@sss.pgh.pa.us 2737 :CBC 4 : return NULL;
2738 : :
5846 tgl@sss.pgh.pa.us 2739 :UBC 0 : default:
2699 andres@anarazel.de 2740 [ # # ]: 0 : elog(ERROR, "unrecognized table_tuple_update status: %u",
2741 : : result);
2742 : : return NULL;
2743 : : }
2744 : : }
2745 : :
5708 tgl@sss.pgh.pa.us 2746 [ + + ]:CBC 2224741 : if (canSetTag)
2747 : 2224342 : (estate->es_processed)++;
2748 : :
1670 alvherre@alvh.no-ip. 2749 : 2224741 : ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
2750 : : slot);
2751 : :
2752 : : /* Process RETURNING if present */
6211 tgl@sss.pgh.pa.us 2753 [ + + ]: 2224633 : if (resultRelInfo->ri_projectReturning)
242 dean.a.rasheed@gmail 2754 : 1513 : return ExecProcessReturning(context, resultRelInfo, false,
2755 : : oldSlot, slot, context->planSlot);
2756 : :
6211 tgl@sss.pgh.pa.us 2757 : 2223120 : return NULL;
2758 : : }
2759 : :
2760 : : /*
2761 : : * ExecOnConflictLockRow --- lock the row for ON CONFLICT DO SELECT/UPDATE
2762 : : *
2763 : : * Try to lock tuple for update as part of speculative insertion for ON
2764 : : * CONFLICT DO UPDATE or ON CONFLICT DO SELECT FOR UPDATE/SHARE.
2765 : : *
2766 : : * Returns true if the row is successfully locked, or false if the caller must
2767 : : * retry the INSERT from scratch.
2768 : : */
2769 : : static bool
242 dean.a.rasheed@gmail 2770 : 2846 : ExecOnConflictLockRow(ModifyTableContext *context,
2771 : : TupleTableSlot *existing,
2772 : : ItemPointer conflictTid,
2773 : : Relation relation,
2774 : : LockTupleMode lockmode,
2775 : : bool isUpdate)
2776 : : {
2777 : : TM_FailureData tmfd;
2778 : : TM_Result test;
2779 : : Datum xminDatum;
2780 : : TransactionId xmin;
2781 : : bool isnull;
2782 : :
2783 : : /*
2784 : : * Lock tuple with lockmode. Don't follow updates when tuple cannot be
2785 : : * locked without doing so. A row locking conflict here means our
2786 : : * previous conclusion that the tuple is conclusively committed is not
2787 : : * true anymore.
2788 : : */
2699 andres@anarazel.de 2789 : 2846 : test = table_tuple_lock(relation, conflictTid,
1670 alvherre@alvh.no-ip. 2790 : 2846 : context->estate->es_snapshot,
2791 : 2846 : existing, context->estate->es_output_cid,
2792 : : lockmode, LockWaitBlock, 0,
2793 : : &tmfd);
4175 andres@anarazel.de 2794 [ + + - + : 2846 : switch (test)
+ - ]
2795 : : {
2760 2796 : 2815 : case TM_Ok:
2797 : : /* success! */
4175 2798 : 2815 : break;
2799 : :
2760 2800 : 28 : case TM_Invisible:
2801 : :
2802 : : /*
2803 : : * This can occur when a just inserted tuple is updated again in
2804 : : * the same command. E.g. because multiple rows with the same
2805 : : * conflicting key values are inserted.
2806 : : *
2807 : : * This is somewhat similar to the ExecUpdate() TM_SelfModified
2808 : : * case. We do not want to proceed because it would lead to the
2809 : : * same row being updated a second time in some unspecified order,
2810 : : * and in contrast to plain UPDATEs there's no historical behavior
2811 : : * to break.
2812 : : *
2813 : : * It is the user's responsibility to prevent this situation from
2814 : : * occurring. These problems are why the SQL standard similarly
2815 : : * specifies that for SQL MERGE, an exception must be raised in
2816 : : * the event of an attempt to update the same row twice.
2817 : : */
2818 : 28 : xminDatum = slot_getsysattr(existing,
2819 : : MinTransactionIdAttributeNumber,
2820 : : &isnull);
2821 [ - + ]: 28 : Assert(!isnull);
2822 : 28 : xmin = DatumGetTransactionId(xminDatum);
2823 : :
2824 [ + - ]: 28 : if (TransactionIdIsCurrentTransactionId(xmin))
4175 2825 [ + - + + ]: 28 : ereport(ERROR,
2826 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
2827 : : /* translator: %s is a SQL command name */
2828 : : errmsg("%s command cannot affect row a second time",
2829 : : isUpdate ? "ON CONFLICT DO UPDATE" : "ON CONFLICT DO SELECT"),
2830 : : errhint("Ensure that no rows proposed for insertion within the same command have duplicate constrained values.")));
2831 : :
2832 : : /* This shouldn't happen */
4175 andres@anarazel.de 2833 [ # # ]:UBC 0 : elog(ERROR, "attempted to lock invisible tuple");
2834 : : break;
2835 : :
2760 2836 : 0 : case TM_SelfModified:
2837 : :
2838 : : /*
2839 : : * This state should never be reached. As a dirty snapshot is used
2840 : : * to find conflicting tuples, speculative insertion wouldn't have
2841 : : * seen this row to conflict with.
2842 : : */
4175 2843 [ # # ]: 0 : elog(ERROR, "unexpected self-updated tuple");
2844 : : break;
2845 : :
2760 andres@anarazel.de 2846 :CBC 2 : case TM_Updated:
4175 2847 [ - + ]: 2 : if (IsolationUsesXactSnapshot())
4175 andres@anarazel.de 2848 [ # # ]:UBC 0 : ereport(ERROR,
2849 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
2850 : : errmsg("could not serialize access due to concurrent update")));
2851 : :
2852 : : /*
2853 : : * Tell caller to try again from the very start.
2854 : : *
2855 : : * It does not make sense to use the usual EvalPlanQual() style
2856 : : * loop here, as the new version of the row might not conflict
2857 : : * anymore, or the conflicting tuple has actually been deleted.
2858 : : */
2760 andres@anarazel.de 2859 :CBC 2 : ExecClearTuple(existing);
2860 : 2 : return false;
2861 : :
2862 : 1 : case TM_Deleted:
2863 [ - + ]: 1 : if (IsolationUsesXactSnapshot())
2760 andres@anarazel.de 2864 [ # # ]:UBC 0 : ereport(ERROR,
2865 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
2866 : : errmsg("could not serialize access due to concurrent delete")));
2867 : :
2868 : : /* see TM_Updated case */
2760 andres@anarazel.de 2869 :CBC 1 : ExecClearTuple(existing);
4175 2870 : 1 : return false;
2871 : :
4175 andres@anarazel.de 2872 :UBC 0 : default:
2699 2873 [ # # ]: 0 : elog(ERROR, "unrecognized table_tuple_lock status: %u", test);
2874 : : }
2875 : :
2876 : : /* Success, the tuple is locked. */
242 dean.a.rasheed@gmail 2877 :CBC 2815 : return true;
2878 : : }
2879 : :
2880 : : /*
2881 : : * ExecOnConflictUpdate --- execute UPDATE of INSERT ON CONFLICT DO UPDATE
2882 : : *
2883 : : * Try to lock tuple for update as part of speculative insertion. If
2884 : : * a qual originating from ON CONFLICT DO UPDATE is satisfied, update
2885 : : * (but still lock row, even though it may not satisfy estate's
2886 : : * snapshot).
2887 : : *
2888 : : * Returns true if we're done (with or without an update), or false if
2889 : : * the caller must retry the INSERT from scratch.
2890 : : */
2891 : : static bool
2892 : 2774 : ExecOnConflictUpdate(ModifyTableContext *context,
2893 : : ResultRelInfo *resultRelInfo,
2894 : : ItemPointer conflictTid,
2895 : : TupleTableSlot *excludedSlot,
2896 : : bool canSetTag,
2897 : : TupleTableSlot **returning)
2898 : : {
2899 : 2774 : ModifyTableState *mtstate = context->mtstate;
2900 : 2774 : ExprContext *econtext = mtstate->ps.ps_ExprContext;
2901 : 2774 : Relation relation = resultRelInfo->ri_RelationDesc;
2902 : 2774 : ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
2903 : 2774 : TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
2904 : : LockTupleMode lockmode;
2905 : :
2906 : : /*
2907 : : * Parse analysis should have blocked ON CONFLICT for all system
2908 : : * relations, which includes these. There's no fundamental obstacle to
2909 : : * supporting this; we'd just need to handle LOCKTAG_TUPLE like the other
2910 : : * ExecUpdate() caller.
2911 : : */
2912 [ - + ]: 2774 : Assert(!resultRelInfo->ri_needLockTagTuple);
2913 : :
2914 : : /* Determine lock mode to use */
2915 : 2774 : lockmode = ExecUpdateLockMode(context->estate, resultRelInfo);
2916 : :
2917 : : /* Lock tuple for update */
2918 [ + + ]: 2774 : if (!ExecOnConflictLockRow(context, existing, conflictTid,
2919 : : resultRelInfo->ri_RelationDesc, lockmode, true))
2920 : 3 : return false;
2921 : :
2922 : : /*
2923 : : * Verify that the tuple is visible to our MVCC snapshot if the current
2924 : : * isolation level mandates that.
2925 : : *
2926 : : * It's not sufficient to rely on the check within ExecUpdate() as e.g.
2927 : : * CONFLICT ... WHERE clause may prevent us from reaching that.
2928 : : *
2929 : : * This means we only ever continue when a new command in the current
2930 : : * transaction could see the row, even though in READ COMMITTED mode the
2931 : : * tuple will not be visible according to the current statement's
2932 : : * snapshot. This is in line with the way UPDATE deals with newer tuple
2933 : : * versions.
2934 : : */
1670 alvherre@alvh.no-ip. 2935 : 2755 : ExecCheckTupleVisible(context->estate, relation, existing);
2936 : :
2937 : : /*
2938 : : * Make tuple and any needed join variables available to ExecQual and
2939 : : * ExecProject. The EXCLUDED tuple is installed in ecxt_innertuple, while
2940 : : * the target's existing tuple is installed in the scantuple. EXCLUDED
2941 : : * has been made to reference INNER_VAR in setrefs.c, but there is no
2942 : : * other redirection.
2943 : : */
2777 andres@anarazel.de 2944 : 2755 : econtext->ecxt_scantuple = existing;
4175 2945 : 2755 : econtext->ecxt_innertuple = excludedSlot;
2946 : 2755 : econtext->ecxt_outertuple = NULL;
2947 : :
3499 2948 [ + + ]: 2755 : if (!ExecQual(onConflictSetWhere, econtext))
2949 : : {
2777 2950 : 22 : ExecClearTuple(existing); /* see return below */
4175 2951 [ - + ]: 22 : InstrCountFiltered1(&mtstate->ps, 1);
2952 : 22 : return true; /* done with the tuple */
2953 : : }
2954 : :
2955 [ + + ]: 2733 : if (resultRelInfo->ri_WithCheckOptions != NIL)
2956 : : {
2957 : : /*
2958 : : * Check target's existing tuple against UPDATE-applicable USING
2959 : : * security barrier quals (if any), enforced here as RLS checks/WCOs.
2960 : : *
2961 : : * The rewriter creates UPDATE RLS checks/WCOs for UPDATE security
2962 : : * quals, and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK.
2963 : : * Since SELECT permission on the target table is always required for
2964 : : * INSERT ... ON CONFLICT DO UPDATE, the rewriter also adds SELECT RLS
2965 : : * checks/WCOs for SELECT security quals, using WCOs of the same kind,
2966 : : * and this check enforces them too.
2967 : : *
2968 : : * The rewriter will also have associated UPDATE-applicable straight
2969 : : * RLS checks/WCOs for the benefit of the ExecUpdate() call that
2970 : : * follows. INSERTs and UPDATEs naturally have mutually exclusive WCO
2971 : : * kinds, so there is no danger of spurious over-enforcement in the
2972 : : * INSERT or UPDATE path.
2973 : : */
2974 : 48 : ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
2975 : : existing,
2976 : : mtstate->ps.state);
2977 : : }
2978 : :
2979 : : /* Project the new tuple version */
3122 alvherre@alvh.no-ip. 2980 : 2717 : ExecProject(resultRelInfo->ri_onConflict->oc_ProjInfo);
2981 : :
2982 : : /*
2983 : : * Note that it is possible that the target tuple has been modified in
2984 : : * this session, after the above table_tuple_lock. We choose to not error
2985 : : * out in that case, in line with ExecUpdate's treatment of similar cases.
2986 : : * This can happen if an UPDATE is triggered from within ExecQual(),
2987 : : * ExecWithCheckOptions() or ExecProject() above, e.g. by selecting from a
2988 : : * wCTE in the ON CONFLICT's SET.
2989 : : */
2990 : :
2991 : : /* Execute UPDATE with projection */
1670 2992 : 5414 : *returning = ExecUpdate(context, resultRelInfo,
2993 : : conflictTid, NULL, existing,
2777 andres@anarazel.de 2994 : 2717 : resultRelInfo->ri_onConflict->oc_ProjSlot,
2995 : : canSetTag);
2996 : :
2997 : : /*
2998 : : * Clear out existing tuple, as there might not be another conflict among
2999 : : * the next input rows. Don't want to hold resources till the end of the
3000 : : * query. First though, make sure that the returning slot, if any, has a
3001 : : * local copy of any OLD pass-by-reference values, if it refers to any OLD
3002 : : * columns.
3003 : : */
634 dean.a.rasheed@gmail 3004 [ + + ]: 2697 : if (*returning != NULL &&
3005 [ + + ]: 174 : resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD)
3006 : 12 : ExecMaterializeSlot(*returning);
3007 : :
2777 andres@anarazel.de 3008 : 2697 : ExecClearTuple(existing);
3009 : :
4175 3010 : 2697 : return true;
3011 : : }
3012 : :
3013 : : /*
3014 : : * ExecOnConflictSelect --- execute SELECT of INSERT ON CONFLICT DO SELECT
3015 : : *
3016 : : * If SELECT FOR UPDATE/SHARE is specified, try to lock tuple as part of
3017 : : * speculative insertion. If a qual originating from ON CONFLICT DO SELECT is
3018 : : * satisfied, select (but still lock row, even though it may not satisfy
3019 : : * estate's snapshot).
3020 : : *
3021 : : * Returns true if we're done (with or without a select), or false if the
3022 : : * caller must retry the INSERT from scratch.
3023 : : */
3024 : : static bool
242 dean.a.rasheed@gmail 3025 : 202 : ExecOnConflictSelect(ModifyTableContext *context,
3026 : : ResultRelInfo *resultRelInfo,
3027 : : ItemPointer conflictTid,
3028 : : TupleTableSlot *excludedSlot,
3029 : : bool canSetTag,
3030 : : TupleTableSlot **returning)
3031 : : {
3032 : 202 : ModifyTableState *mtstate = context->mtstate;
3033 : 202 : ExprContext *econtext = mtstate->ps.ps_ExprContext;
3034 : 202 : Relation relation = resultRelInfo->ri_RelationDesc;
3035 : 202 : ExprState *onConflictSelectWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
3036 : 202 : TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing;
3037 : 202 : LockClauseStrength lockStrength = resultRelInfo->ri_onConflict->oc_LockStrength;
3038 : :
3039 : : /*
3040 : : * Parse analysis should have blocked ON CONFLICT for all system
3041 : : * relations, which includes these. There's no fundamental obstacle to
3042 : : * supporting this; we'd just need to handle LOCKTAG_TUPLE appropriately.
3043 : : */
3044 [ - + ]: 202 : Assert(!resultRelInfo->ri_needLockTagTuple);
3045 : :
3046 : : /* Fetch/lock existing tuple, according to the requested lock strength */
3047 [ + + ]: 202 : if (lockStrength == LCS_NONE)
3048 : : {
3049 [ - + ]: 130 : if (!table_tuple_fetch_row_version(relation,
3050 : : conflictTid,
3051 : : SnapshotAny,
3052 : : existing))
242 dean.a.rasheed@gmail 3053 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT");
3054 : : }
3055 : : else
3056 : : {
3057 : : LockTupleMode lockmode;
3058 : :
242 dean.a.rasheed@gmail 3059 [ + + + + :CBC 72 : switch (lockStrength)
- ]
3060 : : {
3061 : 3 : case LCS_FORKEYSHARE:
3062 : 3 : lockmode = LockTupleKeyShare;
3063 : 3 : break;
3064 : 1 : case LCS_FORSHARE:
3065 : 1 : lockmode = LockTupleShare;
3066 : 1 : break;
3067 : 1 : case LCS_FORNOKEYUPDATE:
3068 : 1 : lockmode = LockTupleNoKeyExclusive;
3069 : 1 : break;
3070 : 67 : case LCS_FORUPDATE:
3071 : 67 : lockmode = LockTupleExclusive;
3072 : 67 : break;
242 dean.a.rasheed@gmail 3073 :UBC 0 : default:
3074 [ # # ]: 0 : elog(ERROR, "Unexpected lock strength %d", (int) lockStrength);
3075 : : }
3076 : :
242 dean.a.rasheed@gmail 3077 [ - + ]:CBC 72 : if (!ExecOnConflictLockRow(context, existing, conflictTid,
3078 : : resultRelInfo->ri_RelationDesc, lockmode, false))
242 dean.a.rasheed@gmail 3079 :UBC 0 : return false;
3080 : : }
3081 : :
3082 : : /*
3083 : : * Verify that the tuple is visible to our MVCC snapshot if the current
3084 : : * isolation level mandates that. See comments in ExecOnConflictUpdate().
3085 : : */
242 dean.a.rasheed@gmail 3086 :CBC 190 : ExecCheckTupleVisible(context->estate, relation, existing);
3087 : :
3088 : : /*
3089 : : * Make tuple and any needed join variables available to ExecQual. The
3090 : : * EXCLUDED tuple is installed in ecxt_innertuple, while the target's
3091 : : * existing tuple is installed in the scantuple. EXCLUDED has been made
3092 : : * to reference INNER_VAR in setrefs.c, but there is no other redirection.
3093 : : */
3094 : 189 : econtext->ecxt_scantuple = existing;
3095 : 189 : econtext->ecxt_innertuple = excludedSlot;
3096 : 189 : econtext->ecxt_outertuple = NULL;
3097 : :
3098 [ + + ]: 189 : if (!ExecQual(onConflictSelectWhere, econtext))
3099 : : {
3100 : 25 : ExecClearTuple(existing); /* see return below */
3101 [ - + ]: 25 : InstrCountFiltered1(&mtstate->ps, 1);
3102 : 25 : return true; /* done with the tuple */
3103 : : }
3104 : :
3105 [ + + ]: 164 : if (resultRelInfo->ri_WithCheckOptions != NIL)
3106 : : {
3107 : : /*
3108 : : * Check target's existing tuple against SELECT-applicable USING
3109 : : * security barrier quals (if any), enforced here as RLS checks/WCOs.
3110 : : *
3111 : : * The rewriter creates WCOs from the USING quals of SELECT policies,
3112 : : * and stores them as WCOs of "kind" WCO_RLS_CONFLICT_CHECK. If FOR
3113 : : * UPDATE/SHARE was specified, UPDATE permissions are required on the
3114 : : * target table, and the rewriter also adds WCOs built from the USING
3115 : : * quals of UPDATE policies, using WCOs of the same kind, and this
3116 : : * check enforces them too.
3117 : : */
3118 : 24 : ExecWithCheckOptions(WCO_RLS_CONFLICT_CHECK, resultRelInfo,
3119 : : existing,
3120 : : mtstate->ps.state);
3121 : : }
3122 : :
3123 : : /* RETURNING is required for DO SELECT */
3124 [ - + ]: 160 : Assert(resultRelInfo->ri_projectReturning);
3125 : :
3126 : 160 : *returning = ExecProcessReturning(context, resultRelInfo, false,
3127 : : existing, existing, context->planSlot);
3128 : :
3129 [ + - ]: 160 : if (canSetTag)
3130 : 160 : context->estate->es_processed++;
3131 : :
3132 : : /*
3133 : : * Before releasing the existing tuple, make sure that the returning slot
3134 : : * has a local copy of any pass-by-reference values.
3135 : : */
3136 : 160 : ExecMaterializeSlot(*returning);
3137 : :
3138 : : /*
3139 : : * Clear out existing tuple, as there might not be another conflict among
3140 : : * the next input rows. Don't want to hold resources till the end of the
3141 : : * query.
3142 : : */
3143 : 160 : ExecClearTuple(existing);
3144 : :
3145 : 160 : return true;
3146 : : }
3147 : :
3148 : : /*
3149 : : * Perform MERGE.
3150 : : */
3151 : : static TupleTableSlot *
1659 alvherre@alvh.no-ip. 3152 : 10108 : ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
3153 : : ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag)
3154 : : {
939 dean.a.rasheed@gmail 3155 : 10108 : TupleTableSlot *rslot = NULL;
3156 : : bool matched;
3157 : :
3158 : : /*-----
3159 : : * If we are dealing with a WHEN MATCHED case, tupleid or oldtuple is
3160 : : * valid, depending on whether the result relation is a table or a view.
3161 : : * We execute the first action for which the additional WHEN MATCHED AND
3162 : : * quals pass. If an action without quals is found, that action is
3163 : : * executed.
3164 : : *
3165 : : * Similarly, in the WHEN NOT MATCHED BY SOURCE case, tupleid or oldtuple
3166 : : * is valid, and we look at the given WHEN NOT MATCHED BY SOURCE actions
3167 : : * in sequence until one passes. This is almost identical to the WHEN
3168 : : * MATCHED case, and both cases are handled by ExecMergeMatched().
3169 : : *
3170 : : * Finally, in the WHEN NOT MATCHED [BY TARGET] case, both tupleid and
3171 : : * oldtuple are invalid, and we look at the given WHEN NOT MATCHED [BY
3172 : : * TARGET] actions in sequence until one passes.
3173 : : *
3174 : : * Things get interesting in case of concurrent update/delete of the
3175 : : * target tuple. Such concurrent update/delete is detected while we are
3176 : : * executing a WHEN MATCHED or WHEN NOT MATCHED BY SOURCE action.
3177 : : *
3178 : : * A concurrent update can:
3179 : : *
3180 : : * 1. modify the target tuple so that the results from checking any
3181 : : * additional quals attached to WHEN MATCHED or WHEN NOT MATCHED BY
3182 : : * SOURCE actions potentially change, but the result from the join
3183 : : * quals does not change.
3184 : : *
3185 : : * In this case, we are still dealing with the same kind of match
3186 : : * (MATCHED or NOT MATCHED BY SOURCE). We recheck the same list of
3187 : : * actions from the start and choose the first one that satisfies the
3188 : : * new target tuple.
3189 : : *
3190 : : * 2. modify the target tuple in the WHEN MATCHED case so that the join
3191 : : * quals no longer pass and hence the source and target tuples no
3192 : : * longer match.
3193 : : *
3194 : : * In this case, we are now dealing with a NOT MATCHED case, and we
3195 : : * process both WHEN NOT MATCHED BY SOURCE and WHEN NOT MATCHED [BY
3196 : : * TARGET] actions. First ExecMergeMatched() processes the list of
3197 : : * WHEN NOT MATCHED BY SOURCE actions in sequence until one passes,
3198 : : * then ExecMergeNotMatched() processes any WHEN NOT MATCHED [BY
3199 : : * TARGET] actions in sequence until one passes. Thus we may execute
3200 : : * two actions; one of each kind.
3201 : : *
3202 : : * Thus we support concurrent updates that turn MATCHED candidate rows
3203 : : * into NOT MATCHED rows. However, we do not attempt to support cases
3204 : : * that would turn NOT MATCHED rows into MATCHED rows, or which would
3205 : : * cause a target row to match a different source row.
3206 : : *
3207 : : * A concurrent delete changes a WHEN MATCHED case to WHEN NOT MATCHED
3208 : : * [BY TARGET].
3209 : : *
3210 : : * ExecMergeMatched() takes care of following the update chain and
3211 : : * re-finding the qualifying WHEN MATCHED or WHEN NOT MATCHED BY SOURCE
3212 : : * action, as long as the target tuple still exists. If the target tuple
3213 : : * gets deleted or a concurrent update causes the join quals to fail, it
3214 : : * returns a matched status of false and we call ExecMergeNotMatched().
3215 : : * Given that ExecMergeMatched() always makes progress by following the
3216 : : * update chain and we never switch from ExecMergeNotMatched() to
3217 : : * ExecMergeMatched(), there is no risk of a livelock.
3218 : : */
956 3219 [ + + + + ]: 10108 : matched = tupleid != NULL || oldtuple != NULL;
1659 alvherre@alvh.no-ip. 3220 [ + + ]: 10108 : if (matched)
939 dean.a.rasheed@gmail 3221 : 8311 : rslot = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple,
3222 : : canSetTag, &matched);
3223 : :
3224 : : /*
3225 : : * Deal with the NOT MATCHED case (either a NOT MATCHED tuple from the
3226 : : * join, or a previously MATCHED tuple for which ExecMergeMatched() set
3227 : : * "matched" to false, indicating that it no longer matches).
3228 : : */
1659 alvherre@alvh.no-ip. 3229 [ + + ]: 10046 : if (!matched)
3230 : : {
3231 : : /*
3232 : : * If a concurrent update turned a MATCHED case into a NOT MATCHED
3233 : : * case, and we have both WHEN NOT MATCHED BY SOURCE and WHEN NOT
3234 : : * MATCHED [BY TARGET] actions, and there is a RETURNING clause,
3235 : : * ExecMergeMatched() may have already executed a WHEN NOT MATCHED BY
3236 : : * SOURCE action, and computed the row to return. If so, we cannot
3237 : : * execute a WHEN NOT MATCHED [BY TARGET] action now, so mark it as
3238 : : * pending (to be processed on the next call to ExecModifyTable()).
3239 : : * Otherwise, just process the action now.
3240 : : */
926 dean.a.rasheed@gmail 3241 [ + + ]: 1806 : if (rslot == NULL)
3242 : 1804 : rslot = ExecMergeNotMatched(context, resultRelInfo, canSetTag);
3243 : : else
3244 : 2 : context->mtstate->mt_merge_pending_not_matched = context->planSlot;
3245 : : }
3246 : :
939 3247 : 10007 : return rslot;
3248 : : }
3249 : :
3250 : : /*
3251 : : * Check and execute the first qualifying MATCHED or NOT MATCHED BY SOURCE
3252 : : * action, depending on whether the join quals are satisfied. If the target
3253 : : * relation is a table, the current target tuple is identified by tupleid.
3254 : : * Otherwise, if the target relation is a view, oldtuple is the current target
3255 : : * tuple from the view.
3256 : : *
3257 : : * We start from the first WHEN MATCHED or WHEN NOT MATCHED BY SOURCE action
3258 : : * and check if the WHEN quals pass, if any. If the WHEN quals for the first
3259 : : * action do not pass, we check the second, then the third and so on. If we
3260 : : * reach the end without finding a qualifying action, we return NULL.
3261 : : * Otherwise, we execute the qualifying action and return its RETURNING
3262 : : * result, if any, or NULL.
3263 : : *
3264 : : * On entry, "*matched" is assumed to be true. If the join quals originally
3265 : : * passed (MATCHED case) and a concurrent update or delete is detected that
3266 : : * causes the join quals to no longer pass, we set "*matched" to false,
3267 : : * indicating that the caller should process any NOT MATCHED [BY TARGET]
3268 : : * actions.
3269 : : *
3270 : : * After a concurrent update, we restart from the first action to look for a
3271 : : * new qualifying action to execute. If the join quals originally passed, and
3272 : : * the concurrent update caused them to no longer pass, then we switch from
3273 : : * the MATCHED to the NOT MATCHED BY SOURCE list of actions before restarting
3274 : : * (and setting "*matched" to false). As a result we may execute a WHEN NOT
3275 : : * MATCHED BY SOURCE action, and set "*matched" to false, causing the caller
3276 : : * to also execute a WHEN NOT MATCHED [BY TARGET] action.
3277 : : */
3278 : : static TupleTableSlot *
1659 alvherre@alvh.no-ip. 3279 : 8311 : ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
3280 : : ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag,
3281 : : bool *matched)
3282 : : {
3283 : 8311 : ModifyTableState *mtstate = context->mtstate;
926 dean.a.rasheed@gmail 3284 : 8311 : List **mergeActions = resultRelInfo->ri_MergeActions;
3285 : : ItemPointerData lockedtid;
3286 : : List *actionStates;
939 3287 : 8311 : TupleTableSlot *newslot = NULL;
3288 : 8311 : TupleTableSlot *rslot = NULL;
1659 alvherre@alvh.no-ip. 3289 : 8311 : EState *estate = context->estate;
3290 : 8311 : ExprContext *econtext = mtstate->ps.ps_ExprContext;
3291 : : bool isNull;
3292 : 8311 : EPQState *epqstate = &mtstate->mt_epqstate;
3293 : : ListCell *l;
3294 : :
3295 : : /* Expect matched to be true on entry */
926 dean.a.rasheed@gmail 3296 [ - + ]: 8311 : Assert(*matched);
3297 : :
3298 : : /*
3299 : : * If there are no WHEN MATCHED or WHEN NOT MATCHED BY SOURCE actions, we
3300 : : * are done.
3301 : : */
3302 [ + + ]: 8311 : if (mergeActions[MERGE_WHEN_MATCHED] == NIL &&
3303 [ + + ]: 780 : mergeActions[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] == NIL)
939 3304 : 332 : return NULL;
3305 : :
3306 : : /*
3307 : : * Make tuple and any needed join variables available to ExecQual and
3308 : : * ExecProject. The target's existing tuple is installed in the scantuple.
3309 : : * This target relation's slot is required only in the case of a MATCHED
3310 : : * or NOT MATCHED BY SOURCE tuple and UPDATE/DELETE actions.
3311 : : */
1659 alvherre@alvh.no-ip. 3312 : 7979 : econtext->ecxt_scantuple = resultRelInfo->ri_oldTupleSlot;
3313 : 7979 : econtext->ecxt_innertuple = context->planSlot;
3314 : 7979 : econtext->ecxt_outertuple = NULL;
3315 : :
3316 : : /*
3317 : : * This routine is only invoked for matched target rows, so we should
3318 : : * either have the tupleid of the target row, or an old tuple from the
3319 : : * target wholerow junk attr.
3320 : : */
956 dean.a.rasheed@gmail 3321 [ + + - + ]: 7979 : Assert(tupleid != NULL || oldtuple != NULL);
748 noah@leadboat.com 3322 : 7979 : ItemPointerSetInvalid(&lockedtid);
956 dean.a.rasheed@gmail 3323 [ + + ]: 7979 : if (oldtuple != NULL)
3324 : : {
748 noah@leadboat.com 3325 [ - + ]: 64 : Assert(!resultRelInfo->ri_needLockTagTuple);
956 dean.a.rasheed@gmail 3326 : 64 : ExecForceStoreHeapTuple(oldtuple, resultRelInfo->ri_oldTupleSlot,
3327 : : false);
3328 : : }
3329 : : else
3330 : : {
748 noah@leadboat.com 3331 [ + + ]: 7915 : if (resultRelInfo->ri_needLockTagTuple)
3332 : : {
3333 : : /*
3334 : : * This locks even for CMD_DELETE, for CMD_NOTHING, and for tuples
3335 : : * that don't match mas_whenqual. MERGE on system catalogs is a
3336 : : * minor use case, so don't bother optimizing those.
3337 : : */
3338 : 5289 : LockTuple(resultRelInfo->ri_RelationDesc, tupleid,
3339 : : InplaceUpdateTupleLock);
3340 : 5289 : lockedtid = *tupleid;
3341 : : }
3342 [ - + ]: 7915 : if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
3343 : : tupleid,
3344 : : SnapshotAny,
3345 : : resultRelInfo->ri_oldTupleSlot))
748 noah@leadboat.com 3346 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch the target tuple");
3347 : : }
3348 : :
3349 : : /*
3350 : : * Test the join condition. If it's satisfied, perform a MATCHED action.
3351 : : * Otherwise, perform a NOT MATCHED BY SOURCE action.
3352 : : *
3353 : : * Note that this join condition will be NULL if there are no NOT MATCHED
3354 : : * BY SOURCE actions --- see transform_MERGE_to_join(). In that case, we
3355 : : * need only consider MATCHED actions here.
3356 : : */
926 dean.a.rasheed@gmail 3357 [ + + ]:CBC 7979 : if (ExecQual(resultRelInfo->ri_MergeJoinCondition, econtext))
3358 : 7851 : actionStates = mergeActions[MERGE_WHEN_MATCHED];
3359 : : else
3360 : 128 : actionStates = mergeActions[MERGE_WHEN_NOT_MATCHED_BY_SOURCE];
3361 : :
3362 : 7979 : lmerge_matched:
3363 : :
3364 [ + + + + : 14300 : foreach(l, actionStates)
+ + ]
3365 : : {
1659 alvherre@alvh.no-ip. 3366 : 8087 : MergeActionState *relaction = (MergeActionState *) lfirst(l);
3367 : 8087 : CmdType commandType = relaction->mas_action->commandType;
3368 : : TM_Result result;
3369 : 8087 : UpdateContext updateCxt = {0};
3370 : :
3371 : : /*
3372 : : * Test condition, if any.
3373 : : *
3374 : : * In the absence of any condition, we perform the action
3375 : : * unconditionally (no need to check separately since ExecQual() will
3376 : : * return true if there are no conditions to evaluate).
3377 : : */
3378 [ + + ]: 8087 : if (!ExecQual(relaction->mas_whenqual, econtext))
3379 : 6278 : continue;
3380 : :
3381 : : /*
3382 : : * Check if the existing target tuple meets the USING checks of
3383 : : * UPDATE/DELETE RLS policies. If those checks fail, we throw an
3384 : : * error.
3385 : : *
3386 : : * The WITH CHECK quals for UPDATE RLS policies are applied in
3387 : : * ExecUpdateAct() and hence we need not do anything special to handle
3388 : : * them.
3389 : : *
3390 : : * NOTE: We must do this after WHEN quals are evaluated, so that we
3391 : : * check policies only when they matter.
3392 : : */
1162 dean.a.rasheed@gmail 3393 [ + + + + ]: 1809 : if (resultRelInfo->ri_WithCheckOptions && commandType != CMD_NOTHING)
3394 : : {
1659 alvherre@alvh.no-ip. 3395 : 76 : ExecWithCheckOptions(commandType == CMD_UPDATE ?
3396 : : WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK,
3397 : : resultRelInfo,
3398 : : resultRelInfo->ri_oldTupleSlot,
3399 [ + + ]: 76 : context->mtstate->ps.state);
3400 : : }
3401 : :
3402 : : /* Perform stated action */
3403 [ + + + - ]: 1793 : switch (commandType)
3404 : : {
3405 : 1422 : case CMD_UPDATE:
3406 : :
3407 : : /*
3408 : : * Project the output tuple, and use that to update the table.
3409 : : * We don't need to filter out junk attributes, because the
3410 : : * UPDATE action's targetlist doesn't have any.
3411 : : */
3412 : 1422 : newslot = ExecProject(relaction->mas_proj);
3413 : :
939 dean.a.rasheed@gmail 3414 : 1422 : mtstate->mt_merge_action = relaction;
1659 alvherre@alvh.no-ip. 3415 [ + + ]: 1422 : if (!ExecUpdatePrologue(context, resultRelInfo,
3416 : : tupleid, NULL, newslot, &result))
3417 : : {
1309 dean.a.rasheed@gmail 3418 [ + + ]: 14 : if (result == TM_Ok)
748 noah@leadboat.com 3419 : 108 : goto out; /* "do nothing" */
3420 : :
1309 dean.a.rasheed@gmail 3421 : 10 : break; /* concurrent update/delete */
3422 : : }
3423 : :
3424 : : /* INSTEAD OF ROW UPDATE Triggers */
956 3425 [ + + ]: 1408 : if (resultRelInfo->ri_TrigDesc &&
3426 [ + + ]: 230 : resultRelInfo->ri_TrigDesc->trig_update_instead_row)
3427 : : {
3428 [ - + ]: 52 : if (!ExecIRUpdateTriggers(estate, resultRelInfo,
3429 : : oldtuple, newslot))
748 noah@leadboat.com 3430 :UBC 0 : goto out; /* "do nothing" */
3431 : : }
3432 : : else
3433 : : {
3434 : : /* checked ri_needLockTagTuple above */
821 noah@leadboat.com 3435 [ - + ]:CBC 1356 : Assert(oldtuple == NULL);
3436 : :
956 dean.a.rasheed@gmail 3437 : 1356 : result = ExecUpdateAct(context, resultRelInfo, tupleid,
3438 : : NULL, newslot, canSetTag,
3439 : : &updateCxt);
3440 : :
3441 : : /*
3442 : : * As in ExecUpdate(), if ExecUpdateAct() reports that a
3443 : : * cross-partition update was done, then there's nothing
3444 : : * else for us to do --- the UPDATE has been turned into a
3445 : : * DELETE and an INSERT, and we must not perform any of
3446 : : * the usual post-update tasks. Also, the RETURNING tuple
3447 : : * (if any) has been projected, so we can just return
3448 : : * that.
3449 : : */
3450 [ + + ]: 1341 : if (updateCxt.crossPartUpdate)
3451 : : {
3452 : 89 : mtstate->mt_merge_updated += 1;
748 noah@leadboat.com 3453 : 89 : rslot = context->cpUpdateReturningSlot;
3454 : 89 : goto out;
3455 : : }
3456 : : }
3457 : :
956 dean.a.rasheed@gmail 3458 [ + + ]: 1304 : if (result == TM_Ok)
3459 : : {
1659 alvherre@alvh.no-ip. 3460 : 1251 : ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
3461 : : tupleid, NULL, newslot);
3462 : 1243 : mtstate->mt_merge_updated += 1;
3463 : : }
3464 : 1296 : break;
3465 : :
3466 : 351 : case CMD_DELETE:
939 dean.a.rasheed@gmail 3467 : 351 : mtstate->mt_merge_action = relaction;
1659 alvherre@alvh.no-ip. 3468 [ + + ]: 351 : if (!ExecDeletePrologue(context, resultRelInfo, tupleid,
3469 : : NULL, NULL, &result))
3470 : : {
1309 dean.a.rasheed@gmail 3471 [ + + ]: 7 : if (result == TM_Ok)
748 noah@leadboat.com 3472 : 4 : goto out; /* "do nothing" */
3473 : :
1309 dean.a.rasheed@gmail 3474 : 3 : break; /* concurrent update/delete */
3475 : : }
3476 : :
3477 : : /* INSTEAD OF ROW DELETE Triggers */
956 3478 [ + + ]: 344 : if (resultRelInfo->ri_TrigDesc &&
3479 [ + + ]: 38 : resultRelInfo->ri_TrigDesc->trig_delete_instead_row)
3480 : : {
3481 [ - + ]: 4 : if (!ExecIRDeleteTriggers(estate, resultRelInfo,
3482 : : oldtuple))
748 noah@leadboat.com 3483 :UBC 0 : goto out; /* "do nothing" */
3484 : : }
3485 : : else
3486 : : {
3487 : : /* checked ri_needLockTagTuple above */
821 noah@leadboat.com 3488 [ - + ]:CBC 340 : Assert(oldtuple == NULL);
3489 : :
956 dean.a.rasheed@gmail 3490 : 340 : result = ExecDeleteAct(context, resultRelInfo, tupleid,
3491 : : false);
3492 : : }
3493 : :
1659 alvherre@alvh.no-ip. 3494 [ + + ]: 344 : if (result == TM_Ok)
3495 : : {
3496 : 333 : ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL,
3497 : : false);
3498 : 333 : mtstate->mt_merge_deleted += 1;
3499 : : }
3500 : 344 : break;
3501 : :
3502 : 20 : case CMD_NOTHING:
3503 : : /* Doing nothing is always OK */
3504 : 20 : result = TM_Ok;
3505 : 20 : break;
3506 : :
1659 alvherre@alvh.no-ip. 3507 :UBC 0 : default:
926 dean.a.rasheed@gmail 3508 [ # # ]: 0 : elog(ERROR, "unknown action in MERGE WHEN clause");
3509 : : }
3510 : :
1659 alvherre@alvh.no-ip. 3511 [ + + + + :CBC 1673 : switch (result)
- - ]
3512 : : {
3513 : 1596 : case TM_Ok:
3514 : : /* all good; perform final actions */
1425 3515 [ + + + + ]: 1596 : if (canSetTag && commandType != CMD_NOTHING)
1659 3516 : 1561 : (estate->es_processed)++;
3517 : :
3518 : 1596 : break;
3519 : :
3520 : 21 : case TM_SelfModified:
3521 : :
3522 : : /*
3523 : : * The target tuple was already updated or deleted by the
3524 : : * current command, or by a later command in the current
3525 : : * transaction. The former case is explicitly disallowed by
3526 : : * the SQL standard for MERGE, which insists that the MERGE
3527 : : * join condition should not join a target row to more than
3528 : : * one source row.
3529 : : *
3530 : : * The latter case arises if the tuple is modified by a
3531 : : * command in a BEFORE trigger, or perhaps by a command in a
3532 : : * volatile function used in the query. In such situations we
3533 : : * should not ignore the MERGE action, but it is equally
3534 : : * unsafe to proceed. We don't want to discard the original
3535 : : * MERGE action while keeping the triggered actions based on
3536 : : * it; and it would be no better to allow the original MERGE
3537 : : * action while discarding the updates that it triggered. So
3538 : : * throwing an error is the only safe course.
3539 : : */
949 dean.a.rasheed@gmail 3540 [ + + ]: 21 : if (context->tmfd.cmax != estate->es_output_cid)
3541 [ + - ]: 8 : ereport(ERROR,
3542 : : (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
3543 : : errmsg("tuple to be updated or deleted was already modified by an operation triggered by the current command"),
3544 : : errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
3545 : :
1659 alvherre@alvh.no-ip. 3546 [ + - ]: 13 : if (TransactionIdIsCurrentTransactionId(context->tmfd.xmax))
3547 [ + - ]: 13 : ereport(ERROR,
3548 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
3549 : : /* translator: %s is a SQL command name */
3550 : : errmsg("%s command cannot affect row a second time",
3551 : : "MERGE"),
3552 : : errhint("Ensure that not more than one source row matches any one target row.")));
3553 : :
3554 : : /* This shouldn't happen */
1659 alvherre@alvh.no-ip. 3555 [ # # ]:UBC 0 : elog(ERROR, "attempted to update or delete invisible tuple");
3556 : : break;
3557 : :
1659 alvherre@alvh.no-ip. 3558 :CBC 9 : case TM_Deleted:
3559 [ - + ]: 9 : if (IsolationUsesXactSnapshot())
1659 alvherre@alvh.no-ip. 3560 [ # # ]:UBC 0 : ereport(ERROR,
3561 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
3562 : : errmsg("could not serialize access due to concurrent delete")));
3563 : :
3564 : : /*
3565 : : * The target tuple was concurrently deleted by some other
3566 : : * transaction. If this was a MATCHED action, the source row
3567 : : * still exists, so set *matched to false, to let the caller
3568 : : * handle it using any NOT MATCHED [BY TARGET] actions.
3569 : : * Otherwise, for a NOT MATCHED BY SOURCE action, neither the
3570 : : * source row nor the target row now exists, so there is no
3571 : : * futher action to execute.
3572 : : */
19 dean.a.rasheed@gmail 3573 [ + + ]:CBC 9 : if (relaction->mas_action->matchKind == MERGE_WHEN_MATCHED)
3574 : 5 : *matched = false;
748 noah@leadboat.com 3575 : 9 : goto out;
3576 : :
1659 alvherre@alvh.no-ip. 3577 : 47 : case TM_Updated:
3578 : : {
3579 : : bool was_matched;
3580 : : Relation resultRelationDesc;
3581 : : TupleTableSlot *epqslot,
3582 : : *inputslot;
3583 : : LockTupleMode lockmode;
3584 : :
221 akorotkov@postgresql 3585 [ + + ]: 47 : if (IsolationUsesXactSnapshot())
3586 [ + - ]: 1 : ereport(ERROR,
3587 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
3588 : : errmsg("could not serialize access due to concurrent update")));
3589 : :
3590 : : /*
3591 : : * The target tuple was concurrently updated by some other
3592 : : * transaction. If we are currently processing a MATCHED
3593 : : * action, use EvalPlanQual() with the new version of the
3594 : : * tuple and recheck the join qual, to detect a change
3595 : : * from the MATCHED to the NOT MATCHED cases. If we are
3596 : : * already processing a NOT MATCHED BY SOURCE action, we
3597 : : * skip this (cannot switch from NOT MATCHED BY SOURCE to
3598 : : * MATCHED).
3599 : : */
926 dean.a.rasheed@gmail 3600 : 46 : was_matched = relaction->mas_action->matchKind == MERGE_WHEN_MATCHED;
1659 alvherre@alvh.no-ip. 3601 : 46 : resultRelationDesc = resultRelInfo->ri_RelationDesc;
3602 : 46 : lockmode = ExecUpdateLockMode(estate, resultRelInfo);
3603 : :
926 dean.a.rasheed@gmail 3604 [ + + ]: 46 : if (was_matched)
3605 : 44 : inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc,
3606 : : resultRelInfo->ri_RangeTableIndex);
3607 : : else
3608 : 2 : inputslot = resultRelInfo->ri_oldTupleSlot;
3609 : :
1659 alvherre@alvh.no-ip. 3610 : 46 : result = table_tuple_lock(resultRelationDesc, tupleid,
3611 : : estate->es_snapshot,
3612 : : inputslot, estate->es_output_cid,
3613 : : lockmode, LockWaitBlock,
3614 : : TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
3615 : : &context->tmfd);
3616 [ + + + - ]: 46 : switch (result)
3617 : : {
3618 : 43 : case TM_Ok:
3619 : :
3620 : : /*
3621 : : * If the tuple was updated and migrated to
3622 : : * another partition concurrently, the current
3623 : : * MERGE implementation can't follow. There's
3624 : : * probably a better way to handle this case, but
3625 : : * it'd require recognizing the relation to which
3626 : : * the tuple moved, and setting our current
3627 : : * resultRelInfo to that.
3628 : : */
402 dean.a.rasheed@gmail 3629 [ - + ]: 43 : if (ItemPointerIndicatesMovedPartitions(tupleid))
1659 alvherre@alvh.no-ip. 3630 [ # # ]:UBC 0 : ereport(ERROR,
3631 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
3632 : : errmsg("tuple to be merged was already moved to another partition due to concurrent update")));
3633 : :
3634 : : /*
3635 : : * If this was a MATCHED case, use EvalPlanQual()
3636 : : * to recheck the join condition.
3637 : : */
926 dean.a.rasheed@gmail 3638 [ + - ]:CBC 43 : if (was_matched)
3639 : : {
3640 : 43 : epqslot = EvalPlanQual(epqstate,
3641 : : resultRelationDesc,
3642 : : resultRelInfo->ri_RangeTableIndex,
3643 : : inputslot);
3644 : :
3645 : : /*
3646 : : * If the subplan didn't return a tuple, then
3647 : : * we must be dealing with an inner join for
3648 : : * which the join condition no longer matches.
3649 : : * This can only happen if there are no NOT
3650 : : * MATCHED actions, and so there is nothing
3651 : : * more to do.
3652 : : */
3653 [ + - - + ]: 43 : if (TupIsNull(epqslot))
748 noah@leadboat.com 3654 :UBC 0 : goto out;
3655 : :
3656 : : /*
3657 : : * If we got a NULL ctid from the subplan, the
3658 : : * join quals no longer pass and we switch to
3659 : : * the NOT MATCHED BY SOURCE case.
3660 : : */
926 dean.a.rasheed@gmail 3661 :CBC 43 : (void) ExecGetJunkAttribute(epqslot,
3662 : 43 : resultRelInfo->ri_RowIdAttNo,
3663 : : &isNull);
3664 [ + + ]: 43 : if (isNull)
3665 : 2 : *matched = false;
3666 : :
3667 : : /*
3668 : : * Otherwise, recheck the join quals to see if
3669 : : * we need to switch to the NOT MATCHED BY
3670 : : * SOURCE case.
3671 : : */
748 noah@leadboat.com 3672 [ + + ]: 43 : if (resultRelInfo->ri_needLockTagTuple)
3673 : : {
3674 [ + - ]: 1 : if (ItemPointerIsValid(&lockedtid))
3675 : 1 : UnlockTuple(resultRelInfo->ri_RelationDesc, &lockedtid,
3676 : : InplaceUpdateTupleLock);
402 dean.a.rasheed@gmail 3677 : 1 : LockTuple(resultRelInfo->ri_RelationDesc, tupleid,
3678 : : InplaceUpdateTupleLock);
3679 : 1 : lockedtid = *tupleid;
3680 : : }
3681 : :
926 3682 [ - + ]: 43 : if (!table_tuple_fetch_row_version(resultRelationDesc,
3683 : : tupleid,
3684 : : SnapshotAny,
3685 : : resultRelInfo->ri_oldTupleSlot))
926 dean.a.rasheed@gmail 3686 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch the target tuple");
3687 : :
926 dean.a.rasheed@gmail 3688 [ + + ]:CBC 43 : if (*matched)
3689 : 41 : *matched = ExecQual(resultRelInfo->ri_MergeJoinCondition,
3690 : : econtext);
3691 : :
3692 : : /* Switch lists, if necessary */
3693 [ + + ]: 43 : if (!*matched)
3694 : : {
3695 : 4 : actionStates = mergeActions[MERGE_WHEN_NOT_MATCHED_BY_SOURCE];
3696 : :
3697 : : /*
3698 : : * If we have both NOT MATCHED BY SOURCE
3699 : : * and NOT MATCHED BY TARGET actions (a
3700 : : * full join between the source and target
3701 : : * relations), the single previously
3702 : : * matched tuple from the outer plan node
3703 : : * is treated as two not matched tuples,
3704 : : * in the same way as if they had not
3705 : : * matched to start with. Therefore, we
3706 : : * must adjust the outer plan node's tuple
3707 : : * count, if we're instrumenting the
3708 : : * query, to get the correct "skipped" row
3709 : : * count --- see show_modifytable_info().
3710 : : */
330 3711 [ + + ]: 4 : if (outerPlanState(mtstate)->instrument &&
3712 [ + - ]: 1 : mergeActions[MERGE_WHEN_NOT_MATCHED_BY_SOURCE] &&
3713 [ + - ]: 1 : mergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET])
3714 : 1 : InstrUpdateTupleCount(outerPlanState(mtstate)->instrument, 1.0);
3715 : : }
3716 : : }
3717 : :
3718 : : /*
3719 : : * Loop back and process the MATCHED or NOT
3720 : : * MATCHED BY SOURCE actions from the start.
3721 : : */
1659 alvherre@alvh.no-ip. 3722 : 43 : goto lmerge_matched;
3723 : :
3724 : 2 : case TM_Deleted:
3725 : :
3726 : : /*
3727 : : * The target tuple was concurrently deleted by
3728 : : * some other transaction. If this was a MATCHED
3729 : : * action, the source row still exists, so set
3730 : : * *matched to false, to let the caller handle it
3731 : : * using any NOT MATCHED [BY TARGET] actions.
3732 : : * Otherwise, for a NOT MATCHED BY SOURCE action,
3733 : : * neither the source row nor the target row now
3734 : : * exists, so there is no futher action to
3735 : : * execute.
3736 : : */
19 dean.a.rasheed@gmail 3737 [ - + ]: 2 : if (was_matched)
19 dean.a.rasheed@gmail 3738 :UBC 0 : *matched = false;
748 noah@leadboat.com 3739 :CBC 2 : goto out;
3740 : :
1659 alvherre@alvh.no-ip. 3741 : 1 : case TM_SelfModified:
3742 : :
3743 : : /*
3744 : : * This can be reached when following an update
3745 : : * chain from a tuple updated by another session,
3746 : : * reaching a tuple that was already updated or
3747 : : * deleted by the current command, or by a later
3748 : : * command in the current transaction. As above,
3749 : : * this should always be treated as an error.
3750 : : */
3751 [ - + ]: 1 : if (context->tmfd.cmax != estate->es_output_cid)
1659 alvherre@alvh.no-ip. 3752 [ # # ]:UBC 0 : ereport(ERROR,
3753 : : (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
3754 : : errmsg("tuple to be updated or deleted was already modified by an operation triggered by the current command"),
3755 : : errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
3756 : :
949 dean.a.rasheed@gmail 3757 [ + - ]:CBC 1 : if (TransactionIdIsCurrentTransactionId(context->tmfd.xmax))
3758 [ + - ]: 1 : ereport(ERROR,
3759 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
3760 : : /* translator: %s is a SQL command name */
3761 : : errmsg("%s command cannot affect row a second time",
3762 : : "MERGE"),
3763 : : errhint("Ensure that not more than one source row matches any one target row.")));
3764 : :
3765 : : /* This shouldn't happen */
949 dean.a.rasheed@gmail 3766 [ # # ]:UBC 0 : elog(ERROR, "attempted to update or delete invisible tuple");
3767 : : goto out;
3768 : :
1659 alvherre@alvh.no-ip. 3769 : 0 : default:
3770 : : /* see table_tuple_lock call in ExecDelete() */
3771 [ # # ]: 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
3772 : : result);
3773 : : goto out;
3774 : : }
3775 : : }
3776 : :
3777 : 0 : case TM_Invisible:
3778 : : case TM_WouldBlock:
3779 : : case TM_BeingModified:
3780 : : /* these should not occur */
3781 [ # # ]: 0 : elog(ERROR, "unexpected tuple operation result: %d", result);
3782 : : break;
3783 : : }
3784 : :
3785 : : /* Process RETURNING if present */
939 dean.a.rasheed@gmail 3786 [ + + ]:CBC 1596 : if (resultRelInfo->ri_projectReturning)
3787 : : {
3788 [ + + - - ]: 296 : switch (commandType)
3789 : : {
3790 : 129 : case CMD_UPDATE:
634 3791 : 129 : rslot = ExecProcessReturning(context,
3792 : : resultRelInfo,
3793 : : false,
3794 : : resultRelInfo->ri_oldTupleSlot,
3795 : : newslot,
3796 : : context->planSlot);
939 3797 : 129 : break;
3798 : :
3799 : 167 : case CMD_DELETE:
634 3800 : 167 : rslot = ExecProcessReturning(context,
3801 : : resultRelInfo,
3802 : : true,
3803 : : resultRelInfo->ri_oldTupleSlot,
3804 : : NULL,
3805 : : context->planSlot);
939 3806 : 167 : break;
3807 : :
939 dean.a.rasheed@gmail 3808 :UBC 0 : case CMD_NOTHING:
3809 : 0 : break;
3810 : :
3811 : 0 : default:
3812 [ # # ]: 0 : elog(ERROR, "unrecognized commandType: %d",
3813 : : (int) commandType);
3814 : : }
3815 : : }
3816 : :
3817 : : /*
3818 : : * We've activated one of the WHEN clauses, so we don't search
3819 : : * further. This is required behaviour, not an optimization.
3820 : : */
1659 alvherre@alvh.no-ip. 3821 :CBC 1596 : break;
3822 : : }
3823 : :
3824 : : /*
3825 : : * Successfully executed an action or no qualifying action was found.
3826 : : */
748 noah@leadboat.com 3827 : 7917 : out:
3828 [ + + ]: 7917 : if (ItemPointerIsValid(&lockedtid))
3829 : 5289 : UnlockTuple(resultRelInfo->ri_RelationDesc, &lockedtid,
3830 : : InplaceUpdateTupleLock);
939 dean.a.rasheed@gmail 3831 : 7917 : return rslot;
3832 : : }
3833 : :
3834 : : /*
3835 : : * Execute the first qualifying NOT MATCHED [BY TARGET] action.
3836 : : */
3837 : : static TupleTableSlot *
1659 alvherre@alvh.no-ip. 3838 : 1806 : ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
3839 : : bool canSetTag)
3840 : : {
3841 : 1806 : ModifyTableState *mtstate = context->mtstate;
3842 : 1806 : ExprContext *econtext = mtstate->ps.ps_ExprContext;
3843 : : List *actionStates;
939 dean.a.rasheed@gmail 3844 : 1806 : TupleTableSlot *rslot = NULL;
3845 : : ListCell *l;
3846 : :
3847 : : /*
3848 : : * For INSERT actions, the root relation's merge action is OK since the
3849 : : * INSERT's targetlist and the WHEN conditions can only refer to the
3850 : : * source relation and hence it does not matter which result relation we
3851 : : * work with.
3852 : : *
3853 : : * XXX does this mean that we can avoid creating copies of actionStates on
3854 : : * partitioned tables, for not-matched actions?
3855 : : */
926 3856 : 1806 : actionStates = resultRelInfo->ri_MergeActions[MERGE_WHEN_NOT_MATCHED_BY_TARGET];
3857 : :
3858 : : /*
3859 : : * Make source tuple available to ExecQual and ExecProject. We don't need
3860 : : * the target tuple, since the WHEN quals and targetlist can't refer to
3861 : : * the target columns.
3862 : : */
1659 alvherre@alvh.no-ip. 3863 : 1806 : econtext->ecxt_scantuple = NULL;
3864 : 1806 : econtext->ecxt_innertuple = context->planSlot;
3865 : 1806 : econtext->ecxt_outertuple = NULL;
3866 : :
3867 [ + - + + : 2386 : foreach(l, actionStates)
+ + ]
3868 : : {
3869 : 1806 : MergeActionState *action = (MergeActionState *) lfirst(l);
3870 : 1806 : CmdType commandType = action->mas_action->commandType;
3871 : : TupleTableSlot *newslot;
3872 : :
3873 : : /*
3874 : : * Test condition, if any.
3875 : : *
3876 : : * In the absence of any condition, we perform the action
3877 : : * unconditionally (no need to check separately since ExecQual() will
3878 : : * return true if there are no conditions to evaluate).
3879 : : */
3880 [ + + ]: 1806 : if (!ExecQual(action->mas_whenqual, econtext))
3881 : 580 : continue;
3882 : :
3883 : : /* Perform stated action */
3884 [ + - - ]: 1226 : switch (commandType)
3885 : : {
3886 : 1226 : case CMD_INSERT:
3887 : :
3888 : : /*
3889 : : * Project the tuple. In case of a partitioned table, the
3890 : : * projection was already built to use the root's descriptor,
3891 : : * so we don't need to map the tuple here.
3892 : : */
3893 : 1226 : newslot = ExecProject(action->mas_proj);
939 dean.a.rasheed@gmail 3894 : 1226 : mtstate->mt_merge_action = action;
3895 : :
3896 : 1226 : rslot = ExecInsert(context, mtstate->rootResultRelInfo,
3897 : : newslot, canSetTag, NULL, NULL);
1659 alvherre@alvh.no-ip. 3898 : 1187 : mtstate->mt_merge_inserted += 1;
3899 : 1187 : break;
1659 alvherre@alvh.no-ip. 3900 :UBC 0 : case CMD_NOTHING:
3901 : : /* Do nothing */
3902 : 0 : break;
3903 : 0 : default:
3904 [ # # ]: 0 : elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause");
3905 : : }
3906 : :
3907 : : /*
3908 : : * We've activated one of the WHEN clauses, so we don't search
3909 : : * further. This is required behaviour, not an optimization.
3910 : : */
1659 alvherre@alvh.no-ip. 3911 :CBC 1187 : break;
3912 : : }
3913 : :
939 dean.a.rasheed@gmail 3914 : 1767 : return rslot;
3915 : : }
3916 : :
3917 : : /*
3918 : : * Initialize state for execution of MERGE.
3919 : : */
3920 : : void
1659 alvherre@alvh.no-ip. 3921 : 1066 : ExecInitMerge(ModifyTableState *mtstate, EState *estate)
3922 : : {
602 amitlan@postgresql.o 3923 : 1066 : List *mergeActionLists = mtstate->mt_mergeActionLists;
3924 : 1066 : List *mergeJoinConditions = mtstate->mt_mergeJoinConditions;
1659 alvherre@alvh.no-ip. 3925 : 1066 : ResultRelInfo *rootRelInfo = mtstate->rootResultRelInfo;
3926 : : ResultRelInfo *resultRelInfo;
3927 : : ExprContext *econtext;
3928 : : ListCell *lc;
3929 : : int i;
3930 : :
602 amitlan@postgresql.o 3931 [ - + ]: 1066 : if (mergeActionLists == NIL)
1659 alvherre@alvh.no-ip. 3932 :UBC 0 : return;
3933 : :
1659 alvherre@alvh.no-ip. 3934 :CBC 1066 : mtstate->mt_merge_subcommands = 0;
3935 : :
3936 [ + + ]: 1066 : if (mtstate->ps.ps_ExprContext == NULL)
3937 : 857 : ExecAssignExprContext(estate, &mtstate->ps);
3938 : 1066 : econtext = mtstate->ps.ps_ExprContext;
3939 : :
3940 : : /*
3941 : : * Create a MergeActionState for each action on the mergeActionList and
3942 : : * add it to either a list of matched actions or not-matched actions.
3943 : : *
3944 : : * Similar logic appears in ExecInitPartitionInfo(), so if changing
3945 : : * anything here, do so there too.
3946 : : */
3947 : 1066 : i = 0;
602 amitlan@postgresql.o 3948 [ + - + + : 2291 : foreach(lc, mergeActionLists)
+ + ]
3949 : : {
1659 alvherre@alvh.no-ip. 3950 : 1225 : List *mergeActionList = lfirst(lc);
3951 : : Node *joinCondition;
3952 : : TupleDesc relationDesc;
3953 : : ListCell *l;
3954 : :
602 amitlan@postgresql.o 3955 : 1225 : joinCondition = (Node *) list_nth(mergeJoinConditions, i);
1659 alvherre@alvh.no-ip. 3956 : 1225 : resultRelInfo = mtstate->resultRelInfo + i;
3957 : 1225 : i++;
3958 : 1225 : relationDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
3959 : :
3960 : : /* initialize slots for MERGE fetches from this rel */
3961 [ + - ]: 1225 : if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
3962 : 1225 : ExecInitMergeTupleSlots(mtstate, resultRelInfo);
3963 : :
3964 : : /* initialize state for join condition checking */
926 dean.a.rasheed@gmail 3965 : 1225 : resultRelInfo->ri_MergeJoinCondition =
3966 : 1225 : ExecInitQual((List *) joinCondition, &mtstate->ps);
3967 : :
1659 alvherre@alvh.no-ip. 3968 [ + - + + : 3393 : foreach(l, mergeActionList)
+ + ]
3969 : : {
3970 : 2168 : MergeAction *action = (MergeAction *) lfirst(l);
3971 : : MergeActionState *action_state;
3972 : : TupleTableSlot *tgtslot;
3973 : : TupleDesc tgtdesc;
3974 : :
3975 : : /*
3976 : : * Build action merge state for this rel. (For partitions,
3977 : : * equivalent code exists in ExecInitPartitionInfo.)
3978 : : */
3979 : 2168 : action_state = makeNode(MergeActionState);
3980 : 2168 : action_state->mas_action = action;
3981 : 2168 : action_state->mas_whenqual = ExecInitQual((List *) action->qual,
3982 : : &mtstate->ps);
3983 : :
3984 : : /*
3985 : : * We create three lists - one for each MergeMatchKind - and stick
3986 : : * the MergeActionState into the appropriate list.
3987 : : */
926 dean.a.rasheed@gmail 3988 : 4336 : resultRelInfo->ri_MergeActions[action->matchKind] =
3989 : 2168 : lappend(resultRelInfo->ri_MergeActions[action->matchKind],
3990 : : action_state);
3991 : :
1659 alvherre@alvh.no-ip. 3992 [ + + + + : 2168 : switch (action->commandType)
- ]
3993 : : {
3994 : 712 : case CMD_INSERT:
3995 : : /* INSERT actions always use rootRelInfo */
3996 : 712 : ExecCheckPlanOutput(rootRelInfo->ri_RelationDesc,
3997 : : action->targetList);
3998 : :
3999 : : /*
4000 : : * If the MERGE targets a partitioned table, any INSERT
4001 : : * actions must be routed through it, not the child
4002 : : * relations. Initialize the routing struct and the root
4003 : : * table's "new" tuple slot for that, if not already done.
4004 : : * The projection we prepare, for all relations, uses the
4005 : : * root relation descriptor, and targets the plan's root
4006 : : * slot. (This is consistent with the fact that we
4007 : : * checked the plan output to match the root relation,
4008 : : * above.)
4009 : : */
4010 [ + + ]: 712 : if (rootRelInfo->ri_RelationDesc->rd_rel->relkind ==
4011 : : RELKIND_PARTITIONED_TABLE)
4012 : : {
4013 [ + + ]: 220 : if (mtstate->mt_partition_tuple_routing == NULL)
4014 : : {
4015 : : /*
4016 : : * Initialize planstate for routing if not already
4017 : : * done.
4018 : : *
4019 : : * Note that the slot is managed as a standalone
4020 : : * slot belonging to ModifyTableState, so we pass
4021 : : * NULL for the 2nd argument.
4022 : : */
4023 : 102 : mtstate->mt_root_tuple_slot =
4024 : 102 : table_slot_create(rootRelInfo->ri_RelationDesc,
4025 : : NULL);
4026 : 102 : mtstate->mt_partition_tuple_routing =
4027 : 102 : ExecSetupPartitionTupleRouting(estate,
4028 : : rootRelInfo->ri_RelationDesc);
4029 : : }
4030 : 220 : tgtslot = mtstate->mt_root_tuple_slot;
4031 : 220 : tgtdesc = RelationGetDescr(rootRelInfo->ri_RelationDesc);
4032 : : }
4033 : : else
4034 : : {
4035 : : /*
4036 : : * If the MERGE targets an inherited table, we insert
4037 : : * into the root table, so we must initialize its
4038 : : * "new" tuple slot, if not already done, and use its
4039 : : * relation descriptor for the projection.
4040 : : *
4041 : : * For non-inherited tables, rootRelInfo and
4042 : : * resultRelInfo are the same, and the "new" tuple
4043 : : * slot will already have been initialized.
4044 : : */
499 dean.a.rasheed@gmail 4045 [ + + ]: 492 : if (rootRelInfo->ri_newTupleSlot == NULL)
4046 : 24 : rootRelInfo->ri_newTupleSlot =
4047 : 24 : table_slot_create(rootRelInfo->ri_RelationDesc,
4048 : : &estate->es_tupleTable);
4049 : :
4050 : 492 : tgtslot = rootRelInfo->ri_newTupleSlot;
4051 : 492 : tgtdesc = RelationGetDescr(rootRelInfo->ri_RelationDesc);
4052 : : }
4053 : :
1659 alvherre@alvh.no-ip. 4054 : 712 : action_state->mas_proj =
4055 : 712 : ExecBuildProjectionInfo(action->targetList, econtext,
4056 : : tgtslot,
4057 : : &mtstate->ps,
4058 : : tgtdesc);
4059 : :
4060 : 712 : mtstate->mt_merge_subcommands |= MERGE_INSERT;
4061 : 712 : break;
4062 : 1071 : case CMD_UPDATE:
4063 : 1071 : action_state->mas_proj =
4064 : 1071 : ExecBuildUpdateProjection(action->targetList,
4065 : : true,
4066 : : action->updateColnos,
4067 : : relationDesc,
4068 : : econtext,
4069 : : resultRelInfo->ri_newTupleSlot,
4070 : : &mtstate->ps);
4071 : 1071 : mtstate->mt_merge_subcommands |= MERGE_UPDATE;
4072 : 1071 : break;
4073 : 335 : case CMD_DELETE:
4074 : 335 : mtstate->mt_merge_subcommands |= MERGE_DELETE;
4075 : 335 : break;
4076 : 50 : case CMD_NOTHING:
4077 : 50 : break;
1659 alvherre@alvh.no-ip. 4078 :UBC 0 : default:
562 dean.a.rasheed@gmail 4079 [ # # ]: 0 : elog(ERROR, "unknown action in MERGE WHEN clause");
4080 : : break;
4081 : : }
4082 : : }
4083 : : }
4084 : :
4085 : : /*
4086 : : * If the MERGE targets an inherited table, any INSERT actions will use
4087 : : * rootRelInfo, and rootRelInfo will not be in the resultRelInfo array.
4088 : : * Therefore we must initialize its WITH CHECK OPTION constraints and
4089 : : * RETURNING projection, as ExecInitModifyTable did for the resultRelInfo
4090 : : * entries.
4091 : : *
4092 : : * Note that the planner does not build a withCheckOptionList or
4093 : : * returningList for the root relation, but as in ExecInitPartitionInfo,
4094 : : * we can use the first resultRelInfo entry as a reference to calculate
4095 : : * the attno's for the root table.
4096 : : */
499 dean.a.rasheed@gmail 4097 [ + + ]:CBC 1066 : if (rootRelInfo != mtstate->resultRelInfo &&
4098 [ + + ]: 162 : rootRelInfo->ri_RelationDesc->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
4099 [ + + ]: 32 : (mtstate->mt_merge_subcommands & MERGE_INSERT) != 0)
4100 : : {
4101 : 24 : ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
4102 : 24 : Relation rootRelation = rootRelInfo->ri_RelationDesc;
4103 : 24 : Relation firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
4104 : 24 : int firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
4105 : 24 : AttrMap *part_attmap = NULL;
4106 : : bool found_whole_row;
4107 : :
4108 [ + + ]: 24 : if (node->withCheckOptionLists != NIL)
4109 : : {
4110 : : List *wcoList;
4111 : 12 : List *wcoExprs = NIL;
4112 : :
4113 : : /* There should be as many WCO lists as result rels */
4114 [ - + ]: 12 : Assert(list_length(node->withCheckOptionLists) ==
4115 : : list_length(node->resultRelations));
4116 : :
4117 : : /*
4118 : : * Use the first WCO list as a reference. In the most common case,
4119 : : * this will be for the same relation as rootRelInfo, and so there
4120 : : * will be no need to adjust its attno's.
4121 : : */
4122 : 12 : wcoList = linitial(node->withCheckOptionLists);
4123 [ + - ]: 12 : if (rootRelation != firstResultRel)
4124 : : {
4125 : : /* Convert any Vars in it to contain the root's attno's */
4126 : : part_attmap =
4127 : 12 : build_attrmap_by_name(RelationGetDescr(rootRelation),
4128 : : RelationGetDescr(firstResultRel),
4129 : : false);
4130 : :
4131 : : wcoList = (List *)
4132 : 12 : map_variable_attnos((Node *) wcoList,
4133 : : firstVarno, 0,
4134 : : part_attmap,
4135 : 12 : RelationGetForm(rootRelation)->reltype,
4136 : : &found_whole_row);
4137 : : }
4138 : :
4139 [ + - + + : 60 : foreach(lc, wcoList)
+ + ]
4140 : : {
4141 : 48 : WithCheckOption *wco = lfirst_node(WithCheckOption, lc);
4142 : 48 : ExprState *wcoExpr = ExecInitQual(castNode(List, wco->qual),
4143 : : &mtstate->ps);
4144 : :
4145 : 48 : wcoExprs = lappend(wcoExprs, wcoExpr);
4146 : : }
4147 : :
4148 : 12 : rootRelInfo->ri_WithCheckOptions = wcoList;
4149 : 12 : rootRelInfo->ri_WithCheckOptionExprs = wcoExprs;
4150 : : }
4151 : :
4152 [ + + ]: 24 : if (node->returningLists != NIL)
4153 : : {
4154 : : List *returningList;
4155 : :
4156 : : /* There should be as many returning lists as result rels */
4157 [ - + ]: 4 : Assert(list_length(node->returningLists) ==
4158 : : list_length(node->resultRelations));
4159 : :
4160 : : /*
4161 : : * Use the first returning list as a reference. In the most common
4162 : : * case, this will be for the same relation as rootRelInfo, and so
4163 : : * there will be no need to adjust its attno's.
4164 : : */
4165 : 4 : returningList = linitial(node->returningLists);
4166 [ + - ]: 4 : if (rootRelation != firstResultRel)
4167 : : {
4168 : : /* Convert any Vars in it to contain the root's attno's */
4169 [ - + ]: 4 : if (part_attmap == NULL)
4170 : : part_attmap =
499 dean.a.rasheed@gmail 4171 :UBC 0 : build_attrmap_by_name(RelationGetDescr(rootRelation),
4172 : : RelationGetDescr(firstResultRel),
4173 : : false);
4174 : :
4175 : : returningList = (List *)
499 dean.a.rasheed@gmail 4176 :CBC 4 : map_variable_attnos((Node *) returningList,
4177 : : firstVarno, 0,
4178 : : part_attmap,
4179 : 4 : RelationGetForm(rootRelation)->reltype,
4180 : : &found_whole_row);
4181 : : }
4182 : 4 : rootRelInfo->ri_returningList = returningList;
4183 : :
4184 : : /* Initialize the RETURNING projection */
4185 : 4 : rootRelInfo->ri_projectReturning =
4186 : 4 : ExecBuildProjectionInfo(returningList, econtext,
4187 : : mtstate->ps.ps_ResultTupleSlot,
4188 : : &mtstate->ps,
4189 : : RelationGetDescr(rootRelation));
4190 : : }
4191 : : }
4192 : : }
4193 : :
4194 : : /*
4195 : : * Initializes the tuple slots in a ResultRelInfo for any MERGE action.
4196 : : *
4197 : : * We mark 'projectNewInfoValid' even though the projections themselves
4198 : : * are not initialized here.
4199 : : */
4200 : : void
1659 alvherre@alvh.no-ip. 4201 : 1240 : ExecInitMergeTupleSlots(ModifyTableState *mtstate,
4202 : : ResultRelInfo *resultRelInfo)
4203 : : {
4204 : 1240 : EState *estate = mtstate->ps.state;
4205 : :
4206 [ - + ]: 1240 : Assert(!resultRelInfo->ri_projectNewInfoValid);
4207 : :
4208 : 1240 : resultRelInfo->ri_oldTupleSlot =
4209 : 1240 : table_slot_create(resultRelInfo->ri_RelationDesc,
4210 : : &estate->es_tupleTable);
4211 : 1240 : resultRelInfo->ri_newTupleSlot =
4212 : 1240 : table_slot_create(resultRelInfo->ri_RelationDesc,
4213 : : &estate->es_tupleTable);
4214 : 1240 : resultRelInfo->ri_projectNewInfoValid = true;
4215 : 1240 : }
4216 : :
4217 : : /*
4218 : : * Process BEFORE EACH STATEMENT triggers
4219 : : */
4220 : : static void
6211 tgl@sss.pgh.pa.us 4221 : 82433 : fireBSTriggers(ModifyTableState *node)
4222 : : {
3129 alvherre@alvh.no-ip. 4223 : 82433 : ModifyTable *plan = (ModifyTable *) node->ps.plan;
2184 heikki.linnakangas@i 4224 : 82433 : ResultRelInfo *resultRelInfo = node->rootResultRelInfo;
4225 : :
6211 tgl@sss.pgh.pa.us 4226 [ + + + + : 82433 : switch (node->operation)
- ]
4227 : : {
4228 : 64944 : case CMD_INSERT:
3451 rhaas@postgresql.org 4229 : 64944 : ExecBSInsertTriggers(node->ps.state, resultRelInfo);
3129 alvherre@alvh.no-ip. 4230 [ + + ]: 64936 : if (plan->onConflictAction == ONCONFLICT_UPDATE)
4175 andres@anarazel.de 4231 : 641 : ExecBSUpdateTriggers(node->ps.state,
4232 : : resultRelInfo);
6211 tgl@sss.pgh.pa.us 4233 : 64936 : break;
4234 : 8531 : case CMD_UPDATE:
3451 rhaas@postgresql.org 4235 : 8531 : ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
6211 tgl@sss.pgh.pa.us 4236 : 8531 : break;
4237 : 7992 : case CMD_DELETE:
3451 rhaas@postgresql.org 4238 : 7992 : ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
6211 tgl@sss.pgh.pa.us 4239 : 7992 : break;
1659 alvherre@alvh.no-ip. 4240 : 966 : case CMD_MERGE:
4241 [ + + ]: 966 : if (node->mt_merge_subcommands & MERGE_INSERT)
4242 : 529 : ExecBSInsertTriggers(node->ps.state, resultRelInfo);
4243 [ + + ]: 966 : if (node->mt_merge_subcommands & MERGE_UPDATE)
4244 : 638 : ExecBSUpdateTriggers(node->ps.state, resultRelInfo);
4245 [ + + ]: 966 : if (node->mt_merge_subcommands & MERGE_DELETE)
4246 : 271 : ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
4247 : 966 : break;
6211 tgl@sss.pgh.pa.us 4248 :UBC 0 : default:
4249 [ # # ]: 0 : elog(ERROR, "unknown operation");
4250 : : break;
4251 : : }
6211 tgl@sss.pgh.pa.us 4252 :CBC 82425 : }
4253 : :
4254 : : /*
4255 : : * Process AFTER EACH STATEMENT triggers
4256 : : */
4257 : : static void
3393 rhodiumtoad@postgres 4258 : 80221 : fireASTriggers(ModifyTableState *node)
4259 : : {
3129 alvherre@alvh.no-ip. 4260 : 80221 : ModifyTable *plan = (ModifyTable *) node->ps.plan;
2184 heikki.linnakangas@i 4261 : 80221 : ResultRelInfo *resultRelInfo = node->rootResultRelInfo;
4262 : :
6211 tgl@sss.pgh.pa.us 4263 [ + + + + : 80221 : switch (node->operation)
- ]
4264 : : {
4265 : 63356 : case CMD_INSERT:
3129 alvherre@alvh.no-ip. 4266 [ + + ]: 63356 : if (plan->onConflictAction == ONCONFLICT_UPDATE)
4175 andres@anarazel.de 4267 : 561 : ExecASUpdateTriggers(node->ps.state,
4268 : : resultRelInfo,
3313 tgl@sss.pgh.pa.us 4269 : 561 : node->mt_oc_transition_capture);
3393 rhodiumtoad@postgres 4270 : 63356 : ExecASInsertTriggers(node->ps.state, resultRelInfo,
4271 : 63356 : node->mt_transition_capture);
6211 tgl@sss.pgh.pa.us 4272 : 63356 : break;
4273 : 8075 : case CMD_UPDATE:
3393 rhodiumtoad@postgres 4274 : 8075 : ExecASUpdateTriggers(node->ps.state, resultRelInfo,
4275 : 8075 : node->mt_transition_capture);
6211 tgl@sss.pgh.pa.us 4276 : 8075 : break;
4277 : 7926 : case CMD_DELETE:
3393 rhodiumtoad@postgres 4278 : 7926 : ExecASDeleteTriggers(node->ps.state, resultRelInfo,
4279 : 7926 : node->mt_transition_capture);
6211 tgl@sss.pgh.pa.us 4280 : 7926 : break;
1659 alvherre@alvh.no-ip. 4281 : 864 : case CMD_MERGE:
4282 [ + + ]: 864 : if (node->mt_merge_subcommands & MERGE_DELETE)
4283 : 244 : ExecASDeleteTriggers(node->ps.state, resultRelInfo,
4284 : 244 : node->mt_transition_capture);
4285 [ + + ]: 864 : if (node->mt_merge_subcommands & MERGE_UPDATE)
4286 : 573 : ExecASUpdateTriggers(node->ps.state, resultRelInfo,
4287 : 573 : node->mt_transition_capture);
4288 [ + + ]: 864 : if (node->mt_merge_subcommands & MERGE_INSERT)
4289 : 484 : ExecASInsertTriggers(node->ps.state, resultRelInfo,
4290 : 484 : node->mt_transition_capture);
4291 : 864 : break;
6211 tgl@sss.pgh.pa.us 4292 :UBC 0 : default:
4293 [ # # ]: 0 : elog(ERROR, "unknown operation");
4294 : : break;
4295 : : }
6211 tgl@sss.pgh.pa.us 4296 :CBC 80221 : }
4297 : :
4298 : : /*
4299 : : * Set up the state needed for collecting transition tuples for AFTER
4300 : : * triggers.
4301 : : */
4302 : : static void
3393 rhodiumtoad@postgres 4303 : 82676 : ExecSetupTransitionCaptureState(ModifyTableState *mtstate, EState *estate)
4304 : : {
3129 alvherre@alvh.no-ip. 4305 : 82676 : ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
2184 heikki.linnakangas@i 4306 : 82676 : ResultRelInfo *targetRelInfo = mtstate->rootResultRelInfo;
4307 : :
4308 : : /* Check for transition tables on the directly targeted relation. */
3393 rhodiumtoad@postgres 4309 : 82676 : mtstate->mt_transition_capture =
3313 tgl@sss.pgh.pa.us 4310 : 82676 : MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
4311 : 82676 : RelationGetRelid(targetRelInfo->ri_RelationDesc),
4312 : : mtstate->operation);
3129 alvherre@alvh.no-ip. 4313 [ + + ]: 82676 : if (plan->operation == CMD_INSERT &&
4314 [ + + ]: 64953 : plan->onConflictAction == ONCONFLICT_UPDATE)
3313 tgl@sss.pgh.pa.us 4315 : 645 : mtstate->mt_oc_transition_capture =
4316 : 645 : MakeTransitionCaptureState(targetRelInfo->ri_TrigDesc,
4317 : 645 : RelationGetRelid(targetRelInfo->ri_RelationDesc),
4318 : : CMD_UPDATE);
3188 rhaas@postgresql.org 4319 : 82676 : }
4320 : :
4321 : : /*
4322 : : * ExecPrepareTupleRouting --- prepare for routing one tuple
4323 : : *
4324 : : * Determine the partition in which the tuple in slot is to be inserted,
4325 : : * and return its ResultRelInfo in *partRelInfo. The return value is
4326 : : * a slot holding the tuple of the partition rowtype.
4327 : : *
4328 : : * This also sets the transition table information in mtstate based on the
4329 : : * selected partition.
4330 : : */
4331 : : static TupleTableSlot *
3129 alvherre@alvh.no-ip. 4332 : 485841 : ExecPrepareTupleRouting(ModifyTableState *mtstate,
4333 : : EState *estate,
4334 : : PartitionTupleRouting *proute,
4335 : : ResultRelInfo *targetRelInfo,
4336 : : TupleTableSlot *slot,
4337 : : ResultRelInfo **partRelInfo)
4338 : : {
4339 : : ResultRelInfo *partrel;
4340 : : TupleConversionMap *map;
4341 : :
4342 : : /*
4343 : : * Lookup the target partition's ResultRelInfo. If ExecFindPartition does
4344 : : * not find a valid partition for the tuple in 'slot' then an error is
4345 : : * raised. An error may also be raised if the found partition is not a
4346 : : * valid target for INSERTs. This is required since a partitioned table
4347 : : * UPDATE to another partition becomes a DELETE+INSERT.
4348 : : */
2887 4349 : 485841 : partrel = ExecFindPartition(mtstate, targetRelInfo, proute, slot, estate);
4350 : :
4351 : : /*
4352 : : * If we're capturing transition tuples, we might need to convert from the
4353 : : * partition rowtype to root partitioned table's rowtype. But if there
4354 : : * are no BEFORE triggers on the partition that could change the tuple, we
4355 : : * can just remember the original unconverted tuple to avoid a needless
4356 : : * round trip conversion.
4357 : : */
3129 4358 [ + + ]: 485697 : if (mtstate->mt_transition_capture != NULL)
4359 : : {
4360 : : bool has_before_insert_row_trig;
4361 : :
2184 heikki.linnakangas@i 4362 [ + + ]: 130 : has_before_insert_row_trig = (partrel->ri_TrigDesc &&
4363 [ + + ]: 28 : partrel->ri_TrigDesc->trig_insert_before_row);
4364 : :
4365 : 102 : mtstate->mt_transition_capture->tcs_original_insert_tuple =
4366 [ + + ]: 102 : !has_before_insert_row_trig ? slot : NULL;
4367 : : }
4368 : :
4369 : : /*
4370 : : * Convert the tuple, if necessary.
4371 : : */
1410 alvherre@alvh.no-ip. 4372 : 485697 : map = ExecGetRootToChildMap(partrel, estate);
2932 andres@anarazel.de 4373 [ + + ]: 485697 : if (map != NULL)
4374 : : {
2184 heikki.linnakangas@i 4375 : 45696 : TupleTableSlot *new_slot = partrel->ri_PartitionTupleSlot;
4376 : :
2932 andres@anarazel.de 4377 : 45696 : slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
4378 : : }
4379 : :
2189 heikki.linnakangas@i 4380 : 485697 : *partRelInfo = partrel;
3129 alvherre@alvh.no-ip. 4381 : 485697 : return slot;
4382 : : }
4383 : :
4384 : : /* ----------------------------------------------------------------
4385 : : * ExecModifyTable
4386 : : *
4387 : : * Perform table modifications as required, and return RETURNING results
4388 : : * if needed.
4389 : : * ----------------------------------------------------------------
4390 : : */
4391 : : static TupleTableSlot *
3374 andres@anarazel.de 4392 : 88633 : ExecModifyTable(PlanState *pstate)
4393 : : {
4394 : 88633 : ModifyTableState *node = castNode(ModifyTableState, pstate);
4395 : : ModifyTableContext context;
6072 bruce@momjian.us 4396 : 88633 : EState *estate = node->ps.state;
4397 : 88633 : CmdType operation = node->operation;
4398 : : ResultRelInfo *resultRelInfo;
4399 : : PlanState *subplanstate;
4400 : : TupleTableSlot *slot;
4401 : : TupleTableSlot *oldSlot;
4402 : : ItemPointerData tuple_ctid;
4403 : : HeapTupleData oldtupdata;
4404 : : HeapTuple oldtuple;
4405 : : ItemPointer tupleid;
4406 : : bool tuplock;
4407 : :
3366 andres@anarazel.de 4408 [ - + ]: 88633 : CHECK_FOR_INTERRUPTS();
4409 : :
4410 : : /*
4411 : : * This should NOT get called during EvalPlanQual; we should have passed a
4412 : : * subplan tree to EvalPlanQual, instead. Use a runtime test not just
4413 : : * Assert because this condition is easy to miss in testing. (Note:
4414 : : * although ModifyTable should not get executed within an EvalPlanQual
4415 : : * operation, we do have to allow it to be initialized and shut down in
4416 : : * case it is within a CTE subplan. Hence this test must be here, not in
4417 : : * ExecInitModifyTable.)
4418 : : */
2594 4419 [ - + ]: 88633 : if (estate->es_epq_active != NULL)
5371 tgl@sss.pgh.pa.us 4420 [ # # ]:UBC 0 : elog(ERROR, "ModifyTable should not be called during EvalPlanQual");
4421 : :
4422 : : /*
4423 : : * If we've already completed processing, don't try to do more. We need
4424 : : * this test because ExecPostprocessPlan might call us an extra time, and
4425 : : * our subplan's nodes aren't necessarily robust against being called
4426 : : * extra times.
4427 : : */
5708 tgl@sss.pgh.pa.us 4428 [ + + ]:CBC 88633 : if (node->mt_done)
4429 : 554 : return NULL;
4430 : :
4431 : : /*
4432 : : * On first call, fire BEFORE STATEMENT triggers before proceeding.
4433 : : */
6211 4434 [ + + ]: 88079 : if (node->fireBSTriggers)
4435 : : {
4436 : 82433 : fireBSTriggers(node);
4437 : 82425 : node->fireBSTriggers = false;
4438 : : }
4439 : :
4440 : : /* Preload local variables */
2021 4441 : 88071 : resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;
4442 : 88071 : subplanstate = outerPlanState(node);
4443 : :
4444 : : /* Set global context */
1670 alvherre@alvh.no-ip. 4445 : 88071 : context.mtstate = node;
4446 : 88071 : context.epqstate = &node->mt_epqstate;
4447 : 88071 : context.estate = estate;
4448 : :
4449 : : /*
4450 : : * Fetch rows from subplan, and execute the required table modification
4451 : : * for each row.
4452 : : */
4453 : : for (;;)
4454 : : {
4455 : : /*
4456 : : * Reset the per-output-tuple exprcontext. This is needed because
4457 : : * triggers expect to use that context as workspace. It's a bit ugly
4458 : : * to do this below the top level of the plan, however. We might need
4459 : : * to rethink this later.
4460 : : */
5899 tgl@sss.pgh.pa.us 4461 [ + + ]: 11357183 : ResetPerTupleExprContext(estate);
4462 : :
4463 : : /*
4464 : : * Reset per-tuple memory context used for processing on conflict and
4465 : : * returning clauses, to free any expression evaluation storage
4466 : : * allocated in the previous cycle.
4467 : : */
2890 andres@anarazel.de 4468 [ + + ]: 11357183 : if (pstate->ps_ExprContext)
4469 : 2245674 : ResetExprContext(pstate->ps_ExprContext);
4470 : :
4471 : : /*
4472 : : * If there is a pending MERGE ... WHEN NOT MATCHED [BY TARGET] action
4473 : : * to execute, do so now --- see the comments in ExecMerge().
4474 : : */
926 dean.a.rasheed@gmail 4475 [ + + ]: 11357183 : if (node->mt_merge_pending_not_matched != NULL)
4476 : : {
4477 : 2 : context.planSlot = node->mt_merge_pending_not_matched;
634 4478 : 2 : context.cpDeletedSlot = NULL;
4479 : :
926 4480 : 2 : slot = ExecMergeNotMatched(&context, node->resultRelInfo,
4481 : 2 : node->canSetTag);
4482 : :
4483 : : /* Clear the pending action */
4484 : 2 : node->mt_merge_pending_not_matched = NULL;
4485 : :
4486 : : /*
4487 : : * If we got a RETURNING result, return it to the caller. We'll
4488 : : * continue the work on next call.
4489 : : */
4490 [ + - ]: 2 : if (slot)
4491 : 2 : return slot;
4492 : :
926 dean.a.rasheed@gmail 4493 :UBC 0 : continue; /* continue with the next tuple */
4494 : : }
4495 : :
4496 : : /* Fetch the next row from subplan */
1636 alvherre@alvh.no-ip. 4497 :CBC 11357181 : context.planSlot = ExecProcNode(subplanstate);
634 dean.a.rasheed@gmail 4498 : 11356899 : context.cpDeletedSlot = NULL;
4499 : :
4500 : : /* No more tuples to process? */
1636 alvherre@alvh.no-ip. 4501 [ + + + + ]: 11356899 : if (TupIsNull(context.planSlot))
4502 : : break;
4503 : :
4504 : : /*
4505 : : * When there are multiple result relations, each tuple contains a
4506 : : * junk column that gives the OID of the rel from which it came.
4507 : : * Extract it and select the correct result relation.
4508 : : */
2021 tgl@sss.pgh.pa.us 4509 [ + + ]: 11276677 : if (AttributeNumberIsValid(node->mt_resultOidAttno))
4510 : : {
4511 : : Datum datum;
4512 : : bool isNull;
4513 : : Oid resultoid;
4514 : :
1636 alvherre@alvh.no-ip. 4515 : 3362 : datum = ExecGetJunkAttribute(context.planSlot, node->mt_resultOidAttno,
4516 : : &isNull);
2021 tgl@sss.pgh.pa.us 4517 [ + + ]: 3362 : if (isNull)
4518 : : {
4519 : : /*
4520 : : * For commands other than MERGE, any tuples having InvalidOid
4521 : : * for tableoid are errors. For MERGE, we may need to handle
4522 : : * them as WHEN NOT MATCHED clauses if any, so do that.
4523 : : *
4524 : : * Note that we use the node's toplevel resultRelInfo, not any
4525 : : * specific partition's.
4526 : : */
1659 alvherre@alvh.no-ip. 4527 [ + - ]: 340 : if (operation == CMD_MERGE)
4528 : : {
1636 4529 : 340 : EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
4530 : :
939 dean.a.rasheed@gmail 4531 : 340 : slot = ExecMerge(&context, node->resultRelInfo,
4532 : 340 : NULL, NULL, node->canSetTag);
4533 : :
4534 : : /*
4535 : : * If we got a RETURNING result, return it to the caller.
4536 : : * We'll continue the work on next call.
4537 : : */
4538 [ + + ]: 332 : if (slot)
4539 : 27 : return slot;
4540 : :
4541 : 305 : continue; /* continue with the next tuple */
4542 : : }
4543 : :
2021 tgl@sss.pgh.pa.us 4544 [ # # ]:UBC 0 : elog(ERROR, "tableoid is NULL");
4545 : : }
2021 tgl@sss.pgh.pa.us 4546 :CBC 3022 : resultoid = DatumGetObjectId(datum);
4547 : :
4548 : : /* If it's not the same as last time, we need to locate the rel */
4549 [ + + ]: 3022 : if (resultoid != node->mt_lastResultOid)
2015 4550 : 2078 : resultRelInfo = ExecLookupResultRelByOid(node, resultoid,
4551 : : false, true);
4552 : : }
4553 : :
4554 : : /*
4555 : : * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do
4556 : : * here is compute the RETURNING expressions.
4557 : : */
3860 rhaas@postgresql.org 4558 [ + + ]: 11276337 : if (resultRelInfo->ri_usesFdwDirectModify)
4559 : : {
4560 [ - + ]: 349 : Assert(resultRelInfo->ri_projectReturning);
4561 : :
4562 : : /*
4563 : : * A scan slot containing the data that was actually inserted,
4564 : : * updated or deleted has already been made available to
4565 : : * ExecProcessReturning by IterateDirectModify, so no need to
4566 : : * provide it here. The individual old and new slots are not
4567 : : * needed, since direct-modify is disabled if the RETURNING list
4568 : : * refers to OLD/NEW values.
4569 : : */
634 dean.a.rasheed@gmail 4570 [ + - - + ]: 349 : Assert((resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_OLD) == 0 &&
4571 : : (resultRelInfo->ri_projectReturning->pi_state.flags & EEO_FLAG_HAS_NEW) == 0);
4572 : :
242 4573 : 349 : slot = ExecProcessReturning(&context, resultRelInfo,
4574 : : operation == CMD_DELETE,
4575 : : NULL, NULL, context.planSlot);
4576 : :
3860 rhaas@postgresql.org 4577 : 349 : return slot;
4578 : : }
4579 : :
1636 alvherre@alvh.no-ip. 4580 : 11275988 : EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
4581 : 11275988 : slot = context.planSlot;
4582 : :
3241 tgl@sss.pgh.pa.us 4583 : 11275988 : tupleid = NULL;
4586 noah@leadboat.com 4584 : 11275988 : oldtuple = NULL;
4585 : :
4586 : : /*
4587 : : * For UPDATE/DELETE/MERGE, fetch the row identity info for the tuple
4588 : : * to be updated/deleted/merged. For a heap relation, that's a TID;
4589 : : * otherwise we may have a wholerow junk attr that carries the old
4590 : : * tuple in toto. Keep this in step with the part of
4591 : : * ExecInitModifyTable that sets up ri_RowIdAttNo.
4592 : : */
1659 alvherre@alvh.no-ip. 4593 [ + + + + : 11275988 : if (operation == CMD_UPDATE || operation == CMD_DELETE ||
+ + ]
4594 : : operation == CMD_MERGE)
4595 : : {
4596 : : char relkind;
4597 : : Datum datum;
4598 : : bool isNull;
4599 : :
2021 tgl@sss.pgh.pa.us 4600 : 3243904 : relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
4601 [ + + + + ]: 3243904 : if (relkind == RELKIND_RELATION ||
4602 [ + + ]: 340 : relkind == RELKIND_MATVIEW ||
4603 : : relkind == RELKIND_PARTITIONED_TABLE)
4604 : : {
4605 : : /*
4606 : : * ri_RowIdAttNo refers to a ctid attribute. See the comment
4607 : : * in ExecInitModifyTable().
4608 : : */
262 amitlan@postgresql.o 4609 [ - + - - ]: 3243568 : Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo) ||
4610 : : relkind == RELKIND_PARTITIONED_TABLE);
2021 tgl@sss.pgh.pa.us 4611 : 3243568 : datum = ExecGetJunkAttribute(slot,
4612 : 3243568 : resultRelInfo->ri_RowIdAttNo,
4613 : : &isNull);
4614 : :
4615 : : /*
4616 : : * For commands other than MERGE, any tuples having a null row
4617 : : * identifier are errors. For MERGE, we may need to handle
4618 : : * them as WHEN NOT MATCHED clauses if any, so do that.
4619 : : *
4620 : : * Note that we use the node's toplevel resultRelInfo, not any
4621 : : * specific partition's.
4622 : : */
4623 [ + + ]: 3243568 : if (isNull)
4624 : : {
1659 alvherre@alvh.no-ip. 4625 [ + - ]: 1425 : if (operation == CMD_MERGE)
4626 : : {
1636 4627 : 1425 : EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
4628 : :
939 dean.a.rasheed@gmail 4629 : 1425 : slot = ExecMerge(&context, node->resultRelInfo,
4630 : 1425 : NULL, NULL, node->canSetTag);
4631 : :
4632 : : /*
4633 : : * If we got a RETURNING result, return it to the
4634 : : * caller. We'll continue the work on next call.
4635 : : */
4636 [ + + ]: 1398 : if (slot)
4637 : 92 : return slot;
4638 : :
4639 : 1334 : continue; /* continue with the next tuple */
4640 : : }
4641 : :
2021 tgl@sss.pgh.pa.us 4642 [ # # ]:UBC 0 : elog(ERROR, "ctid is NULL");
4643 : : }
4644 : :
2021 tgl@sss.pgh.pa.us 4645 :CBC 3242143 : tupleid = (ItemPointer) DatumGetPointer(datum);
4646 : 3242143 : tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
4647 : 3242143 : tupleid = &tuple_ctid;
4648 : : }
4649 : :
4650 : : /*
4651 : : * Use the wholerow attribute, when available, to reconstruct the
4652 : : * old relation tuple. The old tuple serves one or both of two
4653 : : * purposes: 1) it serves as the OLD tuple for row triggers, 2) it
4654 : : * provides values for any unchanged columns for the NEW tuple of
4655 : : * an UPDATE, because the subplan does not produce all the columns
4656 : : * of the target table.
4657 : : *
4658 : : * Note that the wholerow attribute does not carry system columns,
4659 : : * so foreign table triggers miss seeing those, except that we
4660 : : * know enough here to set t_tableOid. Quite separately from
4661 : : * this, the FDW may fetch its own junk attrs to identify the row.
4662 : : *
4663 : : * Other relevant relkinds, currently limited to views, always
4664 : : * have a wholerow attribute.
4665 : : */
4666 [ + + ]: 336 : else if (AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
4667 : : {
4668 : 321 : datum = ExecGetJunkAttribute(slot,
4669 : 321 : resultRelInfo->ri_RowIdAttNo,
4670 : : &isNull);
4671 : :
4672 : : /*
4673 : : * For commands other than MERGE, any tuples having a null row
4674 : : * identifier are errors. For MERGE, we may need to handle
4675 : : * them as WHEN NOT MATCHED clauses if any, so do that.
4676 : : *
4677 : : * Note that we use the node's toplevel resultRelInfo, not any
4678 : : * specific partition's.
4679 : : */
4680 [ + + ]: 321 : if (isNull)
4681 : : {
956 dean.a.rasheed@gmail 4682 [ + - ]: 32 : if (operation == CMD_MERGE)
4683 : : {
4684 : 32 : EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot);
4685 : :
939 4686 : 32 : slot = ExecMerge(&context, node->resultRelInfo,
4687 : 32 : NULL, NULL, node->canSetTag);
4688 : :
4689 : : /*
4690 : : * If we got a RETURNING result, return it to the
4691 : : * caller. We'll continue the work on next call.
4692 : : */
4693 [ + + ]: 28 : if (slot)
4694 : 8 : return slot;
4695 : :
4696 : 20 : continue; /* continue with the next tuple */
4697 : : }
4698 : :
2021 tgl@sss.pgh.pa.us 4699 [ # # ]:UBC 0 : elog(ERROR, "wholerow is NULL");
4700 : : }
4701 : :
2021 tgl@sss.pgh.pa.us 4702 :CBC 289 : oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
4703 : 289 : oldtupdata.t_len =
4704 : 289 : HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
4705 : 289 : ItemPointerSetInvalid(&(oldtupdata.t_self));
4706 : : /* Historically, view triggers see invalid t_tableOid. */
4707 : 289 : oldtupdata.t_tableOid =
4708 [ + + ]: 289 : (relkind == RELKIND_VIEW) ? InvalidOid :
4709 : 107 : RelationGetRelid(resultRelInfo->ri_RelationDesc);
4710 : :
4711 : 289 : oldtuple = &oldtupdata;
4712 : : }
4713 : : else
4714 : : {
4715 : : /* Only foreign tables are allowed to omit a row-ID attr */
4716 [ - + ]: 15 : Assert(relkind == RELKIND_FOREIGN_TABLE);
4717 : : }
4718 : : }
4719 : :
6211 4720 [ + + + + : 11274531 : switch (operation)
- ]
4721 : : {
4722 : 8032084 : case CMD_INSERT:
4723 : : /* Initialize projection info if first time for this table */
2015 4724 [ + + ]: 8032084 : if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
4725 : 64193 : ExecInitInsertProjection(node, resultRelInfo);
1636 alvherre@alvh.no-ip. 4726 : 8032084 : slot = ExecGetInsertNewTuple(resultRelInfo, context.planSlot);
1670 4727 : 8032084 : slot = ExecInsert(&context, resultRelInfo, slot,
1667 4728 : 8032084 : node->canSetTag, NULL, NULL);
6211 tgl@sss.pgh.pa.us 4729 : 8030629 : break;
4730 : :
4731 : 2222932 : case CMD_UPDATE:
748 noah@leadboat.com 4732 : 2222932 : tuplock = false;
4733 : :
4734 : : /* Initialize projection info if first time for this table */
2015 tgl@sss.pgh.pa.us 4735 [ + + ]: 2222932 : if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
4736 : 8322 : ExecInitUpdateProjection(node, resultRelInfo);
4737 : :
4738 : : /*
4739 : : * Make the new tuple by combining plan's output tuple with
4740 : : * the old tuple being updated.
4741 : : */
2021 4742 : 2222932 : oldSlot = resultRelInfo->ri_oldTupleSlot;
4743 [ + + ]: 2222932 : if (oldtuple != NULL)
4744 : : {
748 noah@leadboat.com 4745 [ - + ]: 181 : Assert(!resultRelInfo->ri_needLockTagTuple);
4746 : : /* Use the wholerow junk attr as the old tuple. */
2021 tgl@sss.pgh.pa.us 4747 : 181 : ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
4748 : : }
4749 : : else
4750 : : {
4751 : : /* Fetch the most recent version of old tuple. */
4752 : 2222751 : Relation relation = resultRelInfo->ri_RelationDesc;
4753 : :
748 noah@leadboat.com 4754 [ + + ]: 2222751 : if (resultRelInfo->ri_needLockTagTuple)
4755 : : {
4756 : 15338 : LockTuple(relation, tupleid, InplaceUpdateTupleLock);
4757 : 15338 : tuplock = true;
4758 : : }
2021 tgl@sss.pgh.pa.us 4759 [ - + ]: 2222751 : if (!table_tuple_fetch_row_version(relation, tupleid,
4760 : : SnapshotAny,
4761 : : oldSlot))
2021 tgl@sss.pgh.pa.us 4762 [ # # ]:UBC 0 : elog(ERROR, "failed to fetch tuple being updated");
4763 : : }
1309 dean.a.rasheed@gmail 4764 :CBC 2222932 : slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot,
4765 : : oldSlot);
4766 : :
4767 : : /* Now apply the update. */
1670 alvherre@alvh.no-ip. 4768 : 2222932 : slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
634 dean.a.rasheed@gmail 4769 : 2222932 : oldSlot, slot, node->canSetTag);
748 noah@leadboat.com 4770 [ + + ]: 2222611 : if (tuplock)
4771 : 15338 : UnlockTuple(resultRelInfo->ri_RelationDesc, tupleid,
4772 : : InplaceUpdateTupleLock);
6211 tgl@sss.pgh.pa.us 4773 : 2222611 : break;
4774 : :
4775 : 1011204 : case CMD_DELETE:
1670 alvherre@alvh.no-ip. 4776 : 1011204 : slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple,
914 akorotkov@postgresql 4777 : 1011204 : true, false, node->canSetTag, NULL, NULL, NULL);
6211 tgl@sss.pgh.pa.us 4778 : 1011167 : break;
4779 : :
1659 alvherre@alvh.no-ip. 4780 : 8311 : case CMD_MERGE:
956 dean.a.rasheed@gmail 4781 : 8311 : slot = ExecMerge(&context, resultRelInfo, tupleid, oldtuple,
4782 : 8311 : node->canSetTag);
1659 alvherre@alvh.no-ip. 4783 : 8249 : break;
4784 : :
6211 tgl@sss.pgh.pa.us 4785 :UBC 0 : default:
4786 [ # # ]: 0 : elog(ERROR, "unknown operation");
4787 : : break;
4788 : : }
4789 : :
4790 : : /*
4791 : : * If we got a RETURNING result, return it to caller. We'll continue
4792 : : * the work on next call.
4793 : : */
6211 tgl@sss.pgh.pa.us 4794 [ + + ]:CBC 11272656 : if (slot)
4795 : 5183 : return slot;
4796 : : }
4797 : :
4798 : : /*
4799 : : * Insert remaining tuples for batch insert.
4800 : : */
1417 efujita@postgresql.o 4801 [ + + ]: 80222 : if (estate->es_insert_pending_result_relations != NIL)
4802 : 13 : ExecPendingInserts(estate);
4803 : :
4804 : : /*
4805 : : * We're done, but fire AFTER STATEMENT triggers before exiting.
4806 : : */
6211 tgl@sss.pgh.pa.us 4807 : 80221 : fireASTriggers(node);
4808 : :
5708 4809 : 80221 : node->mt_done = true;
4810 : :
6211 4811 : 80221 : return NULL;
4812 : : }
4813 : :
4814 : : /*
4815 : : * ExecLookupResultRelByOid
4816 : : * If the table with given OID is among the result relations to be
4817 : : * updated by the given ModifyTable node, return its ResultRelInfo.
4818 : : *
4819 : : * If not found, return NULL if missing_ok, else raise error.
4820 : : *
4821 : : * If update_cache is true, then upon successful lookup, update the node's
4822 : : * one-element cache. ONLY ExecModifyTable may pass true for this.
4823 : : */
4824 : : ResultRelInfo *
2015 4825 : 7560 : ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
4826 : : bool missing_ok, bool update_cache)
4827 : : {
4828 [ + + ]: 7560 : if (node->mt_resultOidHash)
4829 : : {
4830 : : /* Use the pre-built hash table to locate the rel */
4831 : : MTTargetRelLookup *mtlookup;
4832 : :
4833 : : mtlookup = (MTTargetRelLookup *)
4834 : 741 : hash_search(node->mt_resultOidHash, &resultoid, HASH_FIND, NULL);
4835 [ + - ]: 741 : if (mtlookup)
4836 : : {
4837 [ + + ]: 741 : if (update_cache)
4838 : : {
4839 : 541 : node->mt_lastResultOid = resultoid;
4840 : 541 : node->mt_lastResultIndex = mtlookup->relationIndex;
4841 : : }
4842 : 741 : return node->resultRelInfo + mtlookup->relationIndex;
4843 : : }
4844 : : }
4845 : : else
4846 : : {
4847 : : /* With few target rels, just search the ResultRelInfo array */
4848 [ + + ]: 12455 : for (int ndx = 0; ndx < node->mt_nrels; ndx++)
4849 : : {
4850 : 7307 : ResultRelInfo *rInfo = node->resultRelInfo + ndx;
4851 : :
4852 [ + + ]: 7307 : if (RelationGetRelid(rInfo->ri_RelationDesc) == resultoid)
4853 : : {
4854 [ + + ]: 1671 : if (update_cache)
4855 : : {
4856 : 1537 : node->mt_lastResultOid = resultoid;
4857 : 1537 : node->mt_lastResultIndex = ndx;
4858 : : }
4859 : 1671 : return rInfo;
4860 : : }
4861 : : }
4862 : : }
4863 : :
4864 [ - + ]: 5148 : if (!missing_ok)
2015 tgl@sss.pgh.pa.us 4865 [ # # ]:UBC 0 : elog(ERROR, "incorrect result relation OID %u", resultoid);
2015 tgl@sss.pgh.pa.us 4866 :CBC 5148 : return NULL;
4867 : : }
4868 : :
4869 : : /* ----------------------------------------------------------------
4870 : : * ExecInitModifyTable
4871 : : * ----------------------------------------------------------------
4872 : : */
4873 : : ModifyTableState *
6211 4874 : 83349 : ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
4875 : : {
4876 : : ModifyTableState *mtstate;
2021 4877 : 83349 : Plan *subplan = outerPlan(node);
6211 4878 : 83349 : CmdType operation = node->operation;
572 amitlan@postgresql.o 4879 : 83349 : int total_nrels = list_length(node->resultRelations);
4880 : : int nrels;
612 4881 : 83349 : List *resultRelations = NIL;
4882 : 83349 : List *withCheckOptionLists = NIL;
4883 : 83349 : List *returningLists = NIL;
4884 : 83349 : List *updateColnosLists = NIL;
602 4885 : 83349 : List *mergeActionLists = NIL;
4886 : 83349 : List *mergeJoinConditions = NIL;
111 4887 : 83349 : List *fdwPrivLists = NIL;
4888 : 83349 : Bitmapset *fdwDirectModifyPlans = NULL;
4889 : : ResultRelInfo *resultRelInfo;
4890 : : List *arowmarks;
4891 : : ListCell *l;
4892 : : int i;
4893 : : Relation rel;
4894 : :
4895 : : /* check for unsupported flags */
6211 tgl@sss.pgh.pa.us 4896 [ - + ]: 83349 : Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
4897 : :
4898 : : /*
4899 : : * Only consider unpruned relations for initializing their ResultRelInfo
4900 : : * struct and other fields such as withCheckOptions, etc.
4901 : : *
4902 : : * Note: We must avoid pruning every result relation. This is important
4903 : : * for MERGE, since even if every result relation is pruned from the
4904 : : * subplan, there might still be NOT MATCHED rows, for which there may be
4905 : : * INSERT actions to perform. To allow these actions to be found, at
4906 : : * least one result relation must be kept. Also, when inserting into a
4907 : : * partitioned table, ExecInitPartitionInfo() needs a ResultRelInfo struct
4908 : : * as a reference for building the ResultRelInfo of the target partition.
4909 : : * In either case, it doesn't matter which result relation is kept, so we
4910 : : * just keep the first one, if all others have been pruned. See also,
4911 : : * ExecDoInitialPruning(), which ensures that this first result relation
4912 : : * has been locked.
4913 : : */
612 amitlan@postgresql.o 4914 : 83349 : i = 0;
4915 [ + - + + : 168356 : foreach(l, node->resultRelations)
+ + ]
4916 : : {
4917 : 85007 : Index rti = lfirst_int(l);
4918 : : bool keep_rel;
4919 : :
572 4920 : 85007 : keep_rel = bms_is_member(rti, estate->es_unpruned_relids);
4921 [ + + + + : 85007 : if (!keep_rel && i == total_nrels - 1 && resultRelations == NIL)
+ + ]
4922 : : {
4923 : : /* all result relations pruned; keep the first one */
4924 : 32 : keep_rel = true;
4925 : 32 : rti = linitial_int(node->resultRelations);
4926 : 32 : i = 0;
4927 : : }
4928 : :
4929 [ + + ]: 85007 : if (keep_rel)
4930 : : {
111 4931 : 84946 : List *fdwPrivList = (List *) list_nth(node->fdwPrivLists, i);
4932 : :
612 4933 : 84946 : resultRelations = lappend_int(resultRelations, rti);
4934 [ + + ]: 84946 : if (node->withCheckOptionLists)
4935 : : {
4936 : 1052 : List *withCheckOptions = list_nth_node(List,
4937 : : node->withCheckOptionLists,
4938 : : i);
4939 : :
4940 : 1052 : withCheckOptionLists = lappend(withCheckOptionLists, withCheckOptions);
4941 : : }
4942 [ + + ]: 84946 : if (node->returningLists)
4943 : : {
4944 : 3881 : List *returningList = list_nth_node(List,
4945 : : node->returningLists,
4946 : : i);
4947 : :
4948 : 3881 : returningLists = lappend(returningLists, returningList);
4949 : : }
4950 [ + + ]: 84946 : if (node->updateColnosLists)
4951 : : {
4952 : 10110 : List *updateColnosList = list_nth(node->updateColnosLists, i);
4953 : :
4954 : 10110 : updateColnosLists = lappend(updateColnosLists, updateColnosList);
4955 : : }
602 4956 [ + + ]: 84946 : if (node->mergeActionLists)
4957 : : {
4958 : 1233 : List *mergeActionList = list_nth(node->mergeActionLists, i);
4959 : :
4960 : 1233 : mergeActionLists = lappend(mergeActionLists, mergeActionList);
4961 : : }
4962 [ + + ]: 84946 : if (node->mergeJoinConditions)
4963 : : {
4964 : 1233 : List *mergeJoinCondition = list_nth(node->mergeJoinConditions, i);
4965 : :
4966 : 1233 : mergeJoinConditions = lappend(mergeJoinConditions, mergeJoinCondition);
4967 : : }
4968 : :
4969 : : /*
4970 : : * fdwPrivLists/fdwDirectModifyPlans are re-indexed to match
4971 : : * resultRelations
4972 : : */
111 4973 : 84946 : fdwPrivLists = lappend(fdwPrivLists, fdwPrivList);
4974 [ + + ]: 84946 : if (bms_is_member(i, node->fdwDirectModifyPlans))
4975 : : {
4976 : 108 : int new_index = list_length(resultRelations) - 1;
4977 : :
4978 : 108 : fdwDirectModifyPlans = bms_add_member(fdwDirectModifyPlans,
4979 : : new_index);
4980 : : }
4981 : : }
612 4982 : 85007 : i++;
4983 : : }
4984 : 83349 : nrels = list_length(resultRelations);
572 4985 [ - + ]: 83349 : Assert(nrels > 0);
4986 : :
4987 : : /*
4988 : : * create state structure
4989 : : */
6211 tgl@sss.pgh.pa.us 4990 : 83349 : mtstate = makeNode(ModifyTableState);
4991 : 83349 : mtstate->ps.plan = (Plan *) node;
4992 : 83349 : mtstate->ps.state = estate;
3374 andres@anarazel.de 4993 : 83349 : mtstate->ps.ExecProcNode = ExecModifyTable;
4994 : :
5708 tgl@sss.pgh.pa.us 4995 : 83349 : mtstate->operation = operation;
4996 : 83349 : mtstate->canSetTag = node->canSetTag;
4997 : 83349 : mtstate->mt_done = false;
4998 : :
2021 4999 : 83349 : mtstate->mt_nrels = nrels;
306 michael@paquier.xyz 5000 : 83349 : mtstate->resultRelInfo = palloc_array(ResultRelInfo, nrels);
5001 : :
926 dean.a.rasheed@gmail 5002 : 83349 : mtstate->mt_merge_pending_not_matched = NULL;
1659 alvherre@alvh.no-ip. 5003 : 83349 : mtstate->mt_merge_inserted = 0;
5004 : 83349 : mtstate->mt_merge_updated = 0;
5005 : 83349 : mtstate->mt_merge_deleted = 0;
612 amitlan@postgresql.o 5006 : 83349 : mtstate->mt_updateColnosLists = updateColnosLists;
602 5007 : 83349 : mtstate->mt_mergeActionLists = mergeActionLists;
5008 : 83349 : mtstate->mt_mergeJoinConditions = mergeJoinConditions;
111 5009 : 83349 : mtstate->mt_fdwPrivLists = fdwPrivLists;
5010 : :
5011 : : /*----------
5012 : : * Resolve the target relation. This is the same as:
5013 : : *
5014 : : * - the relation for which we will fire FOR STATEMENT triggers,
5015 : : * - the relation into whose tuple format all captured transition tuples
5016 : : * must be converted, and
5017 : : * - the root partitioned table used for tuple routing.
5018 : : *
5019 : : * If it's a partitioned or inherited table, the root partition or
5020 : : * appendrel RTE doesn't appear elsewhere in the plan and its RT index is
5021 : : * given explicitly in node->rootRelation. Otherwise, the target relation
5022 : : * is the sole relation in the node->resultRelations list and, since it can
5023 : : * never be pruned, also in the resultRelations list constructed above.
5024 : : *----------
5025 : : */
2190 heikki.linnakangas@i 5026 [ + + ]: 83349 : if (node->rootRelation > 0)
5027 : : {
612 amitlan@postgresql.o 5028 [ - + ]: 1872 : Assert(bms_is_member(node->rootRelation, estate->es_unpruned_relids));
2190 heikki.linnakangas@i 5029 : 1872 : mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
5030 : 1872 : ExecInitResultRelation(estate, mtstate->rootResultRelInfo,
5031 : : node->rootRelation);
5032 : : }
5033 : : else
5034 : : {
1084 tgl@sss.pgh.pa.us 5035 [ - + ]: 81477 : Assert(list_length(node->resultRelations) == 1);
602 amitlan@postgresql.o 5036 [ - + ]: 81477 : Assert(list_length(resultRelations) == 1);
2184 heikki.linnakangas@i 5037 : 81477 : mtstate->rootResultRelInfo = mtstate->resultRelInfo;
5038 : 81477 : ExecInitResultRelation(estate, mtstate->resultRelInfo,
602 amitlan@postgresql.o 5039 : 81477 : linitial_int(resultRelations));
5040 : : }
5041 : :
5042 : : /* set up epqstate with dummy subplan data for the moment */
1242 tgl@sss.pgh.pa.us 5043 : 83349 : EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL,
5044 : : node->epqParam, resultRelations);
6211 5045 : 83349 : mtstate->fireBSTriggers = true;
5046 : :
5047 : : /*
5048 : : * Build state for collecting transition tuples. This requires having a
5049 : : * valid trigger query context, so skip it in explain-only mode.
5050 : : */
2184 heikki.linnakangas@i 5051 [ + + ]: 83349 : if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
5052 : 82676 : ExecSetupTransitionCaptureState(mtstate, estate);
5053 : :
5054 : : /*
5055 : : * Open all the result relations and initialize the ResultRelInfo structs.
5056 : : * (But root relation was initialized above, if it's part of the array.)
5057 : : * We must do this before initializing the subplan, because direct-modify
5058 : : * FDWs expect their ResultRelInfos to be available.
5059 : : */
5708 tgl@sss.pgh.pa.us 5060 : 83349 : resultRelInfo = mtstate->resultRelInfo;
6211 5061 : 83349 : i = 0;
612 amitlan@postgresql.o 5062 [ + - + + : 168064 : foreach(l, resultRelations)
+ + ]
5063 : : {
2190 heikki.linnakangas@i 5064 : 84942 : Index resultRelation = lfirst_int(l);
956 dean.a.rasheed@gmail 5065 : 84942 : List *mergeActions = NIL;
5066 : :
602 amitlan@postgresql.o 5067 [ + + ]: 84942 : if (mergeActionLists)
5068 : 1233 : mergeActions = list_nth(mergeActionLists, i);
5069 : :
2184 heikki.linnakangas@i 5070 [ + + ]: 84942 : if (resultRelInfo != mtstate->rootResultRelInfo)
5071 : : {
5072 : 3465 : ExecInitResultRelation(estate, resultRelInfo, resultRelation);
5073 : :
5074 : : /*
5075 : : * For child result relations, store the root result relation
5076 : : * pointer. We do so for the convenience of places that want to
5077 : : * look at the query's original target relation but don't have the
5078 : : * mtstate handy.
5079 : : */
2015 tgl@sss.pgh.pa.us 5080 : 3465 : resultRelInfo->ri_RootResultRelInfo = mtstate->rootResultRelInfo;
5081 : : }
5082 : :
5083 : : /* Initialize the usesFdwDirectModify flag */
1670 alvherre@alvh.no-ip. 5084 : 84942 : resultRelInfo->ri_usesFdwDirectModify =
111 amitlan@postgresql.o 5085 : 84942 : bms_is_member(i, fdwDirectModifyPlans);
5086 : :
5087 : : /*
5088 : : * Verify result relation is a valid target for the current operation
5089 : : */
403 dean.a.rasheed@gmail 5090 : 84942 : CheckValidResultRel(resultRelInfo, operation, node->onConflictAction,
5091 : : mergeActions);
5092 : :
2021 tgl@sss.pgh.pa.us 5093 : 84715 : resultRelInfo++;
5094 : 84715 : i++;
5095 : : }
5096 : :
5097 : : /*
5098 : : * Now we may initialize the subplan.
5099 : : */
5100 : 83122 : outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
5101 : :
5102 : : /*
5103 : : * Do additional per-result-relation initialization.
5104 : : */
5105 [ + + ]: 167815 : for (i = 0; i < nrels; i++)
5106 : : {
5107 : 84693 : resultRelInfo = &mtstate->resultRelInfo[i];
5108 : :
5109 : : /* Let FDWs init themselves for foreign-table result rels */
3860 rhaas@postgresql.org 5110 [ + + ]: 84693 : if (!resultRelInfo->ri_usesFdwDirectModify &&
5111 [ + + ]: 84585 : resultRelInfo->ri_FdwRoutine != NULL &&
4964 tgl@sss.pgh.pa.us 5112 [ + - ]: 174 : resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
5113 : : {
111 amitlan@postgresql.o 5114 : 174 : List *fdw_private = (List *) list_nth(fdwPrivLists, i);
5115 : :
4964 tgl@sss.pgh.pa.us 5116 : 174 : resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
5117 : : resultRelInfo,
5118 : : fdw_private,
5119 : : i,
5120 : : eflags);
5121 : : }
5122 : :
5123 : : /*
5124 : : * For UPDATE/DELETE/MERGE, find the appropriate junk attr now, either
5125 : : * a 'ctid' or 'wholerow' attribute depending on relkind. For foreign
5126 : : * tables, the FDW might have created additional junk attr(s), but
5127 : : * those are no concern of ours.
5128 : : */
1659 alvherre@alvh.no-ip. 5129 [ + + + + : 84693 : if (operation == CMD_UPDATE || operation == CMD_DELETE ||
+ + ]
5130 : : operation == CMD_MERGE)
5131 : : {
5132 : : char relkind;
5133 : :
2015 tgl@sss.pgh.pa.us 5134 : 19555 : relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
5135 [ + + + + ]: 19555 : if (relkind == RELKIND_RELATION ||
5136 [ + + ]: 413 : relkind == RELKIND_MATVIEW ||
5137 : : relkind == RELKIND_PARTITIONED_TABLE)
5138 : : {
5139 : 19172 : resultRelInfo->ri_RowIdAttNo =
5140 : 19172 : ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
5141 : :
5142 : : /*
5143 : : * For heap relations, a ctid junk attribute must be present.
5144 : : * Partitioned tables should only appear here when all leaf
5145 : : * partitions were pruned, in which case no rows can be
5146 : : * produced and ctid is not needed.
5147 : : */
262 amitlan@postgresql.o 5148 [ + + ]: 19172 : if (relkind == RELKIND_PARTITIONED_TABLE)
5149 [ - + ]: 30 : Assert(nrels == 1);
5150 [ - + ]: 19142 : else if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
2015 tgl@sss.pgh.pa.us 5151 [ # # ]:UBC 0 : elog(ERROR, "could not find junk ctid column");
5152 : : }
2015 tgl@sss.pgh.pa.us 5153 [ + + ]:CBC 383 : else if (relkind == RELKIND_FOREIGN_TABLE)
5154 : : {
5155 : : /*
5156 : : * We don't support MERGE with foreign tables for now. (It's
5157 : : * problematic because the implementation uses CTID.)
5158 : : */
1659 alvherre@alvh.no-ip. 5159 [ - + ]: 193 : Assert(operation != CMD_MERGE);
5160 : :
5161 : : /*
5162 : : * When there is a row-level trigger, there should be a
5163 : : * wholerow attribute. We also require it to be present in
5164 : : * UPDATE and MERGE, so we can get the values of unchanged
5165 : : * columns.
5166 : : */
2015 tgl@sss.pgh.pa.us 5167 : 193 : resultRelInfo->ri_RowIdAttNo =
5168 : 193 : ExecFindJunkAttributeInTlist(subplan->targetlist,
5169 : : "wholerow");
1659 alvherre@alvh.no-ip. 5170 [ + + - + ]: 193 : if ((mtstate->operation == CMD_UPDATE || mtstate->operation == CMD_MERGE) &&
2015 tgl@sss.pgh.pa.us 5171 [ - + ]: 112 : !AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
2015 tgl@sss.pgh.pa.us 5172 [ # # ]:UBC 0 : elog(ERROR, "could not find junk wholerow column");
5173 : : }
5174 : : else
5175 : : {
5176 : : /* Other valid target relkinds must provide wholerow */
2015 tgl@sss.pgh.pa.us 5177 :CBC 190 : resultRelInfo->ri_RowIdAttNo =
5178 : 190 : ExecFindJunkAttributeInTlist(subplan->targetlist,
5179 : : "wholerow");
5180 [ - + ]: 190 : if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
2015 tgl@sss.pgh.pa.us 5181 [ # # ]:UBC 0 : elog(ERROR, "could not find junk wholerow column");
5182 : : }
5183 : : }
5184 : : }
5185 : :
5186 : : /*
5187 : : * If this is an inherited update/delete/merge, there will be a junk
5188 : : * attribute named "tableoid" present in the subplan's targetlist. It
5189 : : * will be used to identify the result relation for a given tuple to be
5190 : : * updated/deleted/merged.
5191 : : */
2015 tgl@sss.pgh.pa.us 5192 :CBC 83122 : mtstate->mt_resultOidAttno =
5193 : 83122 : ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
572 amitlan@postgresql.o 5194 [ + + - + ]: 83122 : Assert(AttributeNumberIsValid(mtstate->mt_resultOidAttno) || total_nrels == 1);
2015 tgl@sss.pgh.pa.us 5195 : 83122 : mtstate->mt_lastResultOid = InvalidOid; /* force lookup at first tuple */
5196 : 83122 : mtstate->mt_lastResultIndex = 0; /* must be zero if no such attr */
5197 : :
5198 : : /* Get the root target relation */
2184 heikki.linnakangas@i 5199 : 83122 : rel = mtstate->rootResultRelInfo->ri_RelationDesc;
5200 : :
5201 : : /*
5202 : : * Build state for tuple routing if it's a partitioned INSERT. An UPDATE
5203 : : * or MERGE might need this too, but only if it actually moves tuples
5204 : : * between partitions; in that case setup is done by
5205 : : * ExecCrossPartitionUpdate.
5206 : : */
3188 rhaas@postgresql.org 5207 [ + + + + ]: 83122 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
5208 : : operation == CMD_INSERT)
3154 5209 : 3488 : mtstate->mt_partition_tuple_routing =
2015 tgl@sss.pgh.pa.us 5210 : 3488 : ExecSetupPartitionTupleRouting(estate, rel);
5211 : :
5212 : : /*
5213 : : * Initialize any WITH CHECK OPTION constraints if needed.
5214 : : */
4834 sfrost@snowman.net 5215 : 83122 : resultRelInfo = mtstate->resultRelInfo;
612 amitlan@postgresql.o 5216 [ + + + + : 84174 : foreach(l, withCheckOptionLists)
+ + ]
5217 : : {
4834 sfrost@snowman.net 5218 : 1052 : List *wcoList = (List *) lfirst(l);
5219 : 1052 : List *wcoExprs = NIL;
5220 : : ListCell *ll;
5221 : :
5222 [ + - + + : 3115 : foreach(ll, wcoList)
+ + ]
5223 : : {
5224 : 2063 : WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
3499 andres@anarazel.de 5225 : 2063 : ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
5226 : : &mtstate->ps);
5227 : :
4834 sfrost@snowman.net 5228 : 2063 : wcoExprs = lappend(wcoExprs, wcoExpr);
5229 : : }
5230 : :
5231 : 1052 : resultRelInfo->ri_WithCheckOptions = wcoList;
5232 : 1052 : resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
5233 : 1052 : resultRelInfo++;
5234 : : }
5235 : :
5236 : : /*
5237 : : * Initialize RETURNING projections if needed.
5238 : : */
612 amitlan@postgresql.o 5239 [ + + ]: 83122 : if (returningLists)
5240 : : {
5241 : : TupleTableSlot *slot;
5242 : : ExprContext *econtext;
5243 : :
5244 : : /*
5245 : : * Initialize result tuple slot and assign its rowtype using the plan
5246 : : * node's declared targetlist, which the planner set up to be the same
5247 : : * as the first (before runtime pruning) RETURNING list. We assume
5248 : : * all the result rels will produce compatible output.
5249 : : */
2888 andres@anarazel.de 5250 : 3657 : ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
6211 tgl@sss.pgh.pa.us 5251 : 3657 : slot = mtstate->ps.ps_ResultTupleSlot;
5252 : :
5253 : : /* Need an econtext too */
3499 andres@anarazel.de 5254 [ + - ]: 3657 : if (mtstate->ps.ps_ExprContext == NULL)
5255 : 3657 : ExecAssignExprContext(estate, &mtstate->ps);
5256 : 3657 : econtext = mtstate->ps.ps_ExprContext;
5257 : :
5258 : : /*
5259 : : * Build a projection for each result rel.
5260 : : */
5708 tgl@sss.pgh.pa.us 5261 : 3657 : resultRelInfo = mtstate->resultRelInfo;
612 amitlan@postgresql.o 5262 [ + - + + : 7538 : foreach(l, returningLists)
+ + ]
5263 : : {
6211 tgl@sss.pgh.pa.us 5264 : 3881 : List *rlist = (List *) lfirst(l);
5265 : :
3111 rhaas@postgresql.org 5266 : 3881 : resultRelInfo->ri_returningList = rlist;
6211 tgl@sss.pgh.pa.us 5267 : 3881 : resultRelInfo->ri_projectReturning =
3499 andres@anarazel.de 5268 : 3881 : ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
3400 tgl@sss.pgh.pa.us 5269 : 3881 : resultRelInfo->ri_RelationDesc->rd_att);
6211 5270 : 3881 : resultRelInfo++;
5271 : : }
5272 : : }
5273 : : else
5274 : : {
5275 : : /*
5276 : : * We still must construct a dummy result tuple type, because InitPlan
5277 : : * expects one (maybe should change that?).
5278 : : */
2894 andres@anarazel.de 5279 : 79465 : ExecInitResultTypeTL(&mtstate->ps);
5280 : :
6211 tgl@sss.pgh.pa.us 5281 : 79465 : mtstate->ps.ps_ExprContext = NULL;
5282 : : }
5283 : :
5284 : : /* Set the list of arbiter indexes if needed for ON CONFLICT */
3122 alvherre@alvh.no-ip. 5285 : 83122 : resultRelInfo = mtstate->resultRelInfo;
5286 [ + + ]: 83122 : if (node->onConflictAction != ONCONFLICT_NONE)
5287 : : {
5288 : : /* insert may only have one relation, inheritance is not expanded */
572 amitlan@postgresql.o 5289 [ - + ]: 1274 : Assert(total_nrels == 1);
3122 alvherre@alvh.no-ip. 5290 : 1274 : resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
5291 : : }
5292 : :
5293 : : /*
5294 : : * For ON CONFLICT DO SELECT/UPDATE, initialize the ON CONFLICT action
5295 : : * state.
5296 : : */
242 dean.a.rasheed@gmail 5297 [ + + ]: 83122 : if (node->onConflictAction == ONCONFLICT_UPDATE ||
5298 [ + + ]: 82429 : node->onConflictAction == ONCONFLICT_SELECT)
5299 : : {
5300 : 935 : OnConflictActionState *onconfl = makeNode(OnConflictActionState);
5301 : :
5302 : : /* already exists if created by RETURNING processing above */
4175 andres@anarazel.de 5303 [ + + ]: 935 : if (mtstate->ps.ps_ExprContext == NULL)
5304 : 477 : ExecAssignExprContext(estate, &mtstate->ps);
5305 : :
5306 : : /* action state for DO SELECT/UPDATE */
1981 tgl@sss.pgh.pa.us 5307 : 935 : resultRelInfo->ri_onConflict = onconfl;
5308 : :
5309 : : /* lock strength for DO SELECT [FOR UPDATE/SHARE] */
242 dean.a.rasheed@gmail 5310 : 935 : onconfl->oc_LockStrength = node->onConflictLockStrength;
5311 : :
5312 : : /* initialize slot for the existing tuple */
1981 tgl@sss.pgh.pa.us 5313 : 935 : onconfl->oc_Existing =
2772 andres@anarazel.de 5314 : 935 : table_slot_create(resultRelInfo->ri_RelationDesc,
5315 : 935 : &mtstate->ps.state->es_tupleTable);
5316 : :
5317 : : /*
5318 : : * For ON CONFLICT DO UPDATE, initialize target list and projection.
5319 : : */
242 dean.a.rasheed@gmail 5320 [ + + ]: 935 : if (node->onConflictAction == ONCONFLICT_UPDATE)
5321 : : {
5322 : : ExprContext *econtext;
5323 : : TupleDesc relationDesc;
5324 : :
5325 : 693 : econtext = mtstate->ps.ps_ExprContext;
5326 : 693 : relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
5327 : :
5328 : : /*
5329 : : * Create the tuple slot for the UPDATE SET projection. We want a
5330 : : * slot of the table's type here, because the slot will be used to
5331 : : * insert into the table, and for RETURNING processing - which may
5332 : : * access system attributes.
5333 : : */
5334 : 693 : onconfl->oc_ProjSlot =
5335 : 693 : table_slot_create(resultRelInfo->ri_RelationDesc,
5336 : 693 : &mtstate->ps.state->es_tupleTable);
5337 : :
5338 : : /* build UPDATE SET projection state */
5339 : 693 : onconfl->oc_ProjInfo =
5340 : 693 : ExecBuildUpdateProjection(node->onConflictSet,
5341 : : true,
5342 : : node->onConflictCols,
5343 : : relationDesc,
5344 : : econtext,
5345 : : onconfl->oc_ProjSlot,
5346 : : &mtstate->ps);
5347 : : }
5348 : :
5349 : : /* initialize state to evaluate the WHERE clause, if any */
4175 andres@anarazel.de 5350 [ + + ]: 935 : if (node->onConflictWhere)
5351 : : {
5352 : : ExprState *qualexpr;
5353 : :
3499 5354 : 209 : qualexpr = ExecInitQual((List *) node->onConflictWhere,
5355 : : &mtstate->ps);
1981 tgl@sss.pgh.pa.us 5356 : 209 : onconfl->oc_WhereClause = qualexpr;
5357 : : }
5358 : : }
5359 : :
5360 : : /*
5361 : : * If we have any secondary relations in an UPDATE or DELETE, they need to
5362 : : * be treated like non-locked relations in SELECT FOR UPDATE, i.e., the
5363 : : * EvalPlanQual mechanism needs to be told about them. This also goes for
5364 : : * the source relations in a MERGE. Locate the relevant ExecRowMarks.
5365 : : */
2021 5366 : 83122 : arowmarks = NIL;
6195 5367 [ + + + + : 85017 : foreach(l, node->rowMarks)
+ + ]
5368 : : {
3472 5369 : 1895 : PlanRowMark *rc = lfirst_node(PlanRowMark, l);
269 amitlan@postgresql.o 5370 : 1895 : RangeTblEntry *rte = exec_rt_fetch(rc->rti, estate);
5371 : : ExecRowMark *erm;
5372 : : ExecAuxRowMark *aerm;
5373 : :
5374 : : /* ignore "parent" rowmarks; they are irrelevant at runtime */
5375 [ + + ]: 1895 : if (rc->isParent)
5376 : 96 : continue;
5377 : :
5378 : : /*
5379 : : * Also ignore rowmarks belonging to child tables that have been
5380 : : * pruned in ExecDoInitialPruning().
5381 : : */
5382 [ + + ]: 1799 : if (rte->rtekind == RTE_RELATION &&
612 5383 [ - + ]: 1422 : !bms_is_member(rc->rti, estate->es_unpruned_relids))
6195 tgl@sss.pgh.pa.us 5384 :UBC 0 : continue;
5385 : :
5386 : : /* Find ExecRowMark and build ExecAuxRowMark */
4171 tgl@sss.pgh.pa.us 5387 :CBC 1799 : erm = ExecFindRowMark(estate, rc->rti, false);
2021 5388 : 1799 : aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
5389 : 1799 : arowmarks = lappend(arowmarks, aerm);
5390 : : }
5391 : :
5392 : : /* For a MERGE command, initialize its state */
1659 alvherre@alvh.no-ip. 5393 [ + + ]: 83122 : if (mtstate->operation == CMD_MERGE)
5394 : 1066 : ExecInitMerge(mtstate, estate);
5395 : :
2021 tgl@sss.pgh.pa.us 5396 : 83122 : EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
5397 : :
5398 : : /*
5399 : : * If there are a lot of result relations, use a hash table to speed the
5400 : : * lookups. If there are not a lot, a simple linear search is faster.
5401 : : *
5402 : : * It's not clear where the threshold is, but try 64 for starters. In a
5403 : : * debugging build, use a small threshold so that we get some test
5404 : : * coverage of both code paths.
5405 : : */
5406 : : #ifdef USE_ASSERT_CHECKING
5407 : : #define MT_NRELS_HASH 4
5408 : : #else
5409 : : #define MT_NRELS_HASH 64
5410 : : #endif
5411 [ + + ]: 83122 : if (nrels >= MT_NRELS_HASH)
5412 : : {
5413 : : HASHCTL hash_ctl;
5414 : :
5415 : 219 : hash_ctl.keysize = sizeof(Oid);
5416 : 219 : hash_ctl.entrysize = sizeof(MTTargetRelLookup);
5417 : 219 : hash_ctl.hcxt = CurrentMemoryContext;
5418 : 219 : mtstate->mt_resultOidHash =
5419 : 219 : hash_create("ModifyTable target hash",
5420 : : nrels, &hash_ctl,
5421 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
5422 [ + + ]: 1227 : for (i = 0; i < nrels; i++)
5423 : : {
5424 : : Oid hashkey;
5425 : : MTTargetRelLookup *mtlookup;
5426 : : bool found;
5427 : :
5428 : 1008 : resultRelInfo = &mtstate->resultRelInfo[i];
5429 : 1008 : hashkey = RelationGetRelid(resultRelInfo->ri_RelationDesc);
5430 : : mtlookup = (MTTargetRelLookup *)
5431 : 1008 : hash_search(mtstate->mt_resultOidHash, &hashkey,
5432 : : HASH_ENTER, &found);
5433 [ - + ]: 1008 : Assert(!found);
5434 : 1008 : mtlookup->relationIndex = i;
5435 : : }
5436 : : }
5437 : : else
5438 : 82903 : mtstate->mt_resultOidHash = NULL;
5439 : :
5440 : : /*
5441 : : * Determine if the FDW supports batch insert and determine the batch size
5442 : : * (a FDW may support batching, but it may be disabled for the
5443 : : * server/table).
5444 : : *
5445 : : * We only do this for INSERT, so that for UPDATE/DELETE the batch size
5446 : : * remains set to 0.
5447 : : */
2090 tomas.vondra@postgre 5448 [ + + ]: 83122 : if (operation == CMD_INSERT)
5449 : : {
5450 : : /* insert may only have one relation, inheritance is not expanded */
572 amitlan@postgresql.o 5451 [ - + ]: 65138 : Assert(total_nrels == 1);
2090 tomas.vondra@postgre 5452 : 65138 : resultRelInfo = mtstate->resultRelInfo;
2015 tgl@sss.pgh.pa.us 5453 [ + - ]: 65138 : if (!resultRelInfo->ri_usesFdwDirectModify &&
5454 [ + + ]: 65138 : resultRelInfo->ri_FdwRoutine != NULL &&
5455 [ + - ]: 89 : resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize &&
5456 [ + - ]: 89 : resultRelInfo->ri_FdwRoutine->ExecForeignBatchInsert)
5457 : : {
5458 : 89 : resultRelInfo->ri_BatchSize =
5459 : 89 : resultRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(resultRelInfo);
2090 tomas.vondra@postgre 5460 [ - + ]: 89 : Assert(resultRelInfo->ri_BatchSize >= 1);
5461 : : }
5462 : : else
2015 tgl@sss.pgh.pa.us 5463 : 65049 : resultRelInfo->ri_BatchSize = 1;
5464 : : }
5465 : :
5466 : : /*
5467 : : * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
5468 : : * to estate->es_auxmodifytables so that it will be run to completion by
5469 : : * ExecPostprocessPlan. (It'd actually work fine to add the primary
5470 : : * ModifyTable node too, but there's no need.) Note the use of lcons not
5471 : : * lappend: we need later-initialized ModifyTable nodes to be shut down
5472 : : * before earlier ones. This ensures that we don't throw away RETURNING
5473 : : * rows that need to be seen by a later CTE subplan.
5474 : : */
5708 5475 [ + + ]: 83122 : if (!mtstate->canSetTag)
5476 : 671 : estate->es_auxmodifytables = lcons(mtstate,
5477 : : estate->es_auxmodifytables);
5478 : :
6211 5479 : 83122 : return mtstate;
5480 : : }
5481 : :
5482 : : /* ----------------------------------------------------------------
5483 : : * ExecEndModifyTable
5484 : : *
5485 : : * Shuts down the plan.
5486 : : *
5487 : : * Returns nothing of interest.
5488 : : * ----------------------------------------------------------------
5489 : : */
5490 : : void
5491 : 80096 : ExecEndModifyTable(ModifyTableState *node)
5492 : : {
5493 : : int i;
5494 : :
5495 : : /*
5496 : : * Allow any FDWs to shut down
5497 : : */
2021 5498 [ + + ]: 161563 : for (i = 0; i < node->mt_nrels; i++)
5499 : : {
5500 : : int j;
4964 5501 : 81467 : ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
5502 : :
3860 rhaas@postgresql.org 5503 [ + + ]: 81467 : if (!resultRelInfo->ri_usesFdwDirectModify &&
5504 [ + + ]: 81367 : resultRelInfo->ri_FdwRoutine != NULL &&
4964 tgl@sss.pgh.pa.us 5505 [ + - ]: 160 : resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
5506 : 160 : resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
5507 : : resultRelInfo);
5508 : :
5509 : : /*
5510 : : * Cleanup the initialized batch slots. This only matters for FDWs
5511 : : * with batching, but the other cases will have ri_NumSlotsInitialized
5512 : : * == 0.
5513 : : */
1949 tomas.vondra@postgre 5514 [ + + ]: 81495 : for (j = 0; j < resultRelInfo->ri_NumSlotsInitialized; j++)
5515 : : {
5516 : 28 : ExecDropSingleTupleTableSlot(resultRelInfo->ri_Slots[j]);
5517 : 28 : ExecDropSingleTupleTableSlot(resultRelInfo->ri_PlanSlots[j]);
5518 : : }
5519 : : }
5520 : :
5521 : : /*
5522 : : * Close all the partitioned tables, leaf partitions, and their indices
5523 : : * and release the slot used for tuple routing, if set.
5524 : : */
3203 rhaas@postgresql.org 5525 [ + + ]: 80096 : if (node->mt_partition_tuple_routing)
5526 : : {
3111 5527 : 3512 : ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
5528 : :
2887 alvherre@alvh.no-ip. 5529 [ + + ]: 3512 : if (node->mt_root_tuple_slot)
5530 : 425 : ExecDropSingleTupleTableSlot(node->mt_root_tuple_slot);
5531 : : }
5532 : :
5533 : : /*
5534 : : * Terminate EPQ execution if active
5535 : : */
6195 tgl@sss.pgh.pa.us 5536 : 80096 : EvalPlanQualEnd(&node->mt_epqstate);
5537 : :
5538 : : /*
5539 : : * shut down subplan
5540 : : */
2021 5541 : 80096 : ExecEndNode(outerPlanState(node));
6211 5542 : 80096 : }
5543 : :
5544 : : void
5936 tgl@sss.pgh.pa.us 5545 :UBC 0 : ExecReScanModifyTable(ModifyTableState *node)
5546 : : {
5547 : : /*
5548 : : * Currently, we don't need to support rescan on ModifyTable nodes. The
5549 : : * semantics of that would be a bit debatable anyway.
5550 : : */
6211 5551 [ # # ]: 0 : elog(ERROR, "ExecReScanModifyTable is not implemented");
5552 : : }
|