1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
|
/*-------------------------------------------------------------------------
*
* nodeMerge.c
* routines to handle Merge nodes relating to the MERGE command
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/executor/nodeMerge.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
#include "executor/nodeMerge.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/tqual.h"
/*
* Check and execute the first qualifying MATCHED action. The current target
* tuple is identified by tupleid.
*
* We start from the first WHEN MATCHED action and check if the WHEN AND quals
* pass, if any. If the WHEN AND quals for the first action do not pass, we
* check the second, then the third and so on. If we reach to the end, no
* action is taken and we return true, indicating that no further action is
* required for this tuple.
*
* If we do find a qualifying action, then we attempt to execute the action.
*
* If the tuple is concurrently updated, EvalPlanQual is run with the updated
* tuple to recheck the join quals. Note that the additional quals associated
* with individual actions are evaluated separately by the MERGE code, while
* EvalPlanQual checks for the join quals. If EvalPlanQual tells us that the
* updated tuple still passes the join quals, then we restart from the first
* action to look for a qualifying action. Otherwise, we return false meaning
* that a NOT MATCHED action must now be executed for the current source tuple.
*/
static bool
ExecMergeMatched(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot, JunkFilter *junkfilter,
ItemPointer tupleid)
{
ExprContext *econtext = mtstate->ps.ps_ExprContext;
bool isNull;
List *mergeMatchedActionStates = NIL;
HeapUpdateFailureData hufd;
bool tuple_updated,
tuple_deleted;
Buffer buffer;
HeapTupleData tuple;
EPQState *epqstate = &mtstate->mt_epqstate;
ResultRelInfo *saved_resultRelInfo;
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
ListCell *l;
TupleTableSlot *saved_slot = slot;
if (mtstate->mt_partition_tuple_routing)
{
Datum datum;
Oid tableoid = InvalidOid;
int leaf_part_index;
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
/*
* In case of partitioned table, we fetch the tableoid while performing
* MATCHED MERGE action.
*/
datum = ExecGetJunkAttribute(slot, junkfilter->jf_otherJunkAttNo,
&isNull);
Assert(!isNull);
tableoid = DatumGetObjectId(datum);
/*
* If we're dealing with a MATCHED tuple, then tableoid must have been
* set correctly. In case of partitioned table, we must now fetch the
* correct result relation corresponding to the child table emitting
* the matching target row. For normal table, there is just one result
* relation and it must be the one emitting the matching row.
*/
leaf_part_index = ExecFindPartitionByOid(proute, tableoid);
resultRelInfo = proute->partitions[leaf_part_index];
if (resultRelInfo == NULL)
{
resultRelInfo = ExecInitPartitionInfo(mtstate,
mtstate->resultRelInfo,
proute, estate, leaf_part_index);
Assert(resultRelInfo != NULL);
}
}
/*
* Save the current information and work with the correct result relation.
*/
saved_resultRelInfo = resultRelInfo;
estate->es_result_relation_info = resultRelInfo;
/*
* And get the correct action lists.
*/
mergeMatchedActionStates =
resultRelInfo->ri_mergeState->matchedActionStates;
/*
* If there are not WHEN MATCHED actions, we are done.
*/
if (mergeMatchedActionStates == NIL)
return true;
/*
* Make tuple and any needed join variables available to ExecQual and
* ExecProject. The target's existing tuple is installed in the scantuple.
* Again, this target relation's slot is required only in the case of a
* MATCHED tuple and UPDATE/DELETE actions.
*/
if (mtstate->mt_partition_tuple_routing)
ExecSetSlotDescriptor(mtstate->mt_existing,
resultRelInfo->ri_RelationDesc->rd_att);
econtext->ecxt_scantuple = mtstate->mt_existing;
econtext->ecxt_innertuple = slot;
econtext->ecxt_outertuple = NULL;
lmerge_matched:;
slot = saved_slot;
/*
* UPDATE/DELETE is only invoked for matched rows. And we must have found
* the tupleid of the target row in that case. We fetch using SnapshotAny
* because we might get called again after EvalPlanQual returns us a new
* tuple. This tuple may not be visible to our MVCC snapshot.
*/
Assert(tupleid != NULL);
tuple.t_self = *tupleid;
if (!heap_fetch(resultRelInfo->ri_RelationDesc, SnapshotAny, &tuple,
&buffer, true, NULL))
elog(ERROR, "Failed to fetch the target tuple");
/* Store target's existing tuple in the state's dedicated slot */
ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false);
foreach(l, mergeMatchedActionStates)
{
MergeActionState *action = (MergeActionState *) lfirst(l);
/*
* Test condition, if any
*
* In the absence of a condition we perform the action unconditionally
* (no need to check separately since ExecQual() will return true if
* there are no conditions to evaluate).
*/
if (!ExecQual(action->whenqual, econtext))
continue;
/*
* Check if the existing target tuple meet the USING checks of
* UPDATE/DELETE RLS policies. If those checks fail, we throw an
* error.
*
* The WITH CHECK quals are applied in ExecUpdate() and hence we need
* not do anything special to handle them.
*
* NOTE: We must do this after WHEN quals are evaluated so that we
* check policies only when they matter.
*/
if (resultRelInfo->ri_WithCheckOptions)
{
ExecWithCheckOptions(action->commandType == CMD_UPDATE ?
WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK,
resultRelInfo,
mtstate->mt_existing,
mtstate->ps.state);
}
/* Perform stated action */
switch (action->commandType)
{
case CMD_UPDATE:
/*
* We set up the projection earlier, so all we do here is
* Project, no need for any other tasks prior to the
* ExecUpdate.
*/
if (mtstate->mt_partition_tuple_routing)
ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc);
ExecProject(action->proj);
/*
* We don't call ExecFilterJunk() because the projected tuple
* using the UPDATE action's targetlist doesn't have a junk
* attribute.
*/
slot = ExecUpdate(mtstate, tupleid, NULL,
mtstate->mt_mergeproj,
slot, epqstate, estate,
&tuple_updated, &hufd,
action, mtstate->canSetTag);
break;
case CMD_DELETE:
/* Nothing to Project for a DELETE action */
slot = ExecDelete(mtstate, tupleid, NULL,
slot, epqstate, estate,
&tuple_deleted, false, &hufd, action,
mtstate->canSetTag);
break;
default:
elog(ERROR, "unknown action in MERGE WHEN MATCHED clause");
}
/*
* Check for any concurrent update/delete operation which may have
* prevented our update/delete. We also check for situations where we
* might be trying to update/delete the same tuple twice.
*/
if ((action->commandType == CMD_UPDATE && !tuple_updated) ||
(action->commandType == CMD_DELETE && !tuple_deleted))
{
switch (hufd.result)
{
case HeapTupleMayBeUpdated:
break;
case HeapTupleInvisible:
/*
* This state should never be reached since the underlying
* JOIN runs with a MVCC snapshot and should only return
* rows visible to us.
*/
elog(ERROR, "unexpected invisible tuple");
break;
case HeapTupleSelfUpdated:
/*
* SQLStandard disallows this for MERGE.
*/
if (TransactionIdIsCurrentTransactionId(hufd.xmax))
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("MERGE command cannot affect row a second time"),
errhint("Ensure that not more than one source row matches any one target row")));
/* This shouldn't happen */
elog(ERROR, "attempted to update or delete invisible tuple");
break;
case HeapTupleUpdated:
/*
* The target tuple was concurrently updated/deleted by
* some other transaction.
*
* If the current tuple is that last tuple in the update
* chain, then we know that the tuple was concurrently
* deleted. Just return and let the caller try NOT MATCHED
* actions.
*
* If the current tuple was concurrently updated, then we
* must run the EvalPlanQual() with the new version of the
* tuple. If EvalPlanQual() does not return a tuple then
* we switch to the NOT MATCHED list of actions.
* If it does return a tuple and the join qual is
* still satisfied, then we just need to recheck the
* MATCHED actions, starting from the top, and execute the
* first qualifying action.
*/
if (!ItemPointerEquals(tupleid, &hufd.ctid))
{
TupleTableSlot *epqslot;
/*
* Since we generate a JOIN query with a target table
* RTE different than the result relation RTE, we must
* pass in the RTI of the relation used in the join
* query and not the one from result relation.
*/
Assert(resultRelInfo->ri_mergeTargetRTI > 0);
epqslot = EvalPlanQual(estate,
epqstate,
resultRelInfo->ri_RelationDesc,
GetEPQRangeTableIndex(resultRelInfo),
LockTupleExclusive,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))
{
(void) ExecGetJunkAttribute(epqslot,
resultRelInfo->ri_junkFilter->jf_junkAttNo,
&isNull);
/*
* A non-NULL ctid means that we are still dealing
* with MATCHED case. But we must retry from the
* start with the updated tuple to ensure that the
* first qualifying WHEN MATCHED action is
* executed.
*
* We don't use the new slot returned by
* EvalPlanQual because we anyways re-install the
* new target tuple in econtext->ecxt_scantuple
* before re-evaluating WHEN AND conditions and
* re-projecting the update targetlists. The
* source side tuple does not change and hence we
* can safely continue to use the old slot.
*/
if (!isNull)
{
/*
* Must update *tupleid to the TID of the
* newer tuple found in the update chain.
*/
*tupleid = hufd.ctid;
ReleaseBuffer(buffer);
goto lmerge_matched;
}
}
}
/*
* Tell the caller about the updated TID, restore the
* state back and return.
*/
*tupleid = hufd.ctid;
estate->es_result_relation_info = saved_resultRelInfo;
ReleaseBuffer(buffer);
return false;
default:
break;
}
}
if (action->commandType == CMD_UPDATE && tuple_updated)
InstrCountFiltered2(&mtstate->ps, 1);
if (action->commandType == CMD_DELETE && tuple_deleted)
InstrCountFiltered3(&mtstate->ps, 1);
/*
* We've activated one of the WHEN clauses, so we don't search
* further. This is required behaviour, not an optimization.
*/
estate->es_result_relation_info = saved_resultRelInfo;
break;
}
ReleaseBuffer(buffer);
/*
* Successfully executed an action or no qualifying action was found.
*/
return true;
}
/*
* Execute the first qualifying NOT MATCHED action.
*/
static void
ExecMergeNotMatched(ModifyTableState *mtstate, EState *estate,
TupleTableSlot *slot)
{
PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
ExprContext *econtext = mtstate->ps.ps_ExprContext;
List *mergeNotMatchedActionStates = NIL;
ResultRelInfo *resultRelInfo;
ListCell *l;
TupleTableSlot *myslot;
/*
* We are dealing with NOT MATCHED tuple. Since for MERGE, partition tree
* is not expanded for the result relation, we continue to work with the
* currently active result relation, which should be of the root of the
* partition tree.
*/
resultRelInfo = mtstate->resultRelInfo;
/*
* For INSERT actions, root relation's merge action is OK since the
* INSERT's targetlist and the WHEN conditions can only refer to the
* source relation and hence it does not matter which result relation we
* work with.
*/
mergeNotMatchedActionStates =
resultRelInfo->ri_mergeState->notMatchedActionStates;
/*
* Make source tuple available to ExecQual and ExecProject. We don't need
* the target tuple since the WHEN quals and the targetlist can't refer to
* the target columns.
*/
econtext->ecxt_scantuple = NULL;
econtext->ecxt_innertuple = slot;
econtext->ecxt_outertuple = NULL;
foreach(l, mergeNotMatchedActionStates)
{
MergeActionState *action = (MergeActionState *) lfirst(l);
/*
* Test condition, if any
*
* In the absence of a condition we perform the action unconditionally
* (no need to check separately since ExecQual() will return true if
* there are no conditions to evaluate).
*/
if (!ExecQual(action->whenqual, econtext))
continue;
/* Perform stated action */
switch (action->commandType)
{
case CMD_INSERT:
/*
* We set up the projection earlier, so all we do here is
* Project, no need for any other tasks prior to the
* ExecInsert.
*/
if (mtstate->mt_partition_tuple_routing)
ExecSetSlotDescriptor(mtstate->mt_mergeproj, action->tupDesc);
ExecProject(action->proj);
/*
* ExecPrepareTupleRouting may modify the passed-in slot. Hence
* pass a local reference so that action->slot is not modified.
*/
myslot = mtstate->mt_mergeproj;
/* Prepare for tuple routing if needed. */
if (proute)
myslot = ExecPrepareTupleRouting(mtstate, estate, proute,
resultRelInfo, myslot);
slot = ExecInsert(mtstate, myslot, slot,
estate, action,
mtstate->canSetTag);
/* Revert ExecPrepareTupleRouting's state change. */
if (proute)
estate->es_result_relation_info = resultRelInfo;
InstrCountFiltered1(&mtstate->ps, 1);
break;
case CMD_NOTHING:
/* Do Nothing */
break;
default:
elog(ERROR, "unknown action in MERGE WHEN NOT MATCHED clause");
}
break;
}
}
/*
* Perform MERGE.
*/
void
ExecMerge(ModifyTableState *mtstate, EState *estate, TupleTableSlot *slot,
JunkFilter *junkfilter, ResultRelInfo *resultRelInfo)
{
ExprContext *econtext = mtstate->ps.ps_ExprContext;
ItemPointer tupleid;
ItemPointerData tuple_ctid;
bool matched = false;
char relkind;
Datum datum;
bool isNull;
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
Assert(relkind == RELKIND_RELATION ||
relkind == RELKIND_PARTITIONED_TABLE);
/*
* Reset per-tuple memory context to free any expression evaluation
* storage allocated in the previous cycle.
*/
ResetExprContext(econtext);
/*
* We run a JOIN between the target relation and the source relation to
* find a set of candidate source rows that has matching row in the target
* table and a set of candidate source rows that does not have matching
* row in the target table. If the join returns us a tuple with target
* relation's tid set, that implies that the join found a matching row for
* the given source tuple. This case triggers the WHEN MATCHED clause of
* the MERGE. Whereas a NULL in the target relation's ctid column
* indicates a NOT MATCHED case.
*/
datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo, &isNull);
if (!isNull)
{
matched = true;
tupleid = (ItemPointer) DatumGetPointer(datum);
tuple_ctid = *tupleid; /* be sure we don't free ctid!! */
tupleid = &tuple_ctid;
}
else
{
matched = false;
tupleid = NULL; /* we don't need it for INSERT actions */
}
/*
* If we are dealing with a WHEN MATCHED case, we execute the first action
* for which the additional WHEN MATCHED AND quals pass. If an action
* without quals is found, that action is executed.
*
* Similarly, if we are dealing with WHEN NOT MATCHED case, we look at the
* given WHEN NOT MATCHED actions in sequence until one passes.
*
* Things get interesting in case of concurrent update/delete of the
* target tuple. Such concurrent update/delete is detected while we are
* executing a WHEN MATCHED action.
*
* A concurrent update can:
*
* 1. modify the target tuple so that it no longer satisfies the
* additional quals attached to the current WHEN MATCHED action OR
*
* In this case, we are still dealing with a WHEN MATCHED case, but
* we should recheck the list of WHEN MATCHED actions and choose the first
* one that satisfies the new target tuple.
*
* 2. modify the target tuple so that the join quals no longer pass and
* hence the source tuple no longer has a match.
*
* In the second case, the source tuple no longer matches the target tuple,
* so we now instead find a qualifying WHEN NOT MATCHED action to execute.
*
* A concurrent delete, changes a WHEN MATCHED case to WHEN NOT MATCHED.
*
* ExecMergeMatched takes care of following the update chain and
* re-finding the qualifying WHEN MATCHED action, as long as the updated
* target tuple still satisfies the join quals i.e. it still remains a
* WHEN MATCHED case. If the tuple gets deleted or the join quals fail, it
* returns and we try ExecMergeNotMatched. Given that ExecMergeMatched
* always make progress by following the update chain and we never switch
* from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a
* livelock.
*/
if (matched)
matched = ExecMergeMatched(mtstate, estate, slot, junkfilter, tupleid);
/*
* Either we were dealing with a NOT MATCHED tuple or ExecMergeNotMatched()
* returned "false", indicating the previously MATCHED tuple is no longer a
* matching tuple.
*/
if (!matched)
ExecMergeNotMatched(mtstate, estate, slot);
}
|