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-2017, 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/htup_details.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 "storage/bufmgr.h"
31 : #include "utils/rel.h"
32 : #include "utils/tqual.h"
33 :
34 :
35 : /* ----------------------------------------------------------------
36 : * ExecLockRows
37 : * ----------------------------------------------------------------
38 : */
39 : static TupleTableSlot * /* return: a tuple or NULL */
40 381 : ExecLockRows(PlanState *pstate)
41 : {
42 381 : LockRowsState *node = castNode(LockRowsState, pstate);
43 : TupleTableSlot *slot;
44 : EState *estate;
45 : PlanState *outerPlan;
46 : bool epq_needed;
47 : ListCell *lc;
48 :
49 381 : CHECK_FOR_INTERRUPTS();
50 :
51 : /*
52 : * get information from the node
53 : */
54 381 : estate = node->ps.state;
55 381 : outerPlan = outerPlanState(node);
56 :
57 : /*
58 : * Get next tuple from subplan, if any.
59 : */
60 : lnext:
61 382 : slot = ExecProcNode(outerPlan);
62 :
63 382 : if (TupIsNull(slot))
64 139 : return NULL;
65 :
66 : /* We don't need EvalPlanQual unless we get updated tuple version(s) */
67 243 : epq_needed = false;
68 :
69 : /*
70 : * Attempt to lock the source tuple(s). (Note we only have locking
71 : * rowmarks in lr_arowMarks.)
72 : */
73 512 : foreach(lc, node->lr_arowMarks)
74 : {
75 270 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
76 270 : ExecRowMark *erm = aerm->rowmark;
77 : HeapTuple *testTuple;
78 : Datum datum;
79 : bool isNull;
80 : HeapTupleData tuple;
81 : Buffer buffer;
82 : HeapUpdateFailureData hufd;
83 : LockTupleMode lockmode;
84 : HTSU_Result test;
85 : HeapTuple copyTuple;
86 :
87 : /* clear any leftover test tuple for this rel */
88 270 : testTuple = &(node->lr_curtuples[erm->rti - 1]);
89 270 : if (*testTuple != NULL)
90 0 : heap_freetuple(*testTuple);
91 270 : *testTuple = NULL;
92 :
93 : /* if child rel, must check whether it produced this row */
94 270 : if (erm->rti != erm->prti)
95 : {
96 : Oid tableoid;
97 :
98 42 : datum = ExecGetJunkAttribute(slot,
99 42 : aerm->toidAttNo,
100 : &isNull);
101 : /* shouldn't ever get a null result... */
102 42 : if (isNull)
103 0 : elog(ERROR, "tableoid is NULL");
104 42 : tableoid = DatumGetObjectId(datum);
105 :
106 42 : Assert(OidIsValid(erm->relid));
107 42 : if (tableoid != erm->relid)
108 : {
109 : /* this child is inactive right now */
110 26 : erm->ermActive = false;
111 26 : ItemPointerSetInvalid(&(erm->curCtid));
112 52 : continue;
113 : }
114 : }
115 244 : erm->ermActive = true;
116 :
117 : /* fetch the tuple's ctid */
118 244 : datum = ExecGetJunkAttribute(slot,
119 244 : aerm->ctidAttNo,
120 : &isNull);
121 : /* shouldn't ever get a null result... */
122 244 : if (isNull)
123 0 : elog(ERROR, "ctid is NULL");
124 :
125 : /* requests for foreign tables must be passed to their FDW */
126 244 : if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
127 : {
128 : FdwRoutine *fdwroutine;
129 0 : bool updated = false;
130 :
131 0 : fdwroutine = GetFdwRoutineForRelation(erm->relation, false);
132 : /* this should have been checked already, but let's be safe */
133 0 : if (fdwroutine->RefetchForeignRow == NULL)
134 0 : ereport(ERROR,
135 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
136 : errmsg("cannot lock rows in foreign table \"%s\"",
137 : RelationGetRelationName(erm->relation))));
138 0 : copyTuple = fdwroutine->RefetchForeignRow(estate,
139 : erm,
140 : datum,
141 : &updated);
142 0 : if (copyTuple == NULL)
143 : {
144 : /* couldn't get the lock, so skip this row */
145 0 : goto lnext;
146 : }
147 :
148 : /* save locked tuple for possible EvalPlanQual testing below */
149 0 : *testTuple = copyTuple;
150 :
151 : /*
152 : * if FDW says tuple was updated before getting locked, we need to
153 : * perform EPQ testing to see if quals are still satisfied
154 : */
155 0 : if (updated)
156 0 : epq_needed = true;
157 :
158 0 : continue;
159 : }
160 :
161 : /* okay, try to lock the tuple */
162 244 : tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
163 244 : switch (erm->markType)
164 : {
165 : case ROW_MARK_EXCLUSIVE:
166 17 : lockmode = LockTupleExclusive;
167 17 : break;
168 : case ROW_MARK_NOKEYEXCLUSIVE:
169 0 : lockmode = LockTupleNoKeyExclusive;
170 0 : break;
171 : case ROW_MARK_SHARE:
172 11 : lockmode = LockTupleShare;
173 11 : break;
174 : case ROW_MARK_KEYSHARE:
175 216 : lockmode = LockTupleKeyShare;
176 216 : break;
177 : default:
178 0 : elog(ERROR, "unsupported rowmark type");
179 : lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
180 : break;
181 : }
182 :
183 244 : test = heap_lock_tuple(erm->relation, &tuple,
184 : estate->es_output_cid,
185 : lockmode, erm->waitPolicy, true,
186 : &buffer, &hufd);
187 244 : ReleaseBuffer(buffer);
188 244 : switch (test)
189 : {
190 : case HeapTupleWouldBlock:
191 : /* couldn't lock tuple in SKIP LOCKED mode */
192 0 : goto lnext;
193 :
194 : case HeapTupleSelfUpdated:
195 :
196 : /*
197 : * The target tuple was already updated or deleted by the
198 : * current command, or by a later command in the current
199 : * transaction. We *must* ignore the tuple in the former
200 : * case, so as to avoid the "Halloween problem" of repeated
201 : * update attempts. In the latter case it might be sensible
202 : * to fetch the updated tuple instead, but doing so would
203 : * require changing heap_update and heap_delete to not
204 : * complain about updating "invisible" tuples, which seems
205 : * pretty scary (heap_lock_tuple will not complain, but few
206 : * callers expect HeapTupleInvisible, and we're not one of
207 : * them). So for now, treat the tuple as deleted and do not
208 : * process.
209 : */
210 1 : goto lnext;
211 :
212 : case HeapTupleMayBeUpdated:
213 : /* got the lock successfully */
214 243 : break;
215 :
216 : case HeapTupleUpdated:
217 0 : if (IsolationUsesXactSnapshot())
218 0 : ereport(ERROR,
219 : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
220 : errmsg("could not serialize access due to concurrent update")));
221 0 : if (ItemPointerEquals(&hufd.ctid, &tuple.t_self))
222 : {
223 : /* Tuple was deleted, so don't return it */
224 0 : goto lnext;
225 : }
226 :
227 : /* updated, so fetch and lock the updated version */
228 0 : copyTuple = EvalPlanQualFetch(estate, erm->relation,
229 : lockmode, erm->waitPolicy,
230 : &hufd.ctid, hufd.xmax);
231 :
232 0 : if (copyTuple == NULL)
233 : {
234 : /*
235 : * Tuple was deleted; or it's locked and we're under SKIP
236 : * LOCKED policy, so don't return it
237 : */
238 0 : goto lnext;
239 : }
240 : /* remember the actually locked tuple's TID */
241 0 : tuple.t_self = copyTuple->t_self;
242 :
243 : /* Save locked tuple for EvalPlanQual testing below */
244 0 : *testTuple = copyTuple;
245 :
246 : /* Remember we need to do EPQ testing */
247 0 : epq_needed = true;
248 :
249 : /* Continue loop until we have all target tuples */
250 0 : break;
251 :
252 : case HeapTupleInvisible:
253 0 : elog(ERROR, "attempted to lock invisible tuple");
254 :
255 : default:
256 0 : elog(ERROR, "unrecognized heap_lock_tuple status: %u",
257 : test);
258 : }
259 :
260 : /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */
261 243 : erm->curCtid = tuple.t_self;
262 : }
263 :
264 : /*
265 : * If we need to do EvalPlanQual testing, do so.
266 : */
267 242 : if (epq_needed)
268 : {
269 : /* Initialize EPQ machinery */
270 0 : EvalPlanQualBegin(&node->lr_epqstate, estate);
271 :
272 : /*
273 : * Transfer any already-fetched tuples into the EPQ state, and fetch a
274 : * copy of any rows that were successfully locked without any update
275 : * having occurred. (We do this in a separate pass so as to avoid
276 : * overhead in the common case where there are no concurrent updates.)
277 : * Make sure any inactive child rels have NULL test tuples in EPQ.
278 : */
279 0 : foreach(lc, node->lr_arowMarks)
280 : {
281 0 : ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc);
282 0 : ExecRowMark *erm = aerm->rowmark;
283 : HeapTupleData tuple;
284 : Buffer buffer;
285 :
286 : /* skip non-active child tables, but clear their test tuples */
287 0 : if (!erm->ermActive)
288 : {
289 0 : Assert(erm->rti != erm->prti); /* check it's child table */
290 0 : EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL);
291 0 : continue;
292 : }
293 :
294 : /* was tuple updated and fetched above? */
295 0 : if (node->lr_curtuples[erm->rti - 1] != NULL)
296 : {
297 : /* yes, so set it as the EPQ test tuple for this rel */
298 0 : EvalPlanQualSetTuple(&node->lr_epqstate,
299 : erm->rti,
300 0 : node->lr_curtuples[erm->rti - 1]);
301 : /* freeing this tuple is now the responsibility of EPQ */
302 0 : node->lr_curtuples[erm->rti - 1] = NULL;
303 0 : continue;
304 : }
305 :
306 : /* foreign tables should have been fetched above */
307 0 : Assert(erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE);
308 0 : Assert(ItemPointerIsValid(&(erm->curCtid)));
309 :
310 : /* okay, fetch the tuple */
311 0 : tuple.t_self = erm->curCtid;
312 0 : if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer,
313 : false, NULL))
314 0 : elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");
315 :
316 : /* successful, copy and store tuple */
317 0 : EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti,
318 : heap_copytuple(&tuple));
319 0 : ReleaseBuffer(buffer);
320 : }
321 :
322 : /*
323 : * Now fetch any non-locked source rows --- the EPQ logic knows how to
324 : * do that.
325 : */
326 0 : EvalPlanQualSetSlot(&node->lr_epqstate, slot);
327 0 : EvalPlanQualFetchRowMarks(&node->lr_epqstate);
328 :
329 : /*
330 : * And finally we can re-evaluate the tuple.
331 : */
332 0 : slot = EvalPlanQualNext(&node->lr_epqstate);
333 0 : if (TupIsNull(slot))
334 : {
335 : /* Updated tuple fails qual, so ignore it and go on */
336 : goto lnext;
337 : }
338 : }
339 :
340 : /* Got all locks, so return the current tuple */
341 242 : return slot;
342 : }
343 :
344 : /* ----------------------------------------------------------------
345 : * ExecInitLockRows
346 : *
347 : * This initializes the LockRows node state structures and
348 : * the node's subplan.
349 : * ----------------------------------------------------------------
350 : */
351 : LockRowsState *
352 363 : ExecInitLockRows(LockRows *node, EState *estate, int eflags)
353 : {
354 : LockRowsState *lrstate;
355 363 : Plan *outerPlan = outerPlan(node);
356 : List *epq_arowmarks;
357 : ListCell *lc;
358 :
359 : /* check for unsupported flags */
360 363 : Assert(!(eflags & EXEC_FLAG_MARK));
361 :
362 : /*
363 : * create state structure
364 : */
365 363 : lrstate = makeNode(LockRowsState);
366 363 : lrstate->ps.plan = (Plan *) node;
367 363 : lrstate->ps.state = estate;
368 363 : lrstate->ps.ExecProcNode = ExecLockRows;
369 :
370 : /*
371 : * Miscellaneous initialization
372 : *
373 : * LockRows nodes never call ExecQual or ExecProject.
374 : */
375 :
376 : /*
377 : * Tuple table initialization (XXX not actually used...)
378 : */
379 363 : ExecInitResultTupleSlot(estate, &lrstate->ps);
380 :
381 : /*
382 : * then initialize outer plan
383 : */
384 363 : outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
385 :
386 : /*
387 : * LockRows nodes do no projections, so initialize projection info for
388 : * this node appropriately
389 : */
390 363 : ExecAssignResultTypeFromTL(&lrstate->ps);
391 363 : lrstate->ps.ps_ProjInfo = NULL;
392 :
393 : /*
394 : * Create workspace in which we can remember per-RTE locked tuples
395 : */
396 363 : lrstate->lr_ntables = list_length(estate->es_range_table);
397 363 : lrstate->lr_curtuples = (HeapTuple *)
398 363 : palloc0(lrstate->lr_ntables * sizeof(HeapTuple));
399 :
400 : /*
401 : * Locate the ExecRowMark(s) that this node is responsible for, and
402 : * construct ExecAuxRowMarks for them. (InitPlan should already have
403 : * built the global list of ExecRowMarks.)
404 : */
405 363 : lrstate->lr_arowMarks = NIL;
406 363 : epq_arowmarks = NIL;
407 750 : foreach(lc, node->rowMarks)
408 : {
409 387 : PlanRowMark *rc = lfirst_node(PlanRowMark, lc);
410 : ExecRowMark *erm;
411 : ExecAuxRowMark *aerm;
412 :
413 : /* ignore "parent" rowmarks; they are irrelevant at runtime */
414 387 : if (rc->isParent)
415 9 : continue;
416 :
417 : /* safety check on size of lr_curtuples array */
418 378 : Assert(rc->rti > 0 && rc->rti <= lrstate->lr_ntables);
419 :
420 : /* find ExecRowMark and build ExecAuxRowMark */
421 378 : erm = ExecFindRowMark(estate, rc->rti, false);
422 378 : aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist);
423 :
424 : /*
425 : * Only locking rowmarks go into our own list. Non-locking marks are
426 : * passed off to the EvalPlanQual machinery. This is because we don't
427 : * want to bother fetching non-locked rows unless we actually have to
428 : * do an EPQ recheck.
429 : */
430 378 : if (RowMarkRequiresRowShareLock(erm->markType))
431 376 : lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
432 : else
433 2 : epq_arowmarks = lappend(epq_arowmarks, aerm);
434 : }
435 :
436 : /* Now we have the info needed to set up EPQ state */
437 363 : EvalPlanQualInit(&lrstate->lr_epqstate, estate,
438 : outerPlan, epq_arowmarks, node->epqParam);
439 :
440 363 : return lrstate;
441 : }
442 :
443 : /* ----------------------------------------------------------------
444 : * ExecEndLockRows
445 : *
446 : * This shuts down the subplan and frees resources allocated
447 : * to this node.
448 : * ----------------------------------------------------------------
449 : */
450 : void
451 359 : ExecEndLockRows(LockRowsState *node)
452 : {
453 359 : EvalPlanQualEnd(&node->lr_epqstate);
454 359 : ExecEndNode(outerPlanState(node));
455 359 : }
456 :
457 :
458 : void
459 0 : ExecReScanLockRows(LockRowsState *node)
460 : {
461 : /*
462 : * if chgParam of subnode is not null then plan will be re-scanned by
463 : * first ExecProcNode.
464 : */
465 0 : if (node->ps.lefttree->chgParam == NULL)
466 0 : ExecReScan(node->ps.lefttree);
467 0 : }
|