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-2024, 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 */
2463 andres@anarazel.de 38 :CBC 7616 : ExecLockRows(PlanState *pstate)
39 : : {
40 : 7616 : LockRowsState *node = castNode(LockRowsState, pstate);
41 : : TupleTableSlot *slot;
42 : : EState *estate;
43 : : PlanState *outerPlan;
44 : : bool epq_needed;
45 : : ListCell *lc;
46 : :
2455 47 [ - + ]: 7616 : CHECK_FOR_INTERRUPTS();
48 : :
49 : : /*
50 : : * get information from the node
51 : : */
5298 tgl@sss.pgh.pa.us 52 : 7616 : estate = node->ps.state;
53 : 7616 : outerPlan = outerPlanState(node);
54 : :
55 : : /*
56 : : * Get next tuple from subplan, if any.
57 : : */
58 : 51 : lnext:
5284 59 : 7667 : slot = ExecProcNode(outerPlan);
60 : :
5298 61 [ + + + + ]: 7667 : if (TupIsNull(slot))
62 : : {
63 : : /* Release any resources held by EPQ mechanism before exiting */
1039 64 : 1558 : EvalPlanQualEnd(&node->lr_epqstate);
5298 65 : 1558 : return NULL;
66 : : }
67 : :
68 : : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
3260 69 : 6109 : epq_needed = false;
70 : :
71 : : /*
72 : : * Attempt to lock the source tuple(s). (Note we only have locking
73 : : * rowmarks in lr_arowMarks.)
74 : : */
4841 75 [ + + + + : 12324 : foreach(lc, node->lr_arowMarks)
+ + ]
76 : : {
77 : 6287 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
78 : 6287 : ExecRowMark *erm = aerm->rowmark;
79 : : Datum datum;
80 : : bool isNull;
81 : : ItemPointerData tid;
82 : : TM_FailureData tmfd;
83 : : LockTupleMode lockmode;
738 alvherre@alvh.no-ip. 84 : 6287 : int lockflags = 0;
85 : : TM_Result test;
86 : : TupleTableSlot *markSlot;
87 : :
88 : : /* clear any leftover test tuple for this rel */
1871 andres@anarazel.de 89 : 6287 : markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
90 : 6287 : ExecClearTuple(markSlot);
91 : :
92 : : /* if child rel, must check whether it produced this row */
5298 tgl@sss.pgh.pa.us 93 [ + + ]: 6287 : if (erm->rti != erm->prti)
94 : : {
95 : : Oid tableoid;
96 : :
97 : 721 : datum = ExecGetJunkAttribute(slot,
4841 98 : 721 : aerm->toidAttNo,
99 : : &isNull);
100 : : /* shouldn't ever get a null result... */
5298 101 [ - + ]: 721 : if (isNull)
5298 tgl@sss.pgh.pa.us 102 [ # # ]:UBC 0 : elog(ERROR, "tableoid is NULL");
5298 tgl@sss.pgh.pa.us 103 :CBC 721 : tableoid = DatumGetObjectId(datum);
104 : :
3311 105 [ - + ]: 721 : Assert(OidIsValid(erm->relid));
106 [ + + ]: 721 : if (tableoid != erm->relid)
107 : : {
108 : : /* this child is inactive right now */
3260 109 : 161 : erm->ermActive = false;
5298 110 : 161 : ItemPointerSetInvalid(&(erm->curCtid));
111 : 161 : continue;
112 : : }
113 : : }
3260 114 : 6126 : erm->ermActive = true;
115 : :
116 : : /* fetch the tuple's ctid */
5298 117 : 6126 : datum = ExecGetJunkAttribute(slot,
4841 118 : 6126 : aerm->ctidAttNo,
119 : : &isNull);
120 : : /* shouldn't ever get a null result... */
5298 121 [ - + ]: 6126 : if (isNull)
5298 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 */
3260 tgl@sss.pgh.pa.us 125 [ - + ]:CBC 6126 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
3260 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 : :
1871 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 */
3260 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 */
1849 andres@anarazel.de 160 :CBC 6126 : tid = *((ItemPointer) DatumGetPointer(datum));
4099 alvherre@alvh.no-ip. 161 [ + + + + : 6126 : switch (erm->markType)
- ]
162 : : {
163 : 1818 : case ROW_MARK_EXCLUSIVE:
164 : 1818 : lockmode = LockTupleExclusive;
165 : 1818 : break;
166 : 31 : case ROW_MARK_NOKEYEXCLUSIVE:
167 : 31 : lockmode = LockTupleNoKeyExclusive;
168 : 31 : break;
169 : 1146 : case ROW_MARK_SHARE:
170 : 1146 : lockmode = LockTupleShare;
171 : 1146 : break;
172 : 3131 : case ROW_MARK_KEYSHARE:
173 : 3131 : lockmode = LockTupleKeyShare;
174 : 3131 : break;
4099 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 : :
738 alvherre@alvh.no-ip. 181 :CBC 6126 : lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
182 [ + + ]: 6126 : if (!IsolationUsesXactSnapshot())
183 : 4513 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
184 : :
185 : 6126 : 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 [ + + + + : 6113 : 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 : 6054 : 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 [ + + ]: 6054 : if (tmfd.traversed)
221 : 28 : epq_needed = true;
222 : 6054 : 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")));
738 alvherre@alvh.no-ip. 229 [ # # ]:UBC 0 : elog(ERROR, "unexpected table_tuple_lock status: %u",
230 : : test);
231 : : break;
232 : :
738 alvherre@alvh.no-ip. 233 :CBC 7 : case TM_Deleted:
234 [ + + ]: 7 : 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 : 5 : goto lnext;
240 : :
738 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 */
1849 andres@anarazel.de 251 :CBC 6054 : erm->curCtid = tid;
252 : : }
253 : :
254 : : /*
255 : : * If we need to do EvalPlanQual testing, do so.
256 : : */
3260 tgl@sss.pgh.pa.us 257 [ + + ]: 6037 : if (epq_needed)
258 : : {
259 : : /* Initialize EPQ machinery */
1683 andres@anarazel.de 260 : 27 : 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 : : */
5284 tgl@sss.pgh.pa.us 266 : 27 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
267 : :
268 : : /*
269 : : * And finally we can re-evaluate the tuple.
270 : : */
271 : 27 : slot = EvalPlanQualNext(&node->lr_epqstate);
272 [ + + + + ]: 27 : 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 */
5298 280 : 6031 : 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 : 3957 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
292 : : {
293 : : LockRowsState *lrstate;
5284 294 : 3957 : Plan *outerPlan = outerPlan(node);
295 : : List *epq_arowmarks;
296 : : ListCell *lc;
297 : :
298 : : /* check for unsupported flags */
5298 299 [ - + ]: 3957 : Assert(!(eflags & EXEC_FLAG_MARK));
300 : :
301 : : /*
302 : : * create state structure
303 : : */
304 : 3957 : lrstate = makeNode(LockRowsState);
305 : 3957 : lrstate->ps.plan = (Plan *) node;
306 : 3957 : lrstate->ps.state = estate;
2463 andres@anarazel.de 307 : 3957 : 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 : : */
1983 319 : 3957 : ExecInitResultTypeTL(&lrstate->ps);
320 : :
321 : : /*
322 : : * then initialize outer plan
323 : : */
5298 tgl@sss.pgh.pa.us 324 : 3957 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
325 : :
326 : : /* node returns unmodified slots from the outer plan */
1977 andres@anarazel.de 327 : 3957 : lrstate->ps.resultopsset = true;
328 : 3957 : 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 : : */
5298 tgl@sss.pgh.pa.us 335 : 3957 : 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 : : */
4841 342 : 3957 : lrstate->lr_arowMarks = NIL;
343 : 3957 : epq_arowmarks = NIL;
5298 344 [ + - + + : 8965 : foreach(lc, node->rowMarks)
+ + ]
345 : : {
2561 346 : 5008 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
347 : : ExecRowMark *erm;
348 : : ExecAuxRowMark *aerm;
349 : :
350 : : /* ignore "parent" rowmarks; they are irrelevant at runtime */
5298 351 [ + + ]: 5008 : if (rc->isParent)
352 : 824 : continue;
353 : :
354 : : /* find ExecRowMark and build ExecAuxRowMark */
3260 355 : 4184 : erm = ExecFindRowMark(estate, rc->rti, false);
4841 356 : 4184 : aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
357 : :
358 : : /*
359 : : * Only locking rowmarks go into our own list. Non-locking marks are
360 : : * passed off to the EvalPlanQual machinery. This is because we don't
361 : : * want to bother fetching non-locked rows unless we actually have to
362 : : * do an EPQ recheck.
363 : : */
5284 364 [ + + ]: 4184 : if (RowMarkRequiresRowShareLock(erm->markType))
4841 365 : 4050 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
366 : : else
367 : 134 : epq_arowmarks = lappend(epq_arowmarks, aerm);
368 : : }
369 : :
370 : : /* Now we have the info needed to set up EPQ state */
371 : 3957 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
372 : : outerPlan, epq_arowmarks, node->epqParam, NIL);
373 : :
5298 374 : 3957 : return lrstate;
375 : : }
376 : :
377 : : /* ----------------------------------------------------------------
378 : : * ExecEndLockRows
379 : : *
380 : : * This shuts down the subplan and frees resources allocated
381 : : * to this node.
382 : : * ----------------------------------------------------------------
383 : : */
384 : : void
385 : 3914 : ExecEndLockRows(LockRowsState *node)
386 : : {
387 : : /* We may have shut down EPQ already, but no harm in another call */
5284 388 : 3914 : EvalPlanQualEnd(&node->lr_epqstate);
5298 389 : 3914 : ExecEndNode(outerPlanState(node));
390 : 3914 : }
391 : :
392 : :
393 : : void
5025 394 : 8 : ExecReScanLockRows(LockRowsState *node)
395 : : {
647 396 : 8 : PlanState *outerPlan = outerPlanState(node);
397 : :
398 : : /*
399 : : * if chgParam of subnode is not null then plan will be re-scanned by
400 : : * first ExecProcNode.
401 : : */
402 [ - + ]: 8 : if (outerPlan->chgParam == NULL)
647 tgl@sss.pgh.pa.us 403 :UBC 0 : ExecReScan(outerPlan);
5298 tgl@sss.pgh.pa.us 404 :CBC 8 : }
|