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 */
3214 andres@anarazel.de 38 :CBC 85605 : ExecLockRows(PlanState *pstate)
39 : : {
40 : 85605 : LockRowsState *node = castNode(LockRowsState, pstate);
41 : : TupleTableSlot *slot;
42 : : EState *estate;
43 : : PlanState *outerPlan;
44 : : bool epq_needed;
45 : : ListCell *lc;
46 : :
3206 47 [ - + ]: 85605 : CHECK_FOR_INTERRUPTS();
48 : :
49 : : /*
50 : : * get information from the node
51 : : */
6049 tgl@sss.pgh.pa.us 52 : 85605 : estate = node->ps.state;
53 : 85605 : outerPlan = outerPlanState(node);
54 : :
55 : : /*
56 : : * Get next tuple from subplan, if any.
57 : : */
58 : 65 : lnext:
6035 59 : 85670 : slot = ExecProcNode(outerPlan);
60 : :
6049 61 [ + + + + ]: 85670 : if (TupIsNull(slot))
62 : : {
63 : : /* Release any resources held by EPQ mechanism before exiting */
1790 64 : 4722 : EvalPlanQualEnd(&node->lr_epqstate);
6049 65 : 4722 : return NULL;
66 : : }
67 : :
68 : : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
4011 69 : 80948 : epq_needed = false;
70 : :
71 : : /*
72 : : * Attempt to lock the source tuple(s). (Note we only have locking
73 : : * rowmarks in lr_arowMarks.)
74 : : */
5592 75 [ + + + + : 162053 : foreach(lc, node->lr_arowMarks)
+ + ]
76 : : {
77 : 81193 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
78 : 81193 : ExecRowMark *erm = aerm->rowmark;
79 : : Datum datum;
80 : : bool isNull;
81 : : ItemPointerData tid;
82 : : TM_FailureData tmfd;
83 : : LockTupleMode lockmode;
1489 alvherre@alvh.no-ip. 84 : 81193 : int lockflags = 0;
85 : : TM_Result test;
86 : : TupleTableSlot *markSlot;
87 : :
88 : : /* clear any leftover test tuple for this rel */
2622 andres@anarazel.de 89 : 81193 : markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
90 : 81193 : ExecClearTuple(markSlot);
91 : :
92 : : /* if child rel, must check whether it produced this row */
6049 tgl@sss.pgh.pa.us 93 [ + + ]: 81193 : if (erm->rti != erm->prti)
94 : : {
95 : : Oid tableoid;
96 : :
97 : 989 : datum = ExecGetJunkAttribute(slot,
5592 98 : 989 : aerm->toidAttNo,
99 : : &isNull);
100 : : /* shouldn't ever get a null result... */
6049 101 [ - + ]: 989 : if (isNull)
6049 tgl@sss.pgh.pa.us 102 [ # # ]:UBC 0 : elog(ERROR, "tableoid is NULL");
6049 tgl@sss.pgh.pa.us 103 :CBC 989 : tableoid = DatumGetObjectId(datum);
104 : :
4062 105 [ - + ]: 989 : Assert(OidIsValid(erm->relid));
106 [ + + ]: 989 : if (tableoid != erm->relid)
107 : : {
108 : : /* this child is inactive right now */
4011 109 : 195 : erm->ermActive = false;
6049 110 : 195 : ItemPointerSetInvalid(&(erm->curCtid));
111 : 195 : continue;
112 : : }
113 : : }
4011 114 : 80998 : erm->ermActive = true;
115 : :
116 : : /* fetch the tuple's ctid */
6049 117 : 80998 : datum = ExecGetJunkAttribute(slot,
5592 118 : 80998 : aerm->ctidAttNo,
119 : : &isNull);
120 : : /* shouldn't ever get a null result... */
6049 121 [ - + ]: 80998 : if (isNull)
6049 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 */
4011 tgl@sss.pgh.pa.us 125 [ - + ]:CBC 80998 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
4011 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 : :
2622 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 */
4011 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 */
2600 andres@anarazel.de 160 :CBC 80998 : tid = *((ItemPointer) DatumGetPointer(datum));
4850 alvherre@alvh.no-ip. 161 [ + + + + : 80998 : switch (erm->markType)
- ]
162 : : {
163 : 4037 : case ROW_MARK_EXCLUSIVE:
164 : 4037 : lockmode = LockTupleExclusive;
165 : 4037 : break;
166 : 40 : case ROW_MARK_NOKEYEXCLUSIVE:
167 : 40 : lockmode = LockTupleNoKeyExclusive;
168 : 40 : break;
169 : 1239 : case ROW_MARK_SHARE:
170 : 1239 : lockmode = LockTupleShare;
171 : 1239 : break;
172 : 75682 : case ROW_MARK_KEYSHARE:
173 : 75682 : lockmode = LockTupleKeyShare;
174 : 75682 : break;
4850 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 : :
1489 alvherre@alvh.no-ip. 181 :CBC 80998 : lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
182 [ + + ]: 80998 : if (!IsolationUsesXactSnapshot())
183 : 77297 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
184 : :
185 : 80998 : 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 [ + + + + : 80984 : switch (test)
+ - - ]
192 : : {
193 : 37 : case TM_WouldBlock:
194 : : /* couldn't lock tuple in SKIP LOCKED mode */
195 : 37 : goto lnext;
196 : :
197 : 4 : 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 : 4 : goto lnext;
213 : :
214 : 80910 : 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 [ + + ]: 80910 : if (tmfd.traversed)
221 : 41 : epq_needed = true;
222 : 80910 : 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")));
1489 alvherre@alvh.no-ip. 229 [ # # ]:UBC 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
230 : : test);
231 : : break;
232 : :
1489 alvherre@alvh.no-ip. 233 :CBC 17 : case TM_Deleted:
234 [ + + ]: 17 : if (IsolationUsesXactSnapshot())
235 [ + - ]: 4 : ereport(ERROR,
236 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
237 : : errmsg("could not serialize access due to concurrent delete")));
238 : : /* tuple was deleted so don't return it */
239 : 13 : goto lnext;
240 : :
1489 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 */
2600 andres@anarazel.de 251 :CBC 80910 : erm->curCtid = tid;
252 : : }
253 : :
254 : : /*
255 : : * If we need to do EvalPlanQual testing, do so.
256 : : */
4011 tgl@sss.pgh.pa.us 257 [ + + ]: 80860 : if (epq_needed)
258 : : {
259 : : /* Initialize EPQ machinery */
2434 andres@anarazel.de 260 : 40 : 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 : : */
6035 tgl@sss.pgh.pa.us 266 : 40 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
267 : :
268 : : /*
269 : : * And finally we can re-evaluate the tuple.
270 : : */
271 : 40 : slot = EvalPlanQualNext(&node->lr_epqstate);
272 [ + + + + ]: 40 : if (TupIsNull(slot))
273 : : {
274 : : /* Updated tuple fails qual, so ignore it and go on */
275 : 11 : goto lnext;
276 : : }
277 : : }
278 : :
279 : : /* Got all locks, so return the current tuple */
6049 280 : 80849 : 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 : 6163 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
292 : : {
293 : : LockRowsState *lrstate;
6035 294 : 6163 : Plan *outerPlan = outerPlan(node);
295 : : List *epq_arowmarks;
296 : : ListCell *lc;
297 : :
298 : : /* check for unsupported flags */
6049 299 [ - + ]: 6163 : Assert(!(eflags & EXEC_FLAG_MARK));
300 : :
301 : : /*
302 : : * create state structure
303 : : */
304 : 6163 : lrstate = makeNode(LockRowsState);
305 : 6163 : lrstate->ps.plan = (Plan *) node;
306 : 6163 : lrstate->ps.state = estate;
3214 andres@anarazel.de 307 : 6163 : 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 : : */
2734 319 : 6163 : ExecInitResultTypeTL(&lrstate->ps);
320 : :
321 : : /*
322 : : * then initialize outer plan
323 : : */
6049 tgl@sss.pgh.pa.us 324 : 6163 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
325 : :
326 : : /* node returns unmodified slots from the outer plan */
2728 andres@anarazel.de 327 : 6163 : lrstate->ps.resultopsset = true;
328 : 6163 : 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 : : */
6049 tgl@sss.pgh.pa.us 335 : 6163 : 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 : : */
5592 342 : 6163 : lrstate->lr_arowMarks = NIL;
343 : 6163 : epq_arowmarks = NIL;
6049 344 [ + - + + : 13858 : foreach(lc, node->rowMarks)
+ + ]
345 : : {
3312 346 : 7695 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
109 amitlan@postgresql.o 347 : 7695 : 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 [ + + ]: 7695 : if (rc->isParent)
353 : 1184 : continue;
354 : :
355 : : /*
356 : : * Also ignore rowmarks belonging to child tables that have been
357 : : * pruned in ExecDoInitialPruning().
358 : : */
359 [ + + ]: 6511 : if (rte->rtekind == RTE_RELATION &&
452 360 [ + + ]: 6510 : !bms_is_member(rc->rti, estate->es_unpruned_relids))
6049 tgl@sss.pgh.pa.us 361 : 48 : continue;
362 : :
363 : : /* find ExecRowMark and build ExecAuxRowMark */
4011 364 : 6463 : erm = ExecFindRowMark(estate, rc->rti, false);
5592 365 : 6463 : 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 : : */
6035 373 [ + + ]: 6463 : if (RowMarkRequiresRowShareLock(erm->markType))
5592 374 : 6293 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
375 : : else
376 : 170 : epq_arowmarks = lappend(epq_arowmarks, aerm);
377 : : }
378 : :
379 : : /* Now we have the info needed to set up EPQ state */
380 : 6163 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
381 : : outerPlan, epq_arowmarks, node->epqParam, NIL);
382 : :
6049 383 : 6163 : 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 : 6105 : ExecEndLockRows(LockRowsState *node)
395 : : {
396 : : /* We may have shut down EPQ already, but no harm in another call */
6035 397 : 6105 : EvalPlanQualEnd(&node->lr_epqstate);
6049 398 : 6105 : ExecEndNode(outerPlanState(node));
399 : 6105 : }
400 : :
401 : :
402 : : void
5776 403 : 8 : ExecReScanLockRows(LockRowsState *node)
404 : : {
1398 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)
1398 tgl@sss.pgh.pa.us 412 :UBC 0 : ExecReScan(outerPlan);
6049 tgl@sss.pgh.pa.us 413 :CBC 8 : }
|