Branch data 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 */
38 : 400924 : ExecLockRows(PlanState *pstate)
39 : : {
40 : 400924 : LockRowsState *node = castNode(LockRowsState, pstate);
41 : 400924 : TupleTableSlot *slot;
42 : 400924 : EState *estate;
43 : 400924 : PlanState *outerPlan;
44 : 400924 : bool epq_needed;
45 : 400924 : ListCell *lc;
46 : :
47 [ + - ]: 400924 : CHECK_FOR_INTERRUPTS();
48 : :
49 : : /*
50 : : * get information from the node
51 : : */
52 : 400924 : estate = node->ps.state;
53 : 400924 : outerPlan = outerPlanState(node);
54 : :
55 : : /*
56 : : * Get next tuple from subplan, if any.
57 : : */
58 : : lnext:
59 : 400925 : slot = ExecProcNode(outerPlan);
60 : :
61 [ + + + + ]: 400925 : if (TupIsNull(slot))
62 : : {
63 : : /* Release any resources held by EPQ mechanism before exiting */
64 : 379 : EvalPlanQualEnd(&node->lr_epqstate);
65 : 379 : return NULL;
66 : : }
67 : :
68 : : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
69 : 400546 : epq_needed = false;
70 : :
71 : : /*
72 : : * Attempt to lock the source tuple(s). (Note we only have locking
73 : : * rowmarks in lr_arowMarks.)
74 : : */
75 [ + - + + : 801130 : foreach(lc, node->lr_arowMarks)
+ + - +
+ ]
76 : : {
77 : 400584 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
78 : 400584 : ExecRowMark *erm = aerm->rowmark;
79 : 400584 : Datum datum;
80 : 400584 : bool isNull;
81 : 400584 : ItemPointerData tid;
82 : 400584 : TM_FailureData tmfd;
83 : 400584 : LockTupleMode lockmode;
84 : 400584 : int lockflags = 0;
85 : 400584 : TM_Result test;
86 : 400584 : TupleTableSlot *markSlot;
87 : :
88 : : /* clear any leftover test tuple for this rel */
89 : 400584 : markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
90 : 400584 : ExecClearTuple(markSlot);
91 : :
92 : : /* if child rel, must check whether it produced this row */
93 [ + + ]: 400584 : if (erm->rti != erm->prti)
94 : : {
95 : 184 : Oid tableoid;
96 : :
97 : 368 : datum = ExecGetJunkAttribute(slot,
98 : 184 : aerm->toidAttNo,
99 : : &isNull);
100 : : /* shouldn't ever get a null result... */
101 [ + - ]: 184 : if (isNull)
102 [ # # # # ]: 0 : elog(ERROR, "tableoid is NULL");
103 : 184 : tableoid = DatumGetObjectId(datum);
104 : :
105 [ + - ]: 184 : Assert(OidIsValid(erm->relid));
106 [ + + ]: 184 : if (tableoid != erm->relid)
107 : : {
108 : : /* this child is inactive right now */
109 : 37 : erm->ermActive = false;
110 : 37 : ItemPointerSetInvalid(&(erm->curCtid));
111 : 37 : continue;
112 : : }
113 [ + + ]: 184 : }
114 : 400547 : erm->ermActive = true;
115 : :
116 : : /* fetch the tuple's ctid */
117 : 801094 : datum = ExecGetJunkAttribute(slot,
118 : 400547 : aerm->ctidAttNo,
119 : : &isNull);
120 : : /* shouldn't ever get a null result... */
121 [ + - ]: 400547 : if (isNull)
122 [ # # # # ]: 0 : elog(ERROR, "ctid is NULL");
123 : :
124 : : /* requests for foreign tables must be passed to their FDW */
125 [ - + ]: 400547 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
126 : : {
127 : 0 : 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 : :
138 : 0 : fdwroutine->RefetchForeignRow(estate,
139 : 0 : erm,
140 : 0 : datum,
141 : 0 : markSlot,
142 : : &updated);
143 [ # # # # ]: 0 : if (TupIsNull(markSlot))
144 : : {
145 : : /* couldn't get the lock, so skip this row */
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 : 0 : }
158 : :
159 : : /* okay, try to lock (and fetch) the tuple */
160 : 400547 : tid = *((ItemPointer) DatumGetPointer(datum));
161 [ + + + + : 400547 : switch (erm->markType)
- ]
162 : : {
163 : : case ROW_MARK_EXCLUSIVE:
164 : 22 : lockmode = LockTupleExclusive;
165 : 22 : break;
166 : : case ROW_MARK_NOKEYEXCLUSIVE:
167 : 1 : lockmode = LockTupleNoKeyExclusive;
168 : 1 : break;
169 : : case ROW_MARK_SHARE:
170 : 12 : lockmode = LockTupleShare;
171 : 12 : break;
172 : : case ROW_MARK_KEYSHARE:
173 : 400512 : lockmode = LockTupleKeyShare;
174 : 400512 : break;
175 : : default:
176 [ # # # # ]: 0 : elog(ERROR, "unsupported rowmark type");
177 : 0 : lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
178 : 0 : break;
179 : : }
180 : :
181 : 400547 : lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
182 [ + + ]: 400547 : if (!IsolationUsesXactSnapshot())
183 : 400546 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
184 : :
185 : 801094 : test = table_tuple_lock(erm->relation, &tid, estate->es_snapshot,
186 : 400547 : markSlot, estate->es_output_cid,
187 : 400547 : lockmode, erm->waitPolicy,
188 : 400547 : lockflags,
189 : : &tmfd);
190 : :
191 [ - - - + : 400547 : switch (test)
+ - - ]
192 : : {
193 : : case TM_WouldBlock:
194 : : /* couldn't lock tuple in SKIP LOCKED mode */
195 : 0 : goto lnext;
196 : :
197 : : 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 : 1 : goto lnext;
213 : :
214 : : 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 [ + - ]: 400546 : if (tmfd.traversed)
221 : 0 : epq_needed = true;
222 : 400546 : break;
223 : :
224 : : case TM_Updated:
225 [ # # ]: 0 : if (IsolationUsesXactSnapshot())
226 [ # # # # ]: 0 : ereport(ERROR,
227 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
228 : : errmsg("could not serialize access due to concurrent update")));
229 [ # # # # ]: 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
230 : : test);
231 : 0 : break;
232 : :
233 : : case TM_Deleted:
234 [ # # ]: 0 : if (IsolationUsesXactSnapshot())
235 [ # # # # ]: 0 : 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 : 0 : goto lnext;
240 : :
241 : : case TM_Invisible:
242 [ # # # # ]: 0 : elog(ERROR, "attempted to lock invisible tuple");
243 : 0 : break;
244 : :
245 : : default:
246 [ # # # # ]: 0 : elog(ERROR, "unrecognized table_tuple_lock status: %u",
247 : : test);
248 : 0 : }
249 : :
250 : : /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
251 : 400546 : erm->curCtid = tid;
252 [ + + + ]: 400584 : }
253 : :
254 : : /*
255 : : * If we need to do EvalPlanQual testing, do so.
256 : : */
257 [ + - ]: 400545 : if (epq_needed)
258 : : {
259 : : /* Initialize EPQ machinery */
260 : 0 : 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 : : */
266 : 0 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
267 : :
268 : : /*
269 : : * And finally we can re-evaluate the tuple.
270 : : */
271 : 0 : slot = EvalPlanQualNext(&node->lr_epqstate);
272 [ # # # # ]: 0 : if (TupIsNull(slot))
273 : : {
274 : : /* Updated tuple fails qual, so ignore it and go on */
275 : 0 : goto lnext;
276 : : }
277 : 0 : }
278 : :
279 : : /* Got all locks, so return the current tuple */
280 : 400545 : return slot;
281 : 400924 : }
282 : :
283 : : /* ----------------------------------------------------------------
284 : : * ExecInitLockRows
285 : : *
286 : : * This initializes the LockRows node state structures and
287 : : * the node's subplan.
288 : : * ----------------------------------------------------------------
289 : : */
290 : : LockRowsState *
291 : 400832 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
292 : : {
293 : 400832 : LockRowsState *lrstate;
294 : 400832 : Plan *outerPlan = outerPlan(node);
295 : 400832 : List *epq_arowmarks;
296 : 400832 : ListCell *lc;
297 : :
298 : : /* check for unsupported flags */
299 [ + - ]: 400832 : Assert(!(eflags & EXEC_FLAG_MARK));
300 : :
301 : : /*
302 : : * create state structure
303 : : */
304 : 400832 : lrstate = makeNode(LockRowsState);
305 : 400832 : lrstate->ps.plan = (Plan *) node;
306 : 400832 : lrstate->ps.state = estate;
307 : 400832 : 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 : : */
319 : 400832 : ExecInitResultTypeTL(&lrstate->ps);
320 : :
321 : : /*
322 : : * then initialize outer plan
323 : : */
324 : 400832 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
325 : :
326 : : /* node returns unmodified slots from the outer plan */
327 : 400832 : lrstate->ps.resultopsset = true;
328 : 801664 : lrstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(lrstate),
329 : 400832 : &lrstate->ps.resultopsfixed);
330 : :
331 : : /*
332 : : * LockRows nodes do no projections, so initialize projection info for
333 : : * this node appropriately
334 : : */
335 : 400832 : 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 : : */
342 : 400832 : lrstate->lr_arowMarks = NIL;
343 : 400832 : epq_arowmarks = NIL;
344 [ + - + + : 801950 : foreach(lc, node->rowMarks)
+ + ]
345 : : {
346 : 401118 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
347 : 401118 : RangeTblEntry *rte = exec_rt_fetch(rc->rti, estate);
348 : 401118 : ExecRowMark *erm;
349 : 401118 : ExecAuxRowMark *aerm;
350 : :
351 : : /* ignore "parent" rowmarks; they are irrelevant at runtime */
352 [ + + ]: 401118 : if (rc->isParent)
353 : 237 : continue;
354 : :
355 : : /*
356 : : * Also ignore rowmarks belonging to child tables that have been
357 : : * pruned in ExecDoInitialPruning().
358 : : */
359 [ + - + + ]: 400881 : if (rte->rtekind == RTE_RELATION &&
360 : 400881 : !bms_is_member(rc->rti, estate->es_unpruned_relids))
361 : 12 : continue;
362 : :
363 : : /* find ExecRowMark and build ExecAuxRowMark */
364 : 400869 : erm = ExecFindRowMark(estate, rc->rti, false);
365 : 400869 : 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 : : */
373 [ + + ]: 400869 : if (RowMarkRequiresRowShareLock(erm->markType))
374 : 400861 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
375 : : else
376 : 8 : epq_arowmarks = lappend(epq_arowmarks, aerm);
377 [ - + + ]: 401118 : }
378 : :
379 : : /* Now we have the info needed to set up EPQ state */
380 : 801664 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
381 : 400832 : outerPlan, epq_arowmarks, node->epqParam, NIL);
382 : :
383 : 801664 : return lrstate;
384 : 400832 : }
385 : :
386 : : /* ----------------------------------------------------------------
387 : : * ExecEndLockRows
388 : : *
389 : : * This shuts down the subplan and frees resources allocated
390 : : * to this node.
391 : : * ----------------------------------------------------------------
392 : : */
393 : : void
394 : 400828 : ExecEndLockRows(LockRowsState *node)
395 : : {
396 : : /* We may have shut down EPQ already, but no harm in another call */
397 : 400828 : EvalPlanQualEnd(&node->lr_epqstate);
398 : 400828 : ExecEndNode(outerPlanState(node));
399 : 400828 : }
400 : :
401 : :
402 : : void
403 : 0 : ExecReScanLockRows(LockRowsState *node)
404 : : {
405 : 0 : 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 [ # # ]: 0 : if (outerPlan->chgParam == NULL)
412 : 0 : ExecReScan(outerPlan);
413 : 0 : }
|