Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeTidrangescan.c
4 : : * Routines to support TID range 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/nodeTidrangescan.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres.h"
16 : :
17 : : #include "access/relscan.h"
18 : : #include "access/sysattr.h"
19 : : #include "access/tableam.h"
20 : : #include "catalog/pg_operator.h"
21 : : #include "executor/executor.h"
22 : : #include "executor/nodeTidrangescan.h"
23 : : #include "nodes/nodeFuncs.h"
24 : : #include "utils/rel.h"
25 : :
26 : :
27 : : /*
28 : : * It's sufficient to check varattno to identify the CTID variable, as any
29 : : * Var in the relation scan qual must be for our table. (Even if it's a
30 : : * parameterized scan referencing some other table's CTID, the other table's
31 : : * Var would have become a Param by the time it gets here.)
32 : : */
33 : : #define IsCTIDVar(node) \
34 : : ((node) != NULL && \
35 : : IsA((node), Var) && \
36 : : ((Var *) (node))->varattno == SelfItemPointerAttributeNumber)
37 : :
38 : : typedef enum
39 : : {
40 : : TIDEXPR_UPPER_BOUND,
41 : : TIDEXPR_LOWER_BOUND,
42 : : } TidExprType;
43 : :
44 : : /* Upper or lower range bound for scan */
45 : : typedef struct TidOpExpr
46 : : {
47 : : TidExprType exprtype; /* type of op; lower or upper */
48 : : ExprState *exprstate; /* ExprState for a TID-yielding subexpr */
49 : : bool inclusive; /* whether op is inclusive */
50 : : } TidOpExpr;
51 : :
52 : : /*
53 : : * For the given 'expr', build and return an appropriate TidOpExpr taking into
54 : : * account the expr's operator and operand order.
55 : : */
56 : : static TidOpExpr *
1142 drowley@postgresql.o 57 :CBC 116 : MakeTidOpExpr(OpExpr *expr, TidRangeScanState *tidstate)
58 : : {
59 : 116 : Node *arg1 = get_leftop((Expr *) expr);
60 : 116 : Node *arg2 = get_rightop((Expr *) expr);
61 : 116 : ExprState *exprstate = NULL;
62 : 116 : bool invert = false;
63 : : TidOpExpr *tidopexpr;
64 : :
65 [ + - + + : 116 : if (IsCTIDVar(arg1))
+ - ]
66 : 98 : exprstate = ExecInitExpr((Expr *) arg2, &tidstate->ss.ps);
67 [ + - + - : 18 : else if (IsCTIDVar(arg2))
+ - ]
68 : : {
69 : 18 : exprstate = ExecInitExpr((Expr *) arg1, &tidstate->ss.ps);
70 : 18 : invert = true;
71 : : }
72 : : else
1142 drowley@postgresql.o 73 [ # # ]:UBC 0 : elog(ERROR, "could not identify CTID variable");
74 : :
1142 drowley@postgresql.o 75 :CBC 116 : tidopexpr = (TidOpExpr *) palloc(sizeof(TidOpExpr));
76 : 116 : tidopexpr->inclusive = false; /* for now */
77 : :
78 [ + + + + : 116 : switch (expr->opno)
- ]
79 : : {
80 : 12 : case TIDLessEqOperator:
81 : 12 : tidopexpr->inclusive = true;
82 : : /* fall through */
83 : 57 : case TIDLessOperator:
84 : 57 : tidopexpr->exprtype = invert ? TIDEXPR_LOWER_BOUND : TIDEXPR_UPPER_BOUND;
85 : 57 : break;
86 : 27 : case TIDGreaterEqOperator:
87 : 27 : tidopexpr->inclusive = true;
88 : : /* fall through */
89 : 59 : case TIDGreaterOperator:
90 : 59 : tidopexpr->exprtype = invert ? TIDEXPR_UPPER_BOUND : TIDEXPR_LOWER_BOUND;
91 : 59 : break;
1142 drowley@postgresql.o 92 :UBC 0 : default:
93 [ # # ]: 0 : elog(ERROR, "could not identify CTID operator");
94 : : }
95 : :
1142 drowley@postgresql.o 96 :CBC 116 : tidopexpr->exprstate = exprstate;
97 : :
98 : 116 : return tidopexpr;
99 : : }
100 : :
101 : : /*
102 : : * Extract the qual subexpressions that yield TIDs to search for,
103 : : * and compile them into ExprStates if they're ordinary expressions.
104 : : */
105 : : static void
106 : 101 : TidExprListCreate(TidRangeScanState *tidrangestate)
107 : : {
108 : 101 : TidRangeScan *node = (TidRangeScan *) tidrangestate->ss.ps.plan;
109 : 101 : List *tidexprs = NIL;
110 : : ListCell *l;
111 : :
112 [ + - + + : 217 : foreach(l, node->tidrangequals)
+ + ]
113 : : {
114 : 116 : OpExpr *opexpr = lfirst(l);
115 : : TidOpExpr *tidopexpr;
116 : :
117 [ - + ]: 116 : if (!IsA(opexpr, OpExpr))
1142 drowley@postgresql.o 118 [ # # ]:UBC 0 : elog(ERROR, "could not identify CTID expression");
119 : :
1142 drowley@postgresql.o 120 :CBC 116 : tidopexpr = MakeTidOpExpr(opexpr, tidrangestate);
121 : 116 : tidexprs = lappend(tidexprs, tidopexpr);
122 : : }
123 : :
124 : 101 : tidrangestate->trss_tidexprs = tidexprs;
125 : 101 : }
126 : :
127 : : /* ----------------------------------------------------------------
128 : : * TidRangeEval
129 : : *
130 : : * Compute and set node's block and offset range to scan by evaluating
131 : : * the trss_tidexprs. Returns false if we detect the range cannot
132 : : * contain any tuples. Returns true if it's possible for the range to
133 : : * contain tuples.
134 : : * ----------------------------------------------------------------
135 : : */
136 : : static bool
137 : 92 : TidRangeEval(TidRangeScanState *node)
138 : : {
139 : 92 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
140 : : ItemPointerData lowerBound;
141 : : ItemPointerData upperBound;
142 : : ListCell *l;
143 : :
144 : : /*
145 : : * Set the upper and lower bounds to the absolute limits of the range of
146 : : * the ItemPointer type. Below we'll try to narrow this range on either
147 : : * side by looking at the TidOpExprs.
148 : : */
149 : 92 : ItemPointerSet(&lowerBound, 0, 0);
150 : 92 : ItemPointerSet(&upperBound, InvalidBlockNumber, PG_UINT16_MAX);
151 : :
152 [ + - + + : 190 : foreach(l, node->trss_tidexprs)
+ + ]
153 : : {
154 : 101 : TidOpExpr *tidopexpr = (TidOpExpr *) lfirst(l);
155 : : ItemPointer itemptr;
156 : : bool isNull;
157 : :
158 : : /* Evaluate this bound. */
159 : : itemptr = (ItemPointer)
160 : 101 : DatumGetPointer(ExecEvalExprSwitchContext(tidopexpr->exprstate,
161 : : econtext,
162 : : &isNull));
163 : :
164 : : /* If the bound is NULL, *nothing* matches the qual. */
165 [ + + ]: 101 : if (isNull)
166 : 3 : return false;
167 : :
168 [ + + ]: 98 : if (tidopexpr->exprtype == TIDEXPR_LOWER_BOUND)
169 : : {
170 : : ItemPointerData lb;
171 : :
172 : 29 : ItemPointerCopy(itemptr, &lb);
173 : :
174 : : /*
175 : : * Normalize non-inclusive ranges to become inclusive. The
176 : : * resulting ItemPointer here may not be a valid item pointer.
177 : : */
178 [ + + ]: 29 : if (!tidopexpr->inclusive)
179 : 23 : ItemPointerInc(&lb);
180 : :
181 : : /* Check if we can narrow the range using this qual */
182 [ + - ]: 29 : if (ItemPointerCompare(&lb, &lowerBound) > 0)
183 : 29 : ItemPointerCopy(&lb, &lowerBound);
184 : : }
185 : :
186 [ + - ]: 69 : else if (tidopexpr->exprtype == TIDEXPR_UPPER_BOUND)
187 : : {
188 : : ItemPointerData ub;
189 : :
190 : 69 : ItemPointerCopy(itemptr, &ub);
191 : :
192 : : /*
193 : : * Normalize non-inclusive ranges to become inclusive. The
194 : : * resulting ItemPointer here may not be a valid item pointer.
195 : : */
196 [ + + ]: 69 : if (!tidopexpr->inclusive)
197 : 30 : ItemPointerDec(&ub);
198 : :
199 : : /* Check if we can narrow the range using this qual */
200 [ + - ]: 69 : if (ItemPointerCompare(&ub, &upperBound) < 0)
201 : 69 : ItemPointerCopy(&ub, &upperBound);
202 : : }
203 : : }
204 : :
205 : 89 : ItemPointerCopy(&lowerBound, &node->trss_mintid);
206 : 89 : ItemPointerCopy(&upperBound, &node->trss_maxtid);
207 : :
208 : 89 : return true;
209 : : }
210 : :
211 : : /* ----------------------------------------------------------------
212 : : * TidRangeNext
213 : : *
214 : : * Retrieve a tuple from the TidRangeScan node's currentRelation
215 : : * using the TIDs in the TidRangeScanState information.
216 : : *
217 : : * ----------------------------------------------------------------
218 : : */
219 : : static TupleTableSlot *
220 : 2973 : TidRangeNext(TidRangeScanState *node)
221 : : {
222 : : TableScanDesc scandesc;
223 : : EState *estate;
224 : : ScanDirection direction;
225 : : TupleTableSlot *slot;
226 : :
227 : : /*
228 : : * extract necessary information from TID scan node
229 : : */
230 : 2973 : scandesc = node->ss.ss_currentScanDesc;
231 : 2973 : estate = node->ss.ps.state;
232 : 2973 : slot = node->ss.ss_ScanTupleSlot;
233 : 2973 : direction = estate->es_direction;
234 : :
235 [ + + ]: 2973 : if (!node->trss_inScan)
236 : : {
237 : : /* First time through, compute TID range to scan */
238 [ + + ]: 92 : if (!TidRangeEval(node))
239 : 3 : return NULL;
240 : :
241 [ + + ]: 89 : if (scandesc == NULL)
242 : : {
243 : 56 : scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation,
244 : : estate->es_snapshot,
245 : : &node->trss_mintid,
246 : : &node->trss_maxtid);
247 : 56 : node->ss.ss_currentScanDesc = scandesc;
248 : : }
249 : : else
250 : : {
251 : : /* rescan with the updated TID range */
252 : 33 : table_rescan_tidrange(scandesc, &node->trss_mintid,
253 : : &node->trss_maxtid);
254 : : }
255 : :
256 : 89 : node->trss_inScan = true;
257 : : }
258 : :
259 : : /* Fetch the next tuple. */
260 [ + + ]: 2970 : if (!table_scan_getnextslot_tidrange(scandesc, direction, slot))
261 : : {
262 : 83 : node->trss_inScan = false;
263 : 83 : ExecClearTuple(slot);
264 : : }
265 : :
266 : 2970 : return slot;
267 : : }
268 : :
269 : : /*
270 : : * TidRangeRecheck -- access method routine to recheck a tuple in EvalPlanQual
271 : : */
272 : : static bool
1142 drowley@postgresql.o 273 :UBC 0 : TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot)
274 : : {
275 : 0 : return true;
276 : : }
277 : :
278 : : /* ----------------------------------------------------------------
279 : : * ExecTidRangeScan(node)
280 : : *
281 : : * Scans the relation using tids and returns the next qualifying tuple.
282 : : * We call the ExecScan() routine and pass it the appropriate
283 : : * access method functions.
284 : : *
285 : : * Conditions:
286 : : * -- the "cursor" maintained by the AMI is positioned at the tuple
287 : : * returned previously.
288 : : *
289 : : * Initial States:
290 : : * -- the relation indicated is opened for TID range scanning.
291 : : * ----------------------------------------------------------------
292 : : */
293 : : static TupleTableSlot *
1142 drowley@postgresql.o 294 :CBC 2973 : ExecTidRangeScan(PlanState *pstate)
295 : : {
296 : 2973 : TidRangeScanState *node = castNode(TidRangeScanState, pstate);
297 : :
298 : 2973 : return ExecScan(&node->ss,
299 : : (ExecScanAccessMtd) TidRangeNext,
300 : : (ExecScanRecheckMtd) TidRangeRecheck);
301 : : }
302 : :
303 : : /* ----------------------------------------------------------------
304 : : * ExecReScanTidRangeScan(node)
305 : : * ----------------------------------------------------------------
306 : : */
307 : : void
308 : 33 : ExecReScanTidRangeScan(TidRangeScanState *node)
309 : : {
310 : : /* mark scan as not in progress, and tid range list as not computed yet */
311 : 33 : node->trss_inScan = false;
312 : :
313 : : /*
314 : : * We must wait until TidRangeNext before calling table_rescan_tidrange.
315 : : */
316 : 33 : ExecScanReScan(&node->ss);
317 : 33 : }
318 : :
319 : : /* ----------------------------------------------------------------
320 : : * ExecEndTidRangeScan
321 : : *
322 : : * Releases any storage allocated through C routines.
323 : : * Returns nothing.
324 : : * ----------------------------------------------------------------
325 : : */
326 : : void
327 : 101 : ExecEndTidRangeScan(TidRangeScanState *node)
328 : : {
329 : 101 : TableScanDesc scan = node->ss.ss_currentScanDesc;
330 : :
331 [ + + ]: 101 : if (scan != NULL)
332 : 56 : table_endscan(scan);
333 : 101 : }
334 : :
335 : : /* ----------------------------------------------------------------
336 : : * ExecInitTidRangeScan
337 : : *
338 : : * Initializes the tid range scan's state information, creates
339 : : * scan keys, and opens the scan relation.
340 : : *
341 : : * Parameters:
342 : : * node: TidRangeScan node produced by the planner.
343 : : * estate: the execution state initialized in InitPlan.
344 : : * ----------------------------------------------------------------
345 : : */
346 : : TidRangeScanState *
347 : 101 : ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags)
348 : : {
349 : : TidRangeScanState *tidrangestate;
350 : : Relation currentRelation;
351 : :
352 : : /*
353 : : * create state structure
354 : : */
355 : 101 : tidrangestate = makeNode(TidRangeScanState);
356 : 101 : tidrangestate->ss.ps.plan = (Plan *) node;
357 : 101 : tidrangestate->ss.ps.state = estate;
358 : 101 : tidrangestate->ss.ps.ExecProcNode = ExecTidRangeScan;
359 : :
360 : : /*
361 : : * Miscellaneous initialization
362 : : *
363 : : * create expression context for node
364 : : */
365 : 101 : ExecAssignExprContext(estate, &tidrangestate->ss.ps);
366 : :
367 : : /*
368 : : * mark scan as not in progress, and TID range as not computed yet
369 : : */
370 : 101 : tidrangestate->trss_inScan = false;
371 : :
372 : : /*
373 : : * open the scan relation
374 : : */
375 : 101 : currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags);
376 : :
377 : 101 : tidrangestate->ss.ss_currentRelation = currentRelation;
378 : 101 : tidrangestate->ss.ss_currentScanDesc = NULL; /* no table scan here */
379 : :
380 : : /*
381 : : * get the scan type from the relation descriptor.
382 : : */
383 : 101 : ExecInitScanTupleSlot(estate, &tidrangestate->ss,
384 : : RelationGetDescr(currentRelation),
385 : : table_slot_callbacks(currentRelation));
386 : :
387 : : /*
388 : : * Initialize result type and projection.
389 : : */
390 : 101 : ExecInitResultTypeTL(&tidrangestate->ss.ps);
391 : 101 : ExecAssignScanProjectionInfo(&tidrangestate->ss);
392 : :
393 : : /*
394 : : * initialize child expressions
395 : : */
396 : 101 : tidrangestate->ss.ps.qual =
397 : 101 : ExecInitQual(node->scan.plan.qual, (PlanState *) tidrangestate);
398 : :
399 : 101 : TidExprListCreate(tidrangestate);
400 : :
401 : : /*
402 : : * all done.
403 : : */
404 : 101 : return tidrangestate;
405 : : }
|