Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * nodeTidscan.c
4 : * Routines to support direct tid scans of relations
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/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 754 : TidExprListCreate(TidScanState *tidstate)
71 : {
72 754 : TidScan *node = (TidScan *) tidstate->ss.ps.plan;
73 : ListCell *l;
74 :
75 754 : tidstate->tss_tidexprs = NIL;
76 754 : tidstate->tss_isCurrentOf = false;
77 :
78 1532 : foreach(l, node->tidquals)
79 : {
80 778 : Expr *expr = (Expr *) lfirst(l);
81 778 : TidExpr *tidexpr = (TidExpr *) palloc0(sizeof(TidExpr));
82 :
83 778 : if (is_opclause(expr))
84 : {
85 : Node *arg1;
86 : Node *arg2;
87 :
88 276 : arg1 = get_leftop(expr);
89 276 : arg2 = get_rightop(expr);
90 276 : if (IsCTIDVar(arg1))
91 228 : tidexpr->exprstate = ExecInitExpr((Expr *) arg2,
92 : &tidstate->ss.ps);
93 48 : else if (IsCTIDVar(arg2))
94 48 : tidexpr->exprstate = ExecInitExpr((Expr *) arg1,
95 : &tidstate->ss.ps);
96 : else
97 0 : elog(ERROR, "could not identify CTID variable");
98 276 : tidexpr->isarray = false;
99 : }
100 502 : else if (expr && IsA(expr, ScalarArrayOpExpr))
101 50 : {
102 50 : ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr;
103 :
104 : Assert(IsCTIDVar(linitial(saex->args)));
105 50 : tidexpr->exprstate = ExecInitExpr(lsecond(saex->args),
106 : &tidstate->ss.ps);
107 50 : tidexpr->isarray = true;
108 : }
109 452 : else if (expr && IsA(expr, CurrentOfExpr))
110 452 : {
111 452 : CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
112 :
113 452 : tidexpr->cexpr = cexpr;
114 452 : tidstate->tss_isCurrentOf = true;
115 : }
116 : else
117 0 : elog(ERROR, "could not identify CTID expression");
118 :
119 778 : 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 754 : }
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 658 : TidListEval(TidScanState *tidstate)
135 : {
136 658 : 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 658 : if (tidstate->ss.ss_currentScanDesc == NULL)
149 652 : tidstate->ss.ss_currentScanDesc =
150 652 : table_beginscan_tid(tidstate->ss.ss_currentRelation,
151 652 : tidstate->ss.ps.state->es_snapshot);
152 658 : 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 658 : numAllocTids = list_length(tidstate->tss_tidexprs);
160 : tidList = (ItemPointerData *)
161 658 : palloc(numAllocTids * sizeof(ItemPointerData));
162 658 : numTids = 0;
163 :
164 1268 : foreach(l, tidstate->tss_tidexprs)
165 : {
166 670 : TidExpr *tidexpr = (TidExpr *) lfirst(l);
167 : ItemPointer itemptr;
168 : bool isNull;
169 :
170 670 : if (tidexpr->exprstate && !tidexpr->isarray)
171 : {
172 : itemptr = (ItemPointer)
173 234 : DatumGetPointer(ExecEvalExprSwitchContext(tidexpr->exprstate,
174 : econtext,
175 : &isNull));
176 234 : 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 234 : if (!table_tuple_tid_valid(scan, itemptr))
187 0 : continue;
188 :
189 234 : if (numTids >= numAllocTids)
190 : {
191 6 : numAllocTids *= 2;
192 : tidList = (ItemPointerData *)
193 6 : repalloc(tidList,
194 : numAllocTids * sizeof(ItemPointerData));
195 : }
196 234 : tidList[numTids++] = *itemptr;
197 : }
198 436 : else if (tidexpr->exprstate && tidexpr->isarray)
199 44 : {
200 : Datum arraydatum;
201 : ArrayType *itemarray;
202 : Datum *ipdatums;
203 : bool *ipnulls;
204 : int ndatums;
205 : int i;
206 :
207 44 : arraydatum = ExecEvalExprSwitchContext(tidexpr->exprstate,
208 : econtext,
209 : &isNull);
210 44 : if (isNull)
211 0 : continue;
212 44 : itemarray = DatumGetArrayTypeP(arraydatum);
213 44 : deconstruct_array_builtin(itemarray, TIDOID, &ipdatums, &ipnulls, &ndatums);
214 44 : if (numTids + ndatums > numAllocTids)
215 : {
216 38 : numAllocTids = numTids + ndatums;
217 : tidList = (ItemPointerData *)
218 38 : repalloc(tidList,
219 : numAllocTids * sizeof(ItemPointerData));
220 : }
221 156 : for (i = 0; i < ndatums; i++)
222 : {
223 112 : if (ipnulls[i])
224 0 : continue;
225 :
226 112 : itemptr = (ItemPointer) DatumGetPointer(ipdatums[i]);
227 :
228 112 : if (!table_tuple_tid_valid(scan, itemptr))
229 18 : continue;
230 :
231 94 : tidList[numTids++] = *itemptr;
232 : }
233 44 : pfree(ipdatums);
234 44 : pfree(ipnulls);
235 : }
236 : else
237 : {
238 : ItemPointerData cursor_tid;
239 :
240 : Assert(tidexpr->cexpr);
241 332 : if (execCurrentOf(tidexpr->cexpr, econtext,
242 392 : RelationGetRelid(tidstate->ss.ss_currentRelation),
243 : &cursor_tid))
244 : {
245 282 : if (numTids >= numAllocTids)
246 : {
247 0 : numAllocTids *= 2;
248 : tidList = (ItemPointerData *)
249 0 : repalloc(tidList,
250 : numAllocTids * sizeof(ItemPointerData));
251 : }
252 282 : tidList[numTids++] = cursor_tid;
253 : }
254 : }
255 : }
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 598 : if (numTids > 1)
264 : {
265 : /* CurrentOfExpr could never appear OR'd with something else */
266 : Assert(!tidstate->tss_isCurrentOf);
267 :
268 50 : qsort(tidList, numTids, sizeof(ItemPointerData),
269 : itemptr_comparator);
270 50 : numTids = qunique(tidList, numTids, sizeof(ItemPointerData),
271 : itemptr_comparator);
272 : }
273 :
274 598 : tidstate->tss_TidList = tidList;
275 598 : tidstate->tss_NumTids = numTids;
276 598 : tidstate->tss_TidPtr = -1;
277 598 : }
278 :
279 : /*
280 : * qsort comparator for ItemPointerData items
281 : */
282 : static int
283 130 : itemptr_comparator(const void *a, const void *b)
284 : {
285 130 : const ItemPointerData *ipa = (const ItemPointerData *) a;
286 130 : const ItemPointerData *ipb = (const ItemPointerData *) b;
287 130 : BlockNumber ba = ItemPointerGetBlockNumber(ipa);
288 130 : BlockNumber bb = ItemPointerGetBlockNumber(ipb);
289 130 : OffsetNumber oa = ItemPointerGetOffsetNumber(ipa);
290 130 : OffsetNumber ob = ItemPointerGetOffsetNumber(ipb);
291 :
292 130 : if (ba < bb)
293 8 : return -1;
294 122 : if (ba > bb)
295 8 : return 1;
296 114 : if (oa < ob)
297 42 : return -1;
298 72 : if (oa > ob)
299 72 : return 1;
300 0 : return 0;
301 : }
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 1208 : TidNext(TidScanState *node)
313 : {
314 : EState *estate;
315 : ScanDirection direction;
316 : Snapshot snapshot;
317 : TableScanDesc scan;
318 : Relation heapRelation;
319 : TupleTableSlot *slot;
320 : ItemPointerData *tidList;
321 : int numTids;
322 : bool bBackward;
323 :
324 : /*
325 : * extract necessary information from tid scan node
326 : */
327 1208 : estate = node->ss.ps.state;
328 1208 : direction = estate->es_direction;
329 1208 : snapshot = estate->es_snapshot;
330 1208 : heapRelation = node->ss.ss_currentRelation;
331 1208 : slot = node->ss.ss_ScanTupleSlot;
332 :
333 : /*
334 : * First time through, compute the list of TIDs to be visited
335 : */
336 1208 : if (node->tss_TidList == NULL)
337 658 : TidListEval(node);
338 :
339 1148 : scan = node->ss.ss_currentScanDesc;
340 1148 : tidList = node->tss_TidList;
341 1148 : numTids = node->tss_NumTids;
342 :
343 : /*
344 : * Initialize or advance scan position, depending on direction.
345 : */
346 1148 : bBackward = ScanDirectionIsBackward(direction);
347 1148 : if (bBackward)
348 : {
349 6 : if (node->tss_TidPtr < 0)
350 : {
351 : /* initialize for backward scan */
352 0 : node->tss_TidPtr = numTids - 1;
353 : }
354 : else
355 6 : node->tss_TidPtr--;
356 : }
357 : else
358 : {
359 1142 : if (node->tss_TidPtr < 0)
360 : {
361 : /* initialize for forward scan */
362 598 : node->tss_TidPtr = 0;
363 : }
364 : else
365 544 : node->tss_TidPtr++;
366 : }
367 :
368 1190 : while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids)
369 : {
370 604 : 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 604 : if (node->tss_isCurrentOf)
378 282 : table_tuple_get_latest_tid(scan, &tid);
379 :
380 604 : if (table_tuple_fetch_row_version(heapRelation, &tid, snapshot, slot))
381 562 : return slot;
382 :
383 : /* Bad TID or failed snapshot qual; try next */
384 42 : if (bBackward)
385 0 : node->tss_TidPtr--;
386 : else
387 42 : node->tss_TidPtr++;
388 :
389 42 : CHECK_FOR_INTERRUPTS();
390 : }
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 586 : return ExecClearTuple(slot);
397 : }
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 : /*
406 : * XXX shouldn't we check here to make sure tuple matches TID list? In
407 : * runtime-key case this is not certain, is it? However, in the WHERE
408 : * CURRENT OF case it might not match anyway ...
409 : */
410 0 : return true;
411 : }
412 :
413 :
414 : /* ----------------------------------------------------------------
415 : * ExecTidScan(node)
416 : *
417 : * Scans the relation using tids and returns
418 : * the next qualifying tuple in the direction specified.
419 : * We call the ExecScan() routine and pass it the appropriate
420 : * access method functions.
421 : *
422 : * Conditions:
423 : * -- the "cursor" maintained by the AMI is positioned at the tuple
424 : * returned previously.
425 : *
426 : * Initial States:
427 : * -- the relation indicated is opened for scanning so that the
428 : * "cursor" is positioned before the first qualifying tuple.
429 : * -- tss_TidPtr is -1.
430 : * ----------------------------------------------------------------
431 : */
432 : static TupleTableSlot *
433 1190 : ExecTidScan(PlanState *pstate)
434 : {
435 1190 : TidScanState *node = castNode(TidScanState, pstate);
436 :
437 1190 : return ExecScan(&node->ss,
438 : (ExecScanAccessMtd) TidNext,
439 : (ExecScanRecheckMtd) TidRecheck);
440 : }
441 :
442 : /* ----------------------------------------------------------------
443 : * ExecReScanTidScan(node)
444 : * ----------------------------------------------------------------
445 : */
446 : void
447 18 : ExecReScanTidScan(TidScanState *node)
448 : {
449 18 : if (node->tss_TidList)
450 6 : pfree(node->tss_TidList);
451 18 : node->tss_TidList = NULL;
452 18 : node->tss_NumTids = 0;
453 18 : node->tss_TidPtr = -1;
454 :
455 : /* not really necessary, but seems good form */
456 18 : if (node->ss.ss_currentScanDesc)
457 6 : table_rescan(node->ss.ss_currentScanDesc, NULL);
458 :
459 18 : ExecScanReScan(&node->ss);
460 18 : }
461 :
462 : /* ----------------------------------------------------------------
463 : * ExecEndTidScan
464 : *
465 : * Releases any storage allocated through C routines.
466 : * Returns nothing.
467 : * ----------------------------------------------------------------
468 : */
469 : void
470 628 : ExecEndTidScan(TidScanState *node)
471 : {
472 628 : if (node->ss.ss_currentScanDesc)
473 574 : table_endscan(node->ss.ss_currentScanDesc);
474 628 : }
475 :
476 : /* ----------------------------------------------------------------
477 : * ExecInitTidScan
478 : *
479 : * Initializes the tid scan's state information, creates
480 : * scan keys, and opens the base and tid relations.
481 : *
482 : * Parameters:
483 : * node: TidScan node produced by the planner.
484 : * estate: the execution state initialized in InitPlan.
485 : * ----------------------------------------------------------------
486 : */
487 : TidScanState *
488 754 : ExecInitTidScan(TidScan *node, EState *estate, int eflags)
489 : {
490 : TidScanState *tidstate;
491 : Relation currentRelation;
492 :
493 : /*
494 : * create state structure
495 : */
496 754 : tidstate = makeNode(TidScanState);
497 754 : tidstate->ss.ps.plan = (Plan *) node;
498 754 : tidstate->ss.ps.state = estate;
499 754 : tidstate->ss.ps.ExecProcNode = ExecTidScan;
500 :
501 : /*
502 : * Miscellaneous initialization
503 : *
504 : * create expression context for node
505 : */
506 754 : ExecAssignExprContext(estate, &tidstate->ss.ps);
507 :
508 : /*
509 : * mark tid list as not computed yet
510 : */
511 754 : tidstate->tss_TidList = NULL;
512 754 : tidstate->tss_NumTids = 0;
513 754 : tidstate->tss_TidPtr = -1;
514 :
515 : /*
516 : * open the scan relation
517 : */
518 754 : currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags);
519 :
520 754 : tidstate->ss.ss_currentRelation = currentRelation;
521 754 : tidstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */
522 :
523 : /*
524 : * get the scan type from the relation descriptor.
525 : */
526 754 : ExecInitScanTupleSlot(estate, &tidstate->ss,
527 : RelationGetDescr(currentRelation),
528 : table_slot_callbacks(currentRelation));
529 :
530 : /*
531 : * Initialize result type and projection.
532 : */
533 754 : ExecInitResultTypeTL(&tidstate->ss.ps);
534 754 : ExecAssignScanProjectionInfo(&tidstate->ss);
535 :
536 : /*
537 : * initialize child expressions
538 : */
539 754 : tidstate->ss.ps.qual =
540 754 : ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
541 :
542 754 : TidExprListCreate(tidstate);
543 :
544 : /*
545 : * all done.
546 : */
547 754 : return tidstate;
548 : }
|