/src/postgres/src/backend/executor/nodeTidrangescan.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * nodeTidrangescan.c |
4 | | * Routines to support TID range scans of relations |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, 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 | 0 | ((node) != NULL && \ |
35 | 0 | IsA((node), Var) && \ |
36 | 0 | ((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 * |
57 | | MakeTidOpExpr(OpExpr *expr, TidRangeScanState *tidstate) |
58 | 0 | { |
59 | 0 | Node *arg1 = get_leftop((Expr *) expr); |
60 | 0 | Node *arg2 = get_rightop((Expr *) expr); |
61 | 0 | ExprState *exprstate = NULL; |
62 | 0 | bool invert = false; |
63 | 0 | TidOpExpr *tidopexpr; |
64 | |
|
65 | 0 | if (IsCTIDVar(arg1)) |
66 | 0 | exprstate = ExecInitExpr((Expr *) arg2, &tidstate->ss.ps); |
67 | 0 | else if (IsCTIDVar(arg2)) |
68 | 0 | { |
69 | 0 | exprstate = ExecInitExpr((Expr *) arg1, &tidstate->ss.ps); |
70 | 0 | invert = true; |
71 | 0 | } |
72 | 0 | else |
73 | 0 | elog(ERROR, "could not identify CTID variable"); |
74 | | |
75 | 0 | tidopexpr = (TidOpExpr *) palloc(sizeof(TidOpExpr)); |
76 | 0 | tidopexpr->inclusive = false; /* for now */ |
77 | |
|
78 | 0 | switch (expr->opno) |
79 | 0 | { |
80 | 0 | case TIDLessEqOperator: |
81 | 0 | tidopexpr->inclusive = true; |
82 | | /* fall through */ |
83 | 0 | case TIDLessOperator: |
84 | 0 | tidopexpr->exprtype = invert ? TIDEXPR_LOWER_BOUND : TIDEXPR_UPPER_BOUND; |
85 | 0 | break; |
86 | 0 | case TIDGreaterEqOperator: |
87 | 0 | tidopexpr->inclusive = true; |
88 | | /* fall through */ |
89 | 0 | case TIDGreaterOperator: |
90 | 0 | tidopexpr->exprtype = invert ? TIDEXPR_UPPER_BOUND : TIDEXPR_LOWER_BOUND; |
91 | 0 | break; |
92 | 0 | default: |
93 | 0 | elog(ERROR, "could not identify CTID operator"); |
94 | 0 | } |
95 | | |
96 | 0 | tidopexpr->exprstate = exprstate; |
97 | |
|
98 | 0 | return tidopexpr; |
99 | 0 | } |
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 | | TidExprListCreate(TidRangeScanState *tidrangestate) |
107 | 0 | { |
108 | 0 | TidRangeScan *node = (TidRangeScan *) tidrangestate->ss.ps.plan; |
109 | 0 | List *tidexprs = NIL; |
110 | 0 | ListCell *l; |
111 | |
|
112 | 0 | foreach(l, node->tidrangequals) |
113 | 0 | { |
114 | 0 | OpExpr *opexpr = lfirst(l); |
115 | 0 | TidOpExpr *tidopexpr; |
116 | |
|
117 | 0 | if (!IsA(opexpr, OpExpr)) |
118 | 0 | elog(ERROR, "could not identify CTID expression"); |
119 | | |
120 | 0 | tidopexpr = MakeTidOpExpr(opexpr, tidrangestate); |
121 | 0 | tidexprs = lappend(tidexprs, tidopexpr); |
122 | 0 | } |
123 | | |
124 | 0 | tidrangestate->trss_tidexprs = tidexprs; |
125 | 0 | } |
126 | | |
127 | | /* ---------------------------------------------------------------- |
128 | | * TidRangeEval |
129 | | * |
130 | | * Compute and set node's block and offset range to scan by evaluating |
131 | | * node->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. We don't bother validating that trss_mintid is less |
134 | | * than or equal to trss_maxtid, as the scan_set_tidrange() table AM |
135 | | * function will handle that. |
136 | | * ---------------------------------------------------------------- |
137 | | */ |
138 | | static bool |
139 | | TidRangeEval(TidRangeScanState *node) |
140 | 0 | { |
141 | 0 | ExprContext *econtext = node->ss.ps.ps_ExprContext; |
142 | 0 | ItemPointerData lowerBound; |
143 | 0 | ItemPointerData upperBound; |
144 | 0 | ListCell *l; |
145 | | |
146 | | /* |
147 | | * Set the upper and lower bounds to the absolute limits of the range of |
148 | | * the ItemPointer type. Below we'll try to narrow this range on either |
149 | | * side by looking at the TidOpExprs. |
150 | | */ |
151 | 0 | ItemPointerSet(&lowerBound, 0, 0); |
152 | 0 | ItemPointerSet(&upperBound, InvalidBlockNumber, PG_UINT16_MAX); |
153 | |
|
154 | 0 | foreach(l, node->trss_tidexprs) |
155 | 0 | { |
156 | 0 | TidOpExpr *tidopexpr = (TidOpExpr *) lfirst(l); |
157 | 0 | ItemPointer itemptr; |
158 | 0 | bool isNull; |
159 | | |
160 | | /* Evaluate this bound. */ |
161 | 0 | itemptr = (ItemPointer) |
162 | 0 | DatumGetPointer(ExecEvalExprSwitchContext(tidopexpr->exprstate, |
163 | 0 | econtext, |
164 | 0 | &isNull)); |
165 | | |
166 | | /* If the bound is NULL, *nothing* matches the qual. */ |
167 | 0 | if (isNull) |
168 | 0 | return false; |
169 | | |
170 | 0 | if (tidopexpr->exprtype == TIDEXPR_LOWER_BOUND) |
171 | 0 | { |
172 | 0 | ItemPointerData lb; |
173 | |
|
174 | 0 | ItemPointerCopy(itemptr, &lb); |
175 | | |
176 | | /* |
177 | | * Normalize non-inclusive ranges to become inclusive. The |
178 | | * resulting ItemPointer here may not be a valid item pointer. |
179 | | */ |
180 | 0 | if (!tidopexpr->inclusive) |
181 | 0 | ItemPointerInc(&lb); |
182 | | |
183 | | /* Check if we can narrow the range using this qual */ |
184 | 0 | if (ItemPointerCompare(&lb, &lowerBound) > 0) |
185 | 0 | ItemPointerCopy(&lb, &lowerBound); |
186 | 0 | } |
187 | | |
188 | 0 | else if (tidopexpr->exprtype == TIDEXPR_UPPER_BOUND) |
189 | 0 | { |
190 | 0 | ItemPointerData ub; |
191 | |
|
192 | 0 | ItemPointerCopy(itemptr, &ub); |
193 | | |
194 | | /* |
195 | | * Normalize non-inclusive ranges to become inclusive. The |
196 | | * resulting ItemPointer here may not be a valid item pointer. |
197 | | */ |
198 | 0 | if (!tidopexpr->inclusive) |
199 | 0 | ItemPointerDec(&ub); |
200 | | |
201 | | /* Check if we can narrow the range using this qual */ |
202 | 0 | if (ItemPointerCompare(&ub, &upperBound) < 0) |
203 | 0 | ItemPointerCopy(&ub, &upperBound); |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | 0 | ItemPointerCopy(&lowerBound, &node->trss_mintid); |
208 | 0 | ItemPointerCopy(&upperBound, &node->trss_maxtid); |
209 | |
|
210 | 0 | return true; |
211 | 0 | } |
212 | | |
213 | | /* ---------------------------------------------------------------- |
214 | | * TidRangeNext |
215 | | * |
216 | | * Retrieve a tuple from the TidRangeScan node's currentRelation |
217 | | * using the TIDs in the TidRangeScanState information. |
218 | | * |
219 | | * ---------------------------------------------------------------- |
220 | | */ |
221 | | static TupleTableSlot * |
222 | | TidRangeNext(TidRangeScanState *node) |
223 | 0 | { |
224 | 0 | TableScanDesc scandesc; |
225 | 0 | EState *estate; |
226 | 0 | ScanDirection direction; |
227 | 0 | TupleTableSlot *slot; |
228 | | |
229 | | /* |
230 | | * extract necessary information from TID scan node |
231 | | */ |
232 | 0 | scandesc = node->ss.ss_currentScanDesc; |
233 | 0 | estate = node->ss.ps.state; |
234 | 0 | slot = node->ss.ss_ScanTupleSlot; |
235 | 0 | direction = estate->es_direction; |
236 | |
|
237 | 0 | if (!node->trss_inScan) |
238 | 0 | { |
239 | | /* First time through, compute TID range to scan */ |
240 | 0 | if (!TidRangeEval(node)) |
241 | 0 | return NULL; |
242 | | |
243 | 0 | if (scandesc == NULL) |
244 | 0 | { |
245 | 0 | scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation, |
246 | 0 | estate->es_snapshot, |
247 | 0 | &node->trss_mintid, |
248 | 0 | &node->trss_maxtid); |
249 | 0 | node->ss.ss_currentScanDesc = scandesc; |
250 | 0 | } |
251 | 0 | else |
252 | 0 | { |
253 | | /* rescan with the updated TID range */ |
254 | 0 | table_rescan_tidrange(scandesc, &node->trss_mintid, |
255 | 0 | &node->trss_maxtid); |
256 | 0 | } |
257 | |
|
258 | 0 | node->trss_inScan = true; |
259 | 0 | } |
260 | | |
261 | | /* Fetch the next tuple. */ |
262 | 0 | if (!table_scan_getnextslot_tidrange(scandesc, direction, slot)) |
263 | 0 | { |
264 | 0 | node->trss_inScan = false; |
265 | 0 | ExecClearTuple(slot); |
266 | 0 | } |
267 | |
|
268 | 0 | return slot; |
269 | 0 | } |
270 | | |
271 | | /* |
272 | | * TidRangeRecheck -- access method routine to recheck a tuple in EvalPlanQual |
273 | | */ |
274 | | static bool |
275 | | TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot) |
276 | 0 | { |
277 | 0 | if (!TidRangeEval(node)) |
278 | 0 | return false; |
279 | | |
280 | 0 | Assert(ItemPointerIsValid(&slot->tts_tid)); |
281 | | |
282 | | /* Recheck the ctid is still within range */ |
283 | 0 | if (ItemPointerCompare(&slot->tts_tid, &node->trss_mintid) < 0 || |
284 | 0 | ItemPointerCompare(&slot->tts_tid, &node->trss_maxtid) > 0) |
285 | 0 | return false; |
286 | | |
287 | 0 | return true; |
288 | 0 | } |
289 | | |
290 | | /* ---------------------------------------------------------------- |
291 | | * ExecTidRangeScan(node) |
292 | | * |
293 | | * Scans the relation using tids and returns the next qualifying tuple. |
294 | | * We call the ExecScan() routine and pass it the appropriate |
295 | | * access method functions. |
296 | | * |
297 | | * Conditions: |
298 | | * -- the "cursor" maintained by the AMI is positioned at the tuple |
299 | | * returned previously. |
300 | | * |
301 | | * Initial States: |
302 | | * -- the relation indicated is opened for TID range scanning. |
303 | | * ---------------------------------------------------------------- |
304 | | */ |
305 | | static TupleTableSlot * |
306 | | ExecTidRangeScan(PlanState *pstate) |
307 | 0 | { |
308 | 0 | TidRangeScanState *node = castNode(TidRangeScanState, pstate); |
309 | |
|
310 | 0 | return ExecScan(&node->ss, |
311 | 0 | (ExecScanAccessMtd) TidRangeNext, |
312 | 0 | (ExecScanRecheckMtd) TidRangeRecheck); |
313 | 0 | } |
314 | | |
315 | | /* ---------------------------------------------------------------- |
316 | | * ExecReScanTidRangeScan(node) |
317 | | * ---------------------------------------------------------------- |
318 | | */ |
319 | | void |
320 | | ExecReScanTidRangeScan(TidRangeScanState *node) |
321 | 0 | { |
322 | | /* mark scan as not in progress, and tid range list as not computed yet */ |
323 | 0 | node->trss_inScan = false; |
324 | | |
325 | | /* |
326 | | * We must wait until TidRangeNext before calling table_rescan_tidrange. |
327 | | */ |
328 | 0 | ExecScanReScan(&node->ss); |
329 | 0 | } |
330 | | |
331 | | /* ---------------------------------------------------------------- |
332 | | * ExecEndTidRangeScan |
333 | | * |
334 | | * Releases any storage allocated through C routines. |
335 | | * Returns nothing. |
336 | | * ---------------------------------------------------------------- |
337 | | */ |
338 | | void |
339 | | ExecEndTidRangeScan(TidRangeScanState *node) |
340 | 0 | { |
341 | 0 | TableScanDesc scan = node->ss.ss_currentScanDesc; |
342 | |
|
343 | 0 | if (scan != NULL) |
344 | 0 | table_endscan(scan); |
345 | 0 | } |
346 | | |
347 | | /* ---------------------------------------------------------------- |
348 | | * ExecInitTidRangeScan |
349 | | * |
350 | | * Initializes the tid range scan's state information, creates |
351 | | * scan keys, and opens the scan relation. |
352 | | * |
353 | | * Parameters: |
354 | | * node: TidRangeScan node produced by the planner. |
355 | | * estate: the execution state initialized in InitPlan. |
356 | | * ---------------------------------------------------------------- |
357 | | */ |
358 | | TidRangeScanState * |
359 | | ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags) |
360 | 0 | { |
361 | 0 | TidRangeScanState *tidrangestate; |
362 | 0 | Relation currentRelation; |
363 | | |
364 | | /* |
365 | | * create state structure |
366 | | */ |
367 | 0 | tidrangestate = makeNode(TidRangeScanState); |
368 | 0 | tidrangestate->ss.ps.plan = (Plan *) node; |
369 | 0 | tidrangestate->ss.ps.state = estate; |
370 | 0 | tidrangestate->ss.ps.ExecProcNode = ExecTidRangeScan; |
371 | | |
372 | | /* |
373 | | * Miscellaneous initialization |
374 | | * |
375 | | * create expression context for node |
376 | | */ |
377 | 0 | ExecAssignExprContext(estate, &tidrangestate->ss.ps); |
378 | | |
379 | | /* |
380 | | * mark scan as not in progress, and TID range as not computed yet |
381 | | */ |
382 | 0 | tidrangestate->trss_inScan = false; |
383 | | |
384 | | /* |
385 | | * open the scan relation |
386 | | */ |
387 | 0 | currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags); |
388 | |
|
389 | 0 | tidrangestate->ss.ss_currentRelation = currentRelation; |
390 | 0 | tidrangestate->ss.ss_currentScanDesc = NULL; /* no table scan here */ |
391 | | |
392 | | /* |
393 | | * get the scan type from the relation descriptor. |
394 | | */ |
395 | 0 | ExecInitScanTupleSlot(estate, &tidrangestate->ss, |
396 | 0 | RelationGetDescr(currentRelation), |
397 | 0 | table_slot_callbacks(currentRelation)); |
398 | | |
399 | | /* |
400 | | * Initialize result type and projection. |
401 | | */ |
402 | 0 | ExecInitResultTypeTL(&tidrangestate->ss.ps); |
403 | 0 | ExecAssignScanProjectionInfo(&tidrangestate->ss); |
404 | | |
405 | | /* |
406 | | * initialize child expressions |
407 | | */ |
408 | 0 | tidrangestate->ss.ps.qual = |
409 | 0 | ExecInitQual(node->scan.plan.qual, (PlanState *) tidrangestate); |
410 | |
|
411 | 0 | TidExprListCreate(tidrangestate); |
412 | | |
413 | | /* |
414 | | * all done. |
415 | | */ |
416 | 0 | return tidrangestate; |
417 | 0 | } |