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