Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeTidscan.c
4 : : * Routines to support direct tid scans of relations
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/nodeTidscan.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : /*
16 : : * INTERFACE ROUTINES
17 : : *
18 : : * ExecTidScan scans a relation using tids
19 : : * ExecInitTidScan creates and initializes state info.
20 : : * ExecReScanTidScan rescans the tid relation.
21 : : * ExecEndTidScan releases all storage.
22 : : */
23 : : #include "postgres.h"
24 : :
25 : : #include "access/sysattr.h"
26 : : #include "access/tableam.h"
27 : : #include "catalog/pg_type.h"
28 : : #include "executor/executor.h"
29 : : #include "executor/nodeTidscan.h"
30 : : #include "lib/qunique.h"
31 : : #include "miscadmin.h"
32 : : #include "nodes/nodeFuncs.h"
33 : : #include "utils/array.h"
34 : : #include "utils/rel.h"
35 : :
36 : :
37 : : /*
38 : : * It's sufficient to check varattno to identify the CTID variable, as any
39 : : * Var in the relation scan qual must be for our table. (Even if it's a
40 : : * parameterized scan referencing some other table's CTID, the other table's
41 : : * Var would have become a Param by the time it gets here.)
42 : : */
43 : : #define IsCTIDVar(node) \
44 : : ((node) != NULL && \
45 : : IsA((node), Var) && \
46 : : ((Var *) (node))->varattno == SelfItemPointerAttributeNumber)
47 : :
48 : : /* one element in tss_tidexprs */
49 : : typedef struct TidExpr
50 : : {
51 : : ExprState *exprstate; /* ExprState for a TID-yielding subexpr */
52 : : bool isarray; /* if true, it yields tid[] not just tid */
53 : : CurrentOfExpr *cexpr; /* alternatively, we can have CURRENT OF */
54 : : } TidExpr;
55 : :
56 : : static void TidExprListCreate(TidScanState *tidstate);
57 : : static void TidListEval(TidScanState *tidstate);
58 : : static int itemptr_comparator(const void *a, const void *b);
59 : : static TupleTableSlot *TidNext(TidScanState *node);
60 : :
61 : :
62 : : /*
63 : : * Extract the qual subexpressions that yield TIDs to search for,
64 : : * and compile them into ExprStates if they're ordinary expressions.
65 : : *
66 : : * CURRENT OF is a special case that we can't compile usefully;
67 : : * just drop it into the TidExpr list as-is.
68 : : */
69 : : static void
70 : 95 : TidExprListCreate(TidScanState *tidstate)
71 : : {
72 : 95 : TidScan *node = (TidScan *) tidstate->ss.ps.plan;
73 : 95 : ListCell *l;
74 : :
75 : 95 : tidstate->tss_tidexprs = NIL;
76 : 95 : tidstate->tss_isCurrentOf = false;
77 : :
78 [ + - + + : 194 : foreach(l, node->tidquals)
+ + ]
79 : : {
80 : 99 : Expr *expr = (Expr *) lfirst(l);
81 : 99 : TidExpr *tidexpr = palloc0_object(TidExpr);
82 : :
83 [ + + ]: 99 : if (is_opclause(expr))
84 : : {
85 : 17 : Node *arg1;
86 : 17 : Node *arg2;
87 : :
88 : 17 : arg1 = get_leftop(expr);
89 : 17 : arg2 = get_rightop(expr);
90 [ + - + + : 17 : if (IsCTIDVar(arg1))
- + ]
91 : 18 : tidexpr->exprstate = ExecInitExpr((Expr *) arg2,
92 : 9 : &tidstate->ss.ps);
93 [ + - ]: 8 : else if (IsCTIDVar(arg2))
94 : 16 : tidexpr->exprstate = ExecInitExpr((Expr *) arg1,
95 : 8 : &tidstate->ss.ps);
96 : : else
97 [ # # # # ]: 0 : elog(ERROR, "could not identify CTID variable");
98 : 17 : tidexpr->isarray = false;
99 : 17 : }
100 [ + - + + ]: 82 : else if (expr && IsA(expr, ScalarArrayOpExpr))
101 : : {
102 : 8 : ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr;
103 : :
104 [ + - ]: 8 : Assert(IsCTIDVar(linitial(saex->args)));
105 : 16 : tidexpr->exprstate = ExecInitExpr(lsecond(saex->args),
106 : 8 : &tidstate->ss.ps);
107 : 8 : tidexpr->isarray = true;
108 : 8 : }
109 [ + - ]: 74 : else if (expr && IsA(expr, CurrentOfExpr))
110 : : {
111 : 74 : CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
112 : :
113 : 74 : tidexpr->cexpr = cexpr;
114 : 74 : tidstate->tss_isCurrentOf = true;
115 : 74 : }
116 : : else
117 [ # # # # ]: 0 : elog(ERROR, "could not identify CTID expression");
118 : :
119 : 99 : tidstate->tss_tidexprs = lappend(tidstate->tss_tidexprs, tidexpr);
120 : 99 : }
121 : :
122 : : /* CurrentOfExpr could never appear OR'd with something else */
123 [ + + + - ]: 95 : Assert(list_length(tidstate->tss_tidexprs) == 1 ||
124 : : !tidstate->tss_isCurrentOf);
125 : 95 : }
126 : :
127 : : /*
128 : : * Compute the list of TIDs to be visited, by evaluating the expressions
129 : : * for them.
130 : : *
131 : : * (The result is actually an array, not a list.)
132 : : */
133 : : static void
134 : 77 : TidListEval(TidScanState *tidstate)
135 : : {
136 : 77 : ExprContext *econtext = tidstate->ss.ps.ps_ExprContext;
137 : 77 : TableScanDesc scan;
138 : 77 : ItemPointerData *tidList;
139 : 77 : int numAllocTids;
140 : 77 : int numTids;
141 : 77 : ListCell *l;
142 : :
143 : : /*
144 : : * Start scan on-demand - initializing a scan isn't free (e.g. heap stats
145 : : * the size of the table), so it makes sense to delay that until needed -
146 : : * the node might never get executed.
147 : : */
148 [ + + ]: 77 : if (tidstate->ss.ss_currentScanDesc == NULL)
149 : 76 : tidstate->ss.ss_currentScanDesc =
150 : 152 : table_beginscan_tid(tidstate->ss.ss_currentRelation,
151 : 76 : tidstate->ss.ps.state->es_snapshot);
152 : 77 : scan = tidstate->ss.ss_currentScanDesc;
153 : :
154 : : /*
155 : : * We initialize the array with enough slots for the case that all quals
156 : : * are simple OpExprs or CurrentOfExprs. If there are any
157 : : * ScalarArrayOpExprs, we may have to enlarge the array.
158 : : */
159 : 77 : numAllocTids = list_length(tidstate->tss_tidexprs);
160 : 77 : tidList = (ItemPointerData *)
161 : 77 : palloc(numAllocTids * sizeof(ItemPointerData));
162 : 77 : numTids = 0;
163 : :
164 [ + - + + : 146 : foreach(l, tidstate->tss_tidexprs)
+ + ]
165 : : {
166 : 69 : TidExpr *tidexpr = (TidExpr *) lfirst(l);
167 : 69 : ItemPointer itemptr;
168 : 69 : bool isNull;
169 : :
170 [ + + + + ]: 69 : if (tidexpr->exprstate && !tidexpr->isarray)
171 : : {
172 : 8 : itemptr = (ItemPointer)
173 : 16 : DatumGetPointer(ExecEvalExprSwitchContext(tidexpr->exprstate,
174 : 8 : econtext,
175 : : &isNull));
176 [ - + ]: 8 : if (isNull)
177 : 0 : continue;
178 : :
179 : : /*
180 : : * We silently discard any TIDs that the AM considers invalid
181 : : * (E.g. for heap, they could be out of range at the time of scan
182 : : * start. Since we hold at least AccessShareLock on the table, it
183 : : * won't be possible for someone to truncate away the blocks we
184 : : * intend to visit.).
185 : : */
186 [ + - ]: 8 : if (!table_tuple_tid_valid(scan, itemptr))
187 : 0 : continue;
188 : :
189 [ + + ]: 8 : if (numTids >= numAllocTids)
190 : : {
191 : 1 : numAllocTids *= 2;
192 : 1 : tidList = (ItemPointerData *)
193 : 2 : repalloc(tidList,
194 : 1 : numAllocTids * sizeof(ItemPointerData));
195 : 1 : }
196 : 8 : tidList[numTids++] = *itemptr;
197 : 8 : }
198 [ + + - + ]: 61 : else if (tidexpr->exprstate && tidexpr->isarray)
199 : : {
200 : 7 : Datum arraydatum;
201 : 7 : ArrayType *itemarray;
202 : 7 : Datum *ipdatums;
203 : 7 : bool *ipnulls;
204 : 7 : int ndatums;
205 : 7 : int i;
206 : :
207 : 14 : arraydatum = ExecEvalExprSwitchContext(tidexpr->exprstate,
208 : 7 : econtext,
209 : : &isNull);
210 [ - + ]: 7 : if (isNull)
211 : 0 : continue;
212 : 7 : itemarray = DatumGetArrayTypeP(arraydatum);
213 : 7 : deconstruct_array_builtin(itemarray, TIDOID, &ipdatums, &ipnulls, &ndatums);
214 [ + + ]: 7 : if (numTids + ndatums > numAllocTids)
215 : : {
216 : 6 : numAllocTids = numTids + ndatums;
217 : 6 : tidList = (ItemPointerData *)
218 : 12 : repalloc(tidList,
219 : 6 : numAllocTids * sizeof(ItemPointerData));
220 : 6 : }
221 [ + + ]: 24 : for (i = 0; i < ndatums; i++)
222 : : {
223 [ - + ]: 17 : if (ipnulls[i])
224 : 0 : continue;
225 : :
226 : 17 : itemptr = (ItemPointer) DatumGetPointer(ipdatums[i]);
227 : :
228 [ + + ]: 17 : if (!table_tuple_tid_valid(scan, itemptr))
229 : 3 : continue;
230 : :
231 : 14 : tidList[numTids++] = *itemptr;
232 : 14 : }
233 : 7 : pfree(ipdatums);
234 : 7 : pfree(ipnulls);
235 [ + - ]: 7 : }
236 : : else
237 : : {
238 : 54 : ItemPointerData cursor_tid;
239 : :
240 [ - + ]: 54 : Assert(tidexpr->cexpr);
241 [ + + + + ]: 108 : if (execCurrentOf(tidexpr->cexpr, econtext,
242 : 54 : RelationGetRelid(tidstate->ss.ss_currentRelation),
243 : : &cursor_tid))
244 : : {
245 [ - + ]: 46 : if (numTids >= numAllocTids)
246 : : {
247 : 0 : numAllocTids *= 2;
248 : 0 : tidList = (ItemPointerData *)
249 : 0 : repalloc(tidList,
250 : 0 : numAllocTids * sizeof(ItemPointerData));
251 : 0 : }
252 : 46 : tidList[numTids++] = cursor_tid;
253 : 46 : }
254 : 54 : }
255 [ - - + ]: 69 : }
256 : :
257 : : /*
258 : : * Sort the array of TIDs into order, and eliminate duplicates.
259 : : * Eliminating duplicates is necessary since we want OR semantics across
260 : : * the list. Sorting makes it easier to detect duplicates, and as a bonus
261 : : * ensures that we will visit the heap in the most efficient way.
262 : : */
263 [ + + ]: 77 : if (numTids > 1)
264 : : {
265 : : /* CurrentOfExpr could never appear OR'd with something else */
266 [ + - ]: 8 : Assert(!tidstate->tss_isCurrentOf);
267 : :
268 : 8 : qsort(tidList, numTids, sizeof(ItemPointerData),
269 : : itemptr_comparator);
270 : 8 : numTids = qunique(tidList, numTids, sizeof(ItemPointerData),
271 : : itemptr_comparator);
272 : 8 : }
273 : :
274 : 77 : tidstate->tss_TidList = tidList;
275 : 77 : tidstate->tss_NumTids = numTids;
276 : 77 : tidstate->tss_TidPtr = -1;
277 : 77 : }
278 : :
279 : : /*
280 : : * qsort comparator for ItemPointerData items
281 : : */
282 : : static int
283 : 19 : itemptr_comparator(const void *a, const void *b)
284 : : {
285 : 19 : const ItemPointerData *ipa = (const ItemPointerData *) a;
286 : 19 : const ItemPointerData *ipb = (const ItemPointerData *) b;
287 : 19 : BlockNumber ba = ItemPointerGetBlockNumber(ipa);
288 : 19 : BlockNumber bb = ItemPointerGetBlockNumber(ipb);
289 : 19 : OffsetNumber oa = ItemPointerGetOffsetNumber(ipa);
290 : 19 : OffsetNumber ob = ItemPointerGetOffsetNumber(ipb);
291 : :
292 [ - + ]: 19 : if (ba < bb)
293 : 0 : return -1;
294 [ - + ]: 19 : if (ba > bb)
295 : 0 : return 1;
296 [ + + ]: 19 : if (oa < ob)
297 : 7 : return -1;
298 [ + - ]: 12 : if (oa > ob)
299 : 12 : return 1;
300 : 0 : return 0;
301 : 19 : }
302 : :
303 : : /* ----------------------------------------------------------------
304 : : * TidNext
305 : : *
306 : : * Retrieve a tuple from the TidScan node's currentRelation
307 : : * using the tids in the TidScanState information.
308 : : *
309 : : * ----------------------------------------------------------------
310 : : */
311 : : static TupleTableSlot *
312 : 126 : TidNext(TidScanState *node)
313 : : {
314 : 126 : EState *estate;
315 : 126 : ScanDirection direction;
316 : 126 : Snapshot snapshot;
317 : 126 : TableScanDesc scan;
318 : 126 : Relation heapRelation;
319 : 126 : TupleTableSlot *slot;
320 : 126 : ItemPointerData *tidList;
321 : 126 : int numTids;
322 : 126 : bool bBackward;
323 : :
324 : : /*
325 : : * extract necessary information from tid scan node
326 : : */
327 : 126 : estate = node->ss.ps.state;
328 : 126 : direction = estate->es_direction;
329 : 126 : snapshot = estate->es_snapshot;
330 : 126 : heapRelation = node->ss.ss_currentRelation;
331 : 126 : slot = node->ss.ss_ScanTupleSlot;
332 : :
333 : : /*
334 : : * First time through, compute the list of TIDs to be visited
335 : : */
336 [ + + ]: 126 : if (node->tss_TidList == NULL)
337 : 77 : TidListEval(node);
338 : :
339 : 126 : scan = node->ss.ss_currentScanDesc;
340 : 126 : tidList = node->tss_TidList;
341 : 126 : numTids = node->tss_NumTids;
342 : :
343 : : /*
344 : : * Initialize or advance scan position, depending on direction.
345 : : */
346 : 126 : bBackward = ScanDirectionIsBackward(direction);
347 [ + + ]: 126 : if (bBackward)
348 : : {
349 [ + - ]: 1 : if (node->tss_TidPtr < 0)
350 : : {
351 : : /* initialize for backward scan */
352 : 0 : node->tss_TidPtr = numTids - 1;
353 : 0 : }
354 : : else
355 : 1 : node->tss_TidPtr--;
356 : 1 : }
357 : : else
358 : : {
359 [ + + ]: 125 : if (node->tss_TidPtr < 0)
360 : : {
361 : : /* initialize for forward scan */
362 : 67 : node->tss_TidPtr = 0;
363 : 67 : }
364 : : else
365 : 58 : node->tss_TidPtr++;
366 : : }
367 : :
368 [ - + + + ]: 132 : while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
369 : : {
370 : 67 : ItemPointerData tid = tidList[node->tss_TidPtr];
371 : :
372 : : /*
373 : : * For WHERE CURRENT OF, the tuple retrieved from the cursor might
374 : : * since have been updated; if so, we should fetch the version that is
375 : : * current according to our snapshot.
376 : : */
377 [ + + ]: 67 : if (node->tss_isCurrentOf)
378 : 46 : table_tuple_get_latest_tid(scan, &tid);
379 : :
380 [ + + ]: 67 : if (table_tuple_fetch_row_version(heapRelation, &tid, snapshot, slot))
381 : 61 : return slot;
382 : :
383 : : /* Bad TID or failed snapshot qual; try next */
384 [ - + ]: 6 : if (bBackward)
385 : 0 : node->tss_TidPtr--;
386 : : else
387 : 6 : node->tss_TidPtr++;
388 : :
389 [ + - ]: 6 : CHECK_FOR_INTERRUPTS();
390 [ + + ]: 67 : }
391 : :
392 : : /*
393 : : * if we get here it means the tid scan failed so we are at the end of the
394 : : * scan..
395 : : */
396 : 65 : return ExecClearTuple(slot);
397 : 126 : }
398 : :
399 : : /*
400 : : * TidRecheck -- access method routine to recheck a tuple in EvalPlanQual
401 : : */
402 : : static bool
403 : 0 : TidRecheck(TidScanState *node, TupleTableSlot *slot)
404 : : {
405 : 0 : ItemPointer match;
406 : :
407 : : /* WHERE CURRENT OF always intends to resolve to the latest tuple */
408 [ # # ]: 0 : if (node->tss_isCurrentOf)
409 : 0 : return true;
410 : :
411 [ # # ]: 0 : if (node->tss_TidList == NULL)
412 : 0 : TidListEval(node);
413 : :
414 : : /*
415 : : * Binary search the TidList to see if this ctid is mentioned and return
416 : : * true if it is.
417 : : */
418 : 0 : match = (ItemPointer) bsearch(&slot->tts_tid, node->tss_TidList,
419 : 0 : node->tss_NumTids, sizeof(ItemPointerData),
420 : : itemptr_comparator);
421 : 0 : return match != NULL;
422 : 0 : }
423 : :
424 : :
425 : : /* ----------------------------------------------------------------
426 : : * ExecTidScan(node)
427 : : *
428 : : * Scans the relation using tids and returns
429 : : * the next qualifying tuple in the direction specified.
430 : : * We call the ExecScan() routine and pass it the appropriate
431 : : * access method functions.
432 : : *
433 : : * Conditions:
434 : : * -- the "cursor" maintained by the AMI is positioned at the tuple
435 : : * returned previously.
436 : : *
437 : : * Initial States:
438 : : * -- the relation indicated is opened for scanning so that the
439 : : * "cursor" is positioned before the first qualifying tuple.
440 : : * -- tss_TidPtr is -1.
441 : : * ----------------------------------------------------------------
442 : : */
443 : : static TupleTableSlot *
444 : 133 : ExecTidScan(PlanState *pstate)
445 : : {
446 : 133 : TidScanState *node = castNode(TidScanState, pstate);
447 : :
448 : 266 : return ExecScan(&node->ss,
449 : : (ExecScanAccessMtd) TidNext,
450 : : (ExecScanRecheckMtd) TidRecheck);
451 : 133 : }
452 : :
453 : : /* ----------------------------------------------------------------
454 : : * ExecReScanTidScan(node)
455 : : * ----------------------------------------------------------------
456 : : */
457 : : void
458 : 3 : ExecReScanTidScan(TidScanState *node)
459 : : {
460 [ + + ]: 3 : if (node->tss_TidList)
461 : 1 : pfree(node->tss_TidList);
462 : 3 : node->tss_TidList = NULL;
463 : 3 : node->tss_NumTids = 0;
464 : 3 : node->tss_TidPtr = -1;
465 : :
466 : : /* not really necessary, but seems good form */
467 [ + + ]: 3 : if (node->ss.ss_currentScanDesc)
468 : 1 : table_rescan(node->ss.ss_currentScanDesc, NULL);
469 : :
470 : 3 : ExecScanReScan(&node->ss);
471 : 3 : }
472 : :
473 : : /* ----------------------------------------------------------------
474 : : * ExecEndTidScan
475 : : *
476 : : * Releases any storage allocated through C routines.
477 : : * Returns nothing.
478 : : * ----------------------------------------------------------------
479 : : */
480 : : void
481 : 75 : ExecEndTidScan(TidScanState *node)
482 : : {
483 [ + + ]: 75 : if (node->ss.ss_currentScanDesc)
484 : 64 : table_endscan(node->ss.ss_currentScanDesc);
485 : 75 : }
486 : :
487 : : /* ----------------------------------------------------------------
488 : : * ExecInitTidScan
489 : : *
490 : : * Initializes the tid scan's state information, creates
491 : : * scan keys, and opens the base and tid relations.
492 : : *
493 : : * Parameters:
494 : : * node: TidScan node produced by the planner.
495 : : * estate: the execution state initialized in InitPlan.
496 : : * ----------------------------------------------------------------
497 : : */
498 : : TidScanState *
499 : 95 : ExecInitTidScan(TidScan *node, EState *estate, int eflags)
500 : : {
501 : 95 : TidScanState *tidstate;
502 : 95 : Relation currentRelation;
503 : :
504 : : /*
505 : : * create state structure
506 : : */
507 : 95 : tidstate = makeNode(TidScanState);
508 : 95 : tidstate->ss.ps.plan = (Plan *) node;
509 : 95 : tidstate->ss.ps.state = estate;
510 : 95 : tidstate->ss.ps.ExecProcNode = ExecTidScan;
511 : :
512 : : /*
513 : : * Miscellaneous initialization
514 : : *
515 : : * create expression context for node
516 : : */
517 : 95 : ExecAssignExprContext(estate, &tidstate->ss.ps);
518 : :
519 : : /*
520 : : * mark tid list as not computed yet
521 : : */
522 : 95 : tidstate->tss_TidList = NULL;
523 : 95 : tidstate->tss_NumTids = 0;
524 : 95 : tidstate->tss_TidPtr = -1;
525 : :
526 : : /*
527 : : * open the scan relation
528 : : */
529 : 95 : currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags);
530 : :
531 : 95 : tidstate->ss.ss_currentRelation = currentRelation;
532 : 95 : tidstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */
533 : :
534 : : /*
535 : : * get the scan type from the relation descriptor.
536 : : */
537 : 190 : ExecInitScanTupleSlot(estate, &tidstate->ss,
538 : 95 : RelationGetDescr(currentRelation),
539 : 95 : table_slot_callbacks(currentRelation));
540 : :
541 : : /*
542 : : * Initialize result type and projection.
543 : : */
544 : 95 : ExecInitResultTypeTL(&tidstate->ss.ps);
545 : 95 : ExecAssignScanProjectionInfo(&tidstate->ss);
546 : :
547 : : /*
548 : : * initialize child expressions
549 : : */
550 : 95 : tidstate->ss.ps.qual =
551 : 95 : ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
552 : :
553 : 95 : TidExprListCreate(tidstate);
554 : :
555 : : /*
556 : : * all done.
557 : : */
558 : 190 : return tidstate;
559 : 95 : }
|