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