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