Coverage Report

Created: 2025-09-27 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}