Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * nodeLockRows.c
4 : * Routines to handle FOR UPDATE/FOR SHARE row locking
5 : *
6 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/executor/nodeLockRows.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : /*
16 : * INTERFACE ROUTINES
17 : * ExecLockRows - fetch locked rows
18 : * ExecInitLockRows - initialize node and subnodes..
19 : * ExecEndLockRows - shutdown node and subnodes
20 : */
21 :
22 : #include "postgres.h"
23 :
24 : #include "access/tableam.h"
25 : #include "access/xact.h"
26 : #include "executor/executor.h"
27 : #include "executor/nodeLockRows.h"
28 : #include "foreign/fdwapi.h"
29 : #include "miscadmin.h"
30 : #include "utils/rel.h"
31 :
32 :
33 : /* ----------------------------------------------------------------
34 : * ExecLockRows
35 : * ----------------------------------------------------------------
36 : */
37 : static TupleTableSlot * /* return: a tuple or NULL */
2092 andres 38 CBC 6987 : ExecLockRows(PlanState *pstate)
39 : {
40 6987 : LockRowsState *node = castNode(LockRowsState, pstate);
41 : TupleTableSlot *slot;
42 : EState *estate;
43 : PlanState *outerPlan;
44 : bool epq_needed;
45 : ListCell *lc;
46 :
2084 47 6987 : CHECK_FOR_INTERRUPTS();
48 :
49 : /*
50 : * get information from the node
51 : */
4927 tgl 52 6987 : estate = node->ps.state;
53 6987 : outerPlan = outerPlanState(node);
54 :
55 : /*
56 : * Get next tuple from subplan, if any.
57 : */
58 51 : lnext:
4913 59 7038 : slot = ExecProcNode(outerPlan);
60 :
4927 61 7038 : if (TupIsNull(slot))
62 : {
63 : /* Release any resources held by EPQ mechanism before exiting */
668 64 1223 : EvalPlanQualEnd(&node->lr_epqstate);
4927 65 1223 : return NULL;
66 : }
67 :
68 : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
2889 69 5815 : epq_needed = false;
70 :
71 : /*
72 : * Attempt to lock the source tuple(s). (Note we only have locking
73 : * rowmarks in lr_arowMarks.)
74 : */
4470 75 11930 : foreach(lc, node->lr_arowMarks)
76 : {
77 6187 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
78 6187 : ExecRowMark *erm = aerm->rowmark;
79 : Datum datum;
80 : bool isNull;
81 : ItemPointerData tid;
82 : TM_FailureData tmfd;
83 : LockTupleMode lockmode;
367 alvherre 84 6187 : int lockflags = 0;
85 : TM_Result test;
86 : TupleTableSlot *markSlot;
87 :
88 : /* clear any leftover test tuple for this rel */
1500 andres 89 6187 : markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
90 6187 : ExecClearTuple(markSlot);
91 :
92 : /* if child rel, must check whether it produced this row */
4927 tgl 93 6187 : if (erm->rti != erm->prti)
94 : {
95 : Oid tableoid;
96 :
97 599 : datum = ExecGetJunkAttribute(slot,
4470 98 599 : aerm->toidAttNo,
99 : &isNull);
100 : /* shouldn't ever get a null result... */
4927 101 599 : if (isNull)
4927 tgl 102 UBC 0 : elog(ERROR, "tableoid is NULL");
4927 tgl 103 CBC 599 : tableoid = DatumGetObjectId(datum);
104 :
2940 105 599 : Assert(OidIsValid(erm->relid));
106 599 : if (tableoid != erm->relid)
107 : {
108 : /* this child is inactive right now */
2889 109 125 : erm->ermActive = false;
4927 110 125 : ItemPointerSetInvalid(&(erm->curCtid));
1478 andres 111 125 : ExecClearTuple(markSlot);
4927 tgl 112 125 : continue;
113 : }
114 : }
2889 115 6062 : erm->ermActive = true;
116 :
117 : /* fetch the tuple's ctid */
4927 118 6062 : datum = ExecGetJunkAttribute(slot,
4470 119 6062 : aerm->ctidAttNo,
120 : &isNull);
121 : /* shouldn't ever get a null result... */
4927 122 6062 : if (isNull)
4927 tgl 123 UBC 0 : elog(ERROR, "ctid is NULL");
124 :
125 : /* requests for foreign tables must be passed to their FDW */
2889 tgl 126 CBC 6062 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
2889 tgl 127 UBC 0 : {
128 : FdwRoutine *fdwroutine;
129 0 : bool updated = false;
130 :
131 0 : fdwroutine = GetFdwRoutineForRelation(erm->relation, false);
132 : /* this should have been checked already, but let's be safe */
133 0 : if (fdwroutine->RefetchForeignRow == NULL)
134 0 : ereport(ERROR,
135 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
136 : errmsg("cannot lock rows in foreign table \"%s\"",
137 : RelationGetRelationName(erm->relation))));
138 :
1500 andres 139 0 : fdwroutine->RefetchForeignRow(estate,
140 : erm,
141 : datum,
142 : markSlot,
143 : &updated);
144 0 : if (TupIsNull(markSlot))
145 : {
146 : /* couldn't get the lock, so skip this row */
2889 tgl 147 0 : goto lnext;
148 : }
149 :
150 : /*
151 : * if FDW says tuple was updated before getting locked, we need to
152 : * perform EPQ testing to see if quals are still satisfied
153 : */
154 0 : if (updated)
155 0 : epq_needed = true;
156 :
157 0 : continue;
158 : }
159 :
160 : /* okay, try to lock (and fetch) the tuple */
1478 andres 161 CBC 6062 : tid = *((ItemPointer) DatumGetPointer(datum));
3728 alvherre 162 6062 : switch (erm->markType)
163 : {
164 1938 : case ROW_MARK_EXCLUSIVE:
165 1938 : lockmode = LockTupleExclusive;
166 1938 : break;
167 31 : case ROW_MARK_NOKEYEXCLUSIVE:
168 31 : lockmode = LockTupleNoKeyExclusive;
169 31 : break;
170 1252 : case ROW_MARK_SHARE:
171 1252 : lockmode = LockTupleShare;
172 1252 : break;
173 2841 : case ROW_MARK_KEYSHARE:
174 2841 : lockmode = LockTupleKeyShare;
175 2841 : break;
3728 alvherre 176 UBC 0 : default:
177 0 : elog(ERROR, "unsupported rowmark type");
178 : lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
179 : break;
180 : }
181 :
367 alvherre 182 CBC 6062 : lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
183 6062 : if (!IsolationUsesXactSnapshot())
184 4221 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
185 :
186 6062 : test = table_tuple_lock(erm->relation, &tid, estate->es_snapshot,
187 : markSlot, estate->es_output_cid,
188 : lockmode, erm->waitPolicy,
189 : lockflags,
190 : &tmfd);
191 :
192 6049 : switch (test)
193 : {
194 37 : case TM_WouldBlock:
195 : /* couldn't lock tuple in SKIP LOCKED mode */
196 37 : goto lnext;
197 :
198 3 : case TM_SelfModified:
199 :
200 : /*
201 : * The target tuple was already updated or deleted by the
202 : * current command, or by a later command in the current
203 : * transaction. We *must* ignore the tuple in the former
204 : * case, so as to avoid the "Halloween problem" of repeated
205 : * update attempts. In the latter case it might be sensible
206 : * to fetch the updated tuple instead, but doing so would
207 : * require changing heap_update and heap_delete to not
208 : * complain about updating "invisible" tuples, which seems
209 : * pretty scary (table_tuple_lock will not complain, but few
210 : * callers expect TM_Invisible, and we're not one of them). So
211 : * for now, treat the tuple as deleted and do not process.
212 : */
213 3 : goto lnext;
214 :
215 5990 : case TM_Ok:
216 :
217 : /*
218 : * Got the lock successfully, the locked tuple saved in
219 : * markSlot for, if needed, EvalPlanQual testing below.
220 : */
221 5990 : if (tmfd.traversed)
222 28 : epq_needed = true;
223 5990 : break;
224 :
225 12 : case TM_Updated:
226 12 : if (IsolationUsesXactSnapshot())
227 12 : ereport(ERROR,
228 : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
229 : errmsg("could not serialize access due to concurrent update")));
367 alvherre 230 UBC 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
231 : test);
232 : break;
233 :
367 alvherre 234 CBC 7 : case TM_Deleted:
235 7 : if (IsolationUsesXactSnapshot())
236 2 : ereport(ERROR,
237 : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
238 : errmsg("could not serialize access due to concurrent update")));
239 : /* tuple was deleted so don't return it */
240 5 : goto lnext;
241 :
367 alvherre 242 UBC 0 : case TM_Invisible:
243 0 : elog(ERROR, "attempted to lock invisible tuple");
244 : break;
245 :
246 0 : default:
247 0 : elog(ERROR, "unrecognized table_tuple_lock status: %u",
248 : test);
249 : }
250 :
251 : /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
1478 andres 252 CBC 5990 : erm->curCtid = tid;
253 : }
254 :
255 : /*
256 : * If we need to do EvalPlanQual testing, do so.
257 : */
2889 tgl 258 5743 : if (epq_needed)
259 : {
260 : /* Initialize EPQ machinery */
1312 andres 261 27 : EvalPlanQualBegin(&node->lr_epqstate);
262 :
263 : /*
264 : * To fetch non-locked source rows the EPQ logic needs to access junk
265 : * columns from the tuple being tested.
266 : */
4913 tgl 267 27 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
268 :
269 : /*
270 : * And finally we can re-evaluate the tuple.
271 : */
272 27 : slot = EvalPlanQualNext(&node->lr_epqstate);
273 27 : if (TupIsNull(slot))
274 : {
275 : /* Updated tuple fails qual, so ignore it and go on */
276 6 : goto lnext;
277 : }
278 : }
279 :
280 : /* Got all locks, so return the current tuple */
4927 281 5737 : return slot;
282 : }
283 :
284 : /* ----------------------------------------------------------------
285 : * ExecInitLockRows
286 : *
287 : * This initializes the LockRows node state structures and
288 : * the node's subplan.
289 : * ----------------------------------------------------------------
290 : */
291 : LockRowsState *
292 3511 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
293 : {
294 : LockRowsState *lrstate;
4913 295 3511 : Plan *outerPlan = outerPlan(node);
296 : List *epq_arowmarks;
297 : ListCell *lc;
298 :
299 : /* check for unsupported flags */
4927 300 3511 : Assert(!(eflags & EXEC_FLAG_MARK));
301 :
302 : /*
303 : * create state structure
304 : */
305 3511 : lrstate = makeNode(LockRowsState);
306 3511 : lrstate->ps.plan = (Plan *) node;
307 3511 : lrstate->ps.state = estate;
2092 andres 308 3511 : lrstate->ps.ExecProcNode = ExecLockRows;
309 :
310 : /*
311 : * Miscellaneous initialization
312 : *
313 : * LockRows nodes never call ExecQual or ExecProject, therefore no
314 : * ExprContext is needed.
315 : */
316 :
317 : /*
318 : * Initialize result type.
319 : */
1612 320 3511 : ExecInitResultTypeTL(&lrstate->ps);
321 :
322 : /*
323 : * then initialize outer plan
324 : */
4927 tgl 325 3511 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
326 :
327 : /* node returns unmodified slots from the outer plan */
1606 andres 328 3511 : lrstate->ps.resultopsset = true;
329 3511 : lrstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(lrstate),
330 : &lrstate->ps.resultopsfixed);
331 :
332 : /*
333 : * LockRows nodes do no projections, so initialize projection info for
334 : * this node appropriately
335 : */
4927 tgl 336 3511 : lrstate->ps.ps_ProjInfo = NULL;
337 :
338 : /*
339 : * Locate the ExecRowMark(s) that this node is responsible for, and
340 : * construct ExecAuxRowMarks for them. (InitPlan should already have
341 : * built the global list of ExecRowMarks.)
342 : */
4470 343 3511 : lrstate->lr_arowMarks = NIL;
344 3511 : epq_arowmarks = NIL;
4927 345 7958 : foreach(lc, node->rowMarks)
346 : {
2190 347 4447 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
348 : ExecRowMark *erm;
349 : ExecAuxRowMark *aerm;
350 :
351 : /* ignore "parent" rowmarks; they are irrelevant at runtime */
4927 352 4447 : if (rc->isParent)
353 723 : continue;
354 :
355 : /* find ExecRowMark and build ExecAuxRowMark */
2889 356 3724 : erm = ExecFindRowMark(estate, rc->rti, false);
4470 357 3724 : aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
358 :
359 : /*
360 : * Only locking rowmarks go into our own list. Non-locking marks are
361 : * passed off to the EvalPlanQual machinery. This is because we don't
362 : * want to bother fetching non-locked rows unless we actually have to
363 : * do an EPQ recheck.
364 : */
4913 365 3724 : if (RowMarkRequiresRowShareLock(erm->markType))
4470 366 3590 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
367 : else
368 134 : epq_arowmarks = lappend(epq_arowmarks, aerm);
369 : }
370 :
371 : /* Now we have the info needed to set up EPQ state */
372 3511 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
373 : outerPlan, epq_arowmarks, node->epqParam);
374 :
4927 375 3511 : return lrstate;
376 : }
377 :
378 : /* ----------------------------------------------------------------
379 : * ExecEndLockRows
380 : *
381 : * This shuts down the subplan and frees resources allocated
382 : * to this node.
383 : * ----------------------------------------------------------------
384 : */
385 : void
386 3468 : ExecEndLockRows(LockRowsState *node)
387 : {
388 : /* We may have shut down EPQ already, but no harm in another call */
4913 389 3468 : EvalPlanQualEnd(&node->lr_epqstate);
4927 390 3468 : ExecEndNode(outerPlanState(node));
391 3468 : }
392 :
393 :
394 : void
4654 395 8 : ExecReScanLockRows(LockRowsState *node)
396 : {
276 tgl 397 GNC 8 : PlanState *outerPlan = outerPlanState(node);
398 :
4927 tgl 399 ECB : /*
400 : * if chgParam of subnode is not null then plan will be re-scanned by
401 : * first ExecProcNode.
402 : */
276 tgl 403 GNC 8 : if (outerPlan->chgParam == NULL)
276 tgl 404 UNC 0 : ExecReScan(outerPlan);
4927 tgl 405 CBC 8 : }
|