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-2026, 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 */
3163 andres@anarazel.de 38 :CBC 486111 : ExecLockRows(PlanState *pstate)
39 : : {
40 : 486111 : LockRowsState *node = castNode(LockRowsState, pstate);
41 : : TupleTableSlot *slot;
42 : : EState *estate;
43 : : PlanState *outerPlan;
44 : : bool epq_needed;
45 : : ListCell *lc;
46 : :
3155 47 [ + + ]: 486111 : CHECK_FOR_INTERRUPTS();
48 : :
49 : : /*
50 : : * get information from the node
51 : : */
5998 tgl@sss.pgh.pa.us 52 : 486111 : estate = node->ps.state;
53 : 486111 : outerPlan = outerPlanState(node);
54 : :
55 : : /*
56 : : * Get next tuple from subplan, if any.
57 : : */
58 : 57 : lnext:
5984 59 : 486168 : slot = ExecProcNode(outerPlan);
60 : :
5998 61 [ + + + + ]: 486168 : if (TupIsNull(slot))
62 : : {
63 : : /* Release any resources held by EPQ mechanism before exiting */
1739 64 : 4469 : EvalPlanQualEnd(&node->lr_epqstate);
5998 65 : 4469 : return NULL;
66 : : }
67 : :
68 : : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
3960 69 : 481699 : epq_needed = false;
70 : :
71 : : /*
72 : : * Attempt to lock the source tuple(s). (Note we only have locking
73 : : * rowmarks in lr_arowMarks.)
74 : : */
5541 75 [ + + + + : 963518 : foreach(lc, node->lr_arowMarks)
+ + ]
76 : : {
77 : 481906 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
78 : 481906 : ExecRowMark *erm = aerm->rowmark;
79 : : Datum datum;
80 : : bool isNull;
81 : : ItemPointerData tid;
82 : : TM_FailureData tmfd;
83 : : LockTupleMode lockmode;
1438 alvherre@alvh.no-ip. 84 : 481906 : int lockflags = 0;
85 : : TM_Result test;
86 : : TupleTableSlot *markSlot;
87 : :
88 : : /* clear any leftover test tuple for this rel */
2571 andres@anarazel.de 89 : 481906 : markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
90 : 481906 : ExecClearTuple(markSlot);
91 : :
92 : : /* if child rel, must check whether it produced this row */
5998 tgl@sss.pgh.pa.us 93 [ + + ]: 481906 : if (erm->rti != erm->prti)
94 : : {
95 : : Oid tableoid;
96 : :
97 : 781 : datum = ExecGetJunkAttribute(slot,
5541 98 : 781 : aerm->toidAttNo,
99 : : &isNull);
100 : : /* shouldn't ever get a null result... */
5998 101 [ - + ]: 781 : if (isNull)
5998 tgl@sss.pgh.pa.us 102 [ # # ]:UBC 0 : elog(ERROR, "tableoid is NULL");
5998 tgl@sss.pgh.pa.us 103 :CBC 781 : tableoid = DatumGetObjectId(datum);
104 : :
4011 105 [ - + ]: 781 : Assert(OidIsValid(erm->relid));
106 [ + + ]: 781 : if (tableoid != erm->relid)
107 : : {
108 : : /* this child is inactive right now */
3960 109 : 158 : erm->ermActive = false;
5998 110 : 158 : ItemPointerSetInvalid(&(erm->curCtid));
111 : 158 : continue;
112 : : }
113 : : }
3960 114 : 481748 : erm->ermActive = true;
115 : :
116 : : /* fetch the tuple's ctid */
5998 117 : 481748 : datum = ExecGetJunkAttribute(slot,
5541 118 : 481748 : aerm->ctidAttNo,
119 : : &isNull);
120 : : /* shouldn't ever get a null result... */
5998 121 [ - + ]: 481748 : if (isNull)
5998 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 */
3960 tgl@sss.pgh.pa.us 125 [ - + ]:CBC 481748 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
3960 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 : :
2571 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 */
3960 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 */
2549 andres@anarazel.de 160 :CBC 481748 : tid = *((ItemPointer) DatumGetPointer(datum));
4799 alvherre@alvh.no-ip. 161 [ + + + + : 481748 : switch (erm->markType)
- ]
162 : : {
163 : 3931 : case ROW_MARK_EXCLUSIVE:
164 : 3931 : lockmode = LockTupleExclusive;
165 : 3931 : break;
166 : 39 : case ROW_MARK_NOKEYEXCLUSIVE:
167 : 39 : lockmode = LockTupleNoKeyExclusive;
168 : 39 : break;
169 : 1161 : case ROW_MARK_SHARE:
170 : 1161 : lockmode = LockTupleShare;
171 : 1161 : break;
172 : 476617 : case ROW_MARK_KEYSHARE:
173 : 476617 : lockmode = LockTupleKeyShare;
174 : 476617 : break;
4799 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 : :
1438 alvherre@alvh.no-ip. 181 :CBC 481748 : lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
182 [ + + ]: 481748 : if (!IsolationUsesXactSnapshot())
183 : 478029 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
184 : :
185 : 481748 : 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 [ + + + + : 481731 : 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 : 481661 : 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 [ + + ]: 481661 : if (tmfd.traversed)
221 : 35 : epq_needed = true;
222 : 481661 : break;
223 : :
224 : 16 : case TM_Updated:
225 [ + - ]: 16 : if (IsolationUsesXactSnapshot())
226 [ + - ]: 16 : ereport(ERROR,
227 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
228 : : errmsg("could not serialize access due to concurrent update")));
1438 alvherre@alvh.no-ip. 229 [ # # ]:UBC 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
230 : : test);
231 : : break;
232 : :
1438 alvherre@alvh.no-ip. 233 :CBC 14 : case TM_Deleted:
234 [ + + ]: 14 : if (IsolationUsesXactSnapshot())
235 [ + - ]: 6 : 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 : 8 : goto lnext;
240 : :
1438 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 */
2549 andres@anarazel.de 251 :CBC 481661 : erm->curCtid = tid;
252 : : }
253 : :
254 : : /*
255 : : * If we need to do EvalPlanQual testing, do so.
256 : : */
3960 tgl@sss.pgh.pa.us 257 [ + + ]: 481612 : if (epq_needed)
258 : : {
259 : : /* Initialize EPQ machinery */
2383 andres@anarazel.de 260 : 34 : 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 : : */
5984 tgl@sss.pgh.pa.us 266 : 34 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
267 : :
268 : : /*
269 : : * And finally we can re-evaluate the tuple.
270 : : */
271 : 34 : slot = EvalPlanQualNext(&node->lr_epqstate);
272 [ + + + + ]: 34 : if (TupIsNull(slot))
273 : : {
274 : : /* Updated tuple fails qual, so ignore it and go on */
275 : 9 : goto lnext;
276 : : }
277 : : }
278 : :
279 : : /* Got all locks, so return the current tuple */
5998 280 : 481603 : 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 : 407021 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
292 : : {
293 : : LockRowsState *lrstate;
5984 294 : 407021 : Plan *outerPlan = outerPlan(node);
295 : : List *epq_arowmarks;
296 : : ListCell *lc;
297 : :
298 : : /* check for unsupported flags */
5998 299 [ - + ]: 407021 : Assert(!(eflags & EXEC_FLAG_MARK));
300 : :
301 : : /*
302 : : * create state structure
303 : : */
304 : 407021 : lrstate = makeNode(LockRowsState);
305 : 407021 : lrstate->ps.plan = (Plan *) node;
306 : 407021 : lrstate->ps.state = estate;
3163 andres@anarazel.de 307 : 407021 : 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 : : */
2683 319 : 407021 : ExecInitResultTypeTL(&lrstate->ps);
320 : :
321 : : /*
322 : : * then initialize outer plan
323 : : */
5998 tgl@sss.pgh.pa.us 324 : 407021 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
325 : :
326 : : /* node returns unmodified slots from the outer plan */
2677 andres@anarazel.de 327 : 407021 : lrstate->ps.resultopsset = true;
328 : 407021 : 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 : : */
5998 tgl@sss.pgh.pa.us 335 : 407021 : 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 : : */
5541 342 : 407021 : lrstate->lr_arowMarks = NIL;
343 : 407021 : epq_arowmarks = NIL;
5998 344 [ + - + + : 815264 : foreach(lc, node->rowMarks)
+ + ]
345 : : {
3261 346 : 408243 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
58 amitlan@postgresql.o 347 : 408243 : RangeTblEntry *rte = exec_rt_fetch(rc->rti, estate);
348 : : ExecRowMark *erm;
349 : : ExecAuxRowMark *aerm;
350 : :
351 : : /* ignore "parent" rowmarks; they are irrelevant at runtime */
352 [ + + ]: 408243 : if (rc->isParent)
353 : 923 : continue;
354 : :
355 : : /*
356 : : * Also ignore rowmarks belonging to child tables that have been
357 : : * pruned in ExecDoInitialPruning().
358 : : */
359 [ + + ]: 407320 : if (rte->rtekind == RTE_RELATION &&
401 360 [ + + ]: 407319 : !bms_is_member(rc->rti, estate->es_unpruned_relids))
5998 tgl@sss.pgh.pa.us 361 : 36 : continue;
362 : :
363 : : /* find ExecRowMark and build ExecAuxRowMark */
3960 364 : 407284 : erm = ExecFindRowMark(estate, rc->rti, false);
5541 365 : 407284 : aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
366 : :
367 : : /*
368 : : * Only locking rowmarks go into our own list. Non-locking marks are
369 : : * passed off to the EvalPlanQual machinery. This is because we don't
370 : : * want to bother fetching non-locked rows unless we actually have to
371 : : * do an EPQ recheck.
372 : : */
5984 373 [ + + ]: 407284 : if (RowMarkRequiresRowShareLock(erm->markType))
5541 374 : 407122 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
375 : : else
376 : 162 : epq_arowmarks = lappend(epq_arowmarks, aerm);
377 : : }
378 : :
379 : : /* Now we have the info needed to set up EPQ state */
380 : 407021 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
381 : : outerPlan, epq_arowmarks, node->epqParam, NIL);
382 : :
5998 383 : 407021 : return lrstate;
384 : : }
385 : :
386 : : /* ----------------------------------------------------------------
387 : : * ExecEndLockRows
388 : : *
389 : : * This shuts down the subplan and frees resources allocated
390 : : * to this node.
391 : : * ----------------------------------------------------------------
392 : : */
393 : : void
394 : 406962 : ExecEndLockRows(LockRowsState *node)
395 : : {
396 : : /* We may have shut down EPQ already, but no harm in another call */
5984 397 : 406962 : EvalPlanQualEnd(&node->lr_epqstate);
5998 398 : 406962 : ExecEndNode(outerPlanState(node));
399 : 406962 : }
400 : :
401 : :
402 : : void
5725 403 : 8 : ExecReScanLockRows(LockRowsState *node)
404 : : {
1347 405 : 8 : PlanState *outerPlan = outerPlanState(node);
406 : :
407 : : /*
408 : : * if chgParam of subnode is not null then plan will be re-scanned by
409 : : * first ExecProcNode.
410 : : */
411 [ - + ]: 8 : if (outerPlan->chgParam == NULL)
1347 tgl@sss.pgh.pa.us 412 :UBC 0 : ExecReScan(outerPlan);
5998 tgl@sss.pgh.pa.us 413 :CBC 8 : }
|