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 */
38 19644 : ExecLockRows(PlanState *pstate)
39 : {
40 19644 : LockRowsState *node = castNode(LockRowsState, pstate);
41 : TupleTableSlot *slot;
42 : EState *estate;
43 : PlanState *outerPlan;
44 : bool epq_needed;
45 : ListCell *lc;
46 :
47 19644 : CHECK_FOR_INTERRUPTS();
48 :
49 : /*
50 : * get information from the node
51 : */
52 19644 : estate = node->ps.state;
53 19644 : outerPlan = outerPlanState(node);
54 :
55 : /*
56 : * Get next tuple from subplan, if any.
57 : */
58 19748 : lnext:
59 19748 : slot = ExecProcNode(outerPlan);
60 :
61 19748 : if (TupIsNull(slot))
62 : {
63 : /* Release any resources held by EPQ mechanism before exiting */
64 3354 : EvalPlanQualEnd(&node->lr_epqstate);
65 3354 : return NULL;
66 : }
67 :
68 : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
69 16394 : 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 33536 : foreach(lc, node->lr_arowMarks)
76 : {
77 17288 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
78 17288 : ExecRowMark *erm = aerm->rowmark;
79 : Datum datum;
80 : bool isNull;
81 : ItemPointerData tid;
82 : TM_FailureData tmfd;
83 : LockTupleMode lockmode;
84 17288 : int lockflags = 0;
85 : TM_Result test;
86 : TupleTableSlot *markSlot;
87 :
88 : /* clear any leftover test tuple for this rel */
89 17288 : markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti);
90 17288 : ExecClearTuple(markSlot);
91 :
92 : /* if child rel, must check whether it produced this row */
93 17288 : if (erm->rti != erm->prti)
94 : {
95 : Oid tableoid;
96 :
97 1592 : datum = ExecGetJunkAttribute(slot,
98 1592 : aerm->toidAttNo,
99 : &isNull);
100 : /* shouldn't ever get a null result... */
101 1592 : if (isNull)
102 0 : elog(ERROR, "tableoid is NULL");
103 1592 : tableoid = DatumGetObjectId(datum);
104 :
105 : Assert(OidIsValid(erm->relid));
106 1592 : if (tableoid != erm->relid)
107 : {
108 : /* this child is inactive right now */
109 400 : erm->ermActive = false;
110 400 : ItemPointerSetInvalid(&(erm->curCtid));
111 400 : continue;
112 : }
113 : }
114 16888 : erm->ermActive = true;
115 :
116 : /* fetch the tuple's ctid */
117 16888 : datum = ExecGetJunkAttribute(slot,
118 16888 : aerm->ctidAttNo,
119 : &isNull);
120 : /* shouldn't ever get a null result... */
121 16888 : if (isNull)
122 0 : elog(ERROR, "ctid is NULL");
123 :
124 : /* requests for foreign tables must be passed to their FDW */
125 16888 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
126 : {
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 :
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 */
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 */
160 16888 : tid = *((ItemPointer) DatumGetPointer(datum));
161 16888 : switch (erm->markType)
162 : {
163 7890 : case ROW_MARK_EXCLUSIVE:
164 7890 : lockmode = LockTupleExclusive;
165 7890 : break;
166 72 : case ROW_MARK_NOKEYEXCLUSIVE:
167 72 : lockmode = LockTupleNoKeyExclusive;
168 72 : break;
169 2512 : case ROW_MARK_SHARE:
170 2512 : lockmode = LockTupleShare;
171 2512 : break;
172 6414 : case ROW_MARK_KEYSHARE:
173 6414 : lockmode = LockTupleKeyShare;
174 6414 : break;
175 0 : default:
176 0 : elog(ERROR, "unsupported rowmark type");
177 : lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
178 : break;
179 : }
180 :
181 16888 : lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
182 16888 : if (!IsolationUsesXactSnapshot())
183 9194 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
184 :
185 16888 : 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 16862 : switch (test)
192 : {
193 74 : case TM_WouldBlock:
194 : /* couldn't lock tuple in SKIP LOCKED mode */
195 74 : goto lnext;
196 :
197 6 : 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 6 : goto lnext;
213 :
214 16742 : 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 16742 : if (tmfd.traversed)
221 58 : epq_needed = true;
222 16742 : break;
223 :
224 24 : case TM_Updated:
225 24 : if (IsolationUsesXactSnapshot())
226 24 : 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 : break;
232 :
233 16 : case TM_Deleted:
234 16 : if (IsolationUsesXactSnapshot())
235 4 : 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 12 : goto lnext;
240 :
241 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 */
251 16742 : erm->curCtid = tid;
252 : }
253 :
254 : /*
255 : * If we need to do EvalPlanQual testing, do so.
256 : */
257 16248 : if (epq_needed)
258 : {
259 : /* Initialize EPQ machinery */
260 56 : 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 56 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
267 :
268 : /*
269 : * And finally we can re-evaluate the tuple.
270 : */
271 56 : slot = EvalPlanQualNext(&node->lr_epqstate);
272 56 : if (TupIsNull(slot))
273 : {
274 : /* Updated tuple fails qual, so ignore it and go on */
275 12 : goto lnext;
276 : }
277 : }
278 :
279 : /* Got all locks, so return the current tuple */
280 16236 : 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 8116 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
292 : {
293 : LockRowsState *lrstate;
294 8116 : Plan *outerPlan = outerPlan(node);
295 : List *epq_arowmarks;
296 : ListCell *lc;
297 :
298 : /* check for unsupported flags */
299 : Assert(!(eflags & EXEC_FLAG_MARK));
300 :
301 : /*
302 : * create state structure
303 : */
304 8116 : lrstate = makeNode(LockRowsState);
305 8116 : lrstate->ps.plan = (Plan *) node;
306 8116 : lrstate->ps.state = estate;
307 8116 : 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 8116 : ExecInitResultTypeTL(&lrstate->ps);
320 :
321 : /*
322 : * then initialize outer plan
323 : */
324 8116 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
325 :
326 : /* node returns unmodified slots from the outer plan */
327 8116 : lrstate->ps.resultopsset = true;
328 8116 : 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 : */
335 8116 : 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 8116 : lrstate->lr_arowMarks = NIL;
343 8116 : epq_arowmarks = NIL;
344 18630 : foreach(lc, node->rowMarks)
345 : {
346 10514 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
347 : ExecRowMark *erm;
348 : ExecAuxRowMark *aerm;
349 :
350 : /* ignore "parent" rowmarks; they are irrelevant at runtime */
351 10514 : if (rc->isParent)
352 1780 : continue;
353 :
354 : /* find ExecRowMark and build ExecAuxRowMark */
355 8734 : erm = ExecFindRowMark(estate, rc->rti, false);
356 8734 : 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 : */
364 8734 : if (RowMarkRequiresRowShareLock(erm->markType))
365 8466 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
366 : else
367 268 : epq_arowmarks = lappend(epq_arowmarks, aerm);
368 : }
369 :
370 : /* Now we have the info needed to set up EPQ state */
371 8116 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
372 : outerPlan, epq_arowmarks, node->epqParam, NIL);
373 :
374 8116 : 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 8030 : ExecEndLockRows(LockRowsState *node)
386 : {
387 : /* We may have shut down EPQ already, but no harm in another call */
388 8030 : EvalPlanQualEnd(&node->lr_epqstate);
389 8030 : ExecEndNode(outerPlanState(node));
390 8030 : }
391 :
392 :
393 : void
394 16 : ExecReScanLockRows(LockRowsState *node)
395 : {
396 16 : 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 16 : if (outerPlan->chgParam == NULL)
403 0 : ExecReScan(outerPlan);
404 16 : }
|