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