Coverage Report

Created: 2026-08-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/executor/nodeSeqscan.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * nodeSeqscan.c
4
 *    Support routines for sequential scans of relations.
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/executor/nodeSeqscan.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
/*
16
 * INTERFACE ROUTINES
17
 *    ExecSeqScan       sequentially scans a relation.
18
 *    ExecSeqNext       retrieve next tuple in sequential order.
19
 *    ExecInitSeqScan     creates and initializes a seqscan node.
20
 *    ExecEndSeqScan      releases any storage allocated.
21
 *    ExecReScanSeqScan   rescans the relation
22
 *
23
 *    ExecSeqScanEstimate   estimates DSM space needed for parallel scan
24
 *    ExecSeqScanInitializeDSM initialize DSM for parallel scan
25
 *    ExecSeqScanReInitializeDSM reinitialize DSM for fresh parallel scan
26
 *    ExecSeqScanInitializeWorker attach to DSM info in parallel worker
27
 */
28
#include "postgres.h"
29
30
#include "access/relscan.h"
31
#include "access/tableam.h"
32
#include "executor/execParallel.h"
33
#include "executor/execScan.h"
34
#include "executor/executor.h"
35
#include "executor/nodeSeqscan.h"
36
#include "utils/rel.h"
37
38
static TupleTableSlot *SeqNext(SeqScanState *node);
39
40
/* ----------------------------------------------------------------
41
 *            Scan Support
42
 * ----------------------------------------------------------------
43
 */
44
45
/* ----------------------------------------------------------------
46
 *    SeqNext
47
 *
48
 *    This is a workhorse for ExecSeqScan
49
 * ----------------------------------------------------------------
50
 */
51
static pg_always_inline TupleTableSlot *
52
SeqNext(SeqScanState *node)
53
0
{
54
0
  TableScanDesc scandesc;
55
0
  EState     *estate;
56
0
  ScanDirection direction;
57
0
  TupleTableSlot *slot;
58
59
  /*
60
   * get information from the estate and scan state
61
   */
62
0
  scandesc = node->ss.ss_currentScanDesc;
63
0
  estate = node->ss.ps.state;
64
0
  direction = estate->es_direction;
65
0
  slot = node->ss.ss_ScanTupleSlot;
66
67
0
  if (scandesc == NULL)
68
0
  {
69
0
    uint32    flags = SO_NONE;
70
71
0
    if (ScanRelIsReadOnly(&node->ss))
72
0
      flags |= SO_HINT_REL_READ_ONLY;
73
74
0
    if (estate->es_instrument & INSTRUMENT_IO)
75
0
      flags |= SO_SCAN_INSTRUMENT;
76
77
    /*
78
     * We reach here if the scan is not parallel, or if we're serially
79
     * executing a scan that was planned to be parallel.
80
     */
81
0
    scandesc = table_beginscan(node->ss.ss_currentRelation,
82
0
                   estate->es_snapshot,
83
0
                   0, NULL, flags);
84
0
    node->ss.ss_currentScanDesc = scandesc;
85
0
  }
86
87
  /*
88
   * get the next tuple from the table
89
   */
90
0
  if (table_scan_getnextslot(scandesc, direction, slot))
91
0
    return slot;
92
0
  return NULL;
93
0
}
94
95
/*
96
 * SeqRecheck -- access method routine to recheck a tuple in EvalPlanQual
97
 */
98
static pg_always_inline bool
99
SeqRecheck(SeqScanState *node, TupleTableSlot *slot)
100
0
{
101
  /*
102
   * Note that unlike IndexScan, SeqScan never use keys in heap_beginscan
103
   * (and this is very bad) - so, here we do not check are keys ok or not.
104
   */
105
0
  return true;
106
0
}
107
108
/* ----------------------------------------------------------------
109
 *    ExecSeqScan(node)
110
 *
111
 *    Scans the relation sequentially and returns the next qualifying
112
 *    tuple. This variant is used when there is no es_epq_active, no qual
113
 *    and no projection.  Passing const-NULLs for these to ExecScanExtended
114
 *    allows the compiler to eliminate the additional code that would
115
 *    ordinarily be required for the evaluation of these.
116
 * ----------------------------------------------------------------
117
 */
118
static TupleTableSlot *
119
ExecSeqScan(PlanState *pstate)
120
0
{
121
0
  SeqScanState *node = castNode(SeqScanState, pstate);
122
123
0
  Assert(pstate->state->es_epq_active == NULL);
124
0
  Assert(pstate->qual == NULL);
125
0
  Assert(pstate->ps_ProjInfo == NULL);
126
127
0
  return ExecScanExtended(&node->ss,
128
0
              (ExecScanAccessMtd) SeqNext,
129
0
              (ExecScanRecheckMtd) SeqRecheck,
130
0
              NULL,
131
0
              NULL,
132
0
              NULL);
133
0
}
134
135
/*
136
 * Variant of ExecSeqScan() but when qual evaluation is required.
137
 */
138
static TupleTableSlot *
139
ExecSeqScanWithQual(PlanState *pstate)
140
0
{
141
0
  SeqScanState *node = castNode(SeqScanState, pstate);
142
143
  /*
144
   * Use pg_assume() for != NULL tests to make the compiler realize no
145
   * runtime check for the field is needed in ExecScanExtended().
146
   */
147
0
  Assert(pstate->state->es_epq_active == NULL);
148
0
  pg_assume(pstate->qual != NULL);
149
0
  Assert(pstate->ps_ProjInfo == NULL);
150
151
0
  return ExecScanExtended(&node->ss,
152
0
              (ExecScanAccessMtd) SeqNext,
153
0
              (ExecScanRecheckMtd) SeqRecheck,
154
0
              NULL,
155
0
              pstate->qual,
156
0
              NULL);
157
0
}
158
159
/*
160
 * Variant of ExecSeqScan() but when projection is required.
161
 */
162
static TupleTableSlot *
163
ExecSeqScanWithProject(PlanState *pstate)
164
0
{
165
0
  SeqScanState *node = castNode(SeqScanState, pstate);
166
167
0
  Assert(pstate->state->es_epq_active == NULL);
168
0
  Assert(pstate->qual == NULL);
169
0
  pg_assume(pstate->ps_ProjInfo != NULL);
170
171
0
  return ExecScanExtended(&node->ss,
172
0
              (ExecScanAccessMtd) SeqNext,
173
0
              (ExecScanRecheckMtd) SeqRecheck,
174
0
              NULL,
175
0
              NULL,
176
0
              pstate->ps_ProjInfo);
177
0
}
178
179
/*
180
 * Variant of ExecSeqScan() but when qual evaluation and projection are
181
 * required.
182
 */
183
static TupleTableSlot *
184
ExecSeqScanWithQualProject(PlanState *pstate)
185
0
{
186
0
  SeqScanState *node = castNode(SeqScanState, pstate);
187
188
0
  Assert(pstate->state->es_epq_active == NULL);
189
0
  pg_assume(pstate->qual != NULL);
190
0
  pg_assume(pstate->ps_ProjInfo != NULL);
191
192
0
  return ExecScanExtended(&node->ss,
193
0
              (ExecScanAccessMtd) SeqNext,
194
0
              (ExecScanRecheckMtd) SeqRecheck,
195
0
              NULL,
196
0
              pstate->qual,
197
0
              pstate->ps_ProjInfo);
198
0
}
199
200
/*
201
 * Variant of ExecSeqScan for when EPQ evaluation is required.  We don't
202
 * bother adding variants of this for with/without qual and projection as
203
 * EPQ doesn't seem as exciting a case to optimize for.
204
 */
205
static TupleTableSlot *
206
ExecSeqScanEPQ(PlanState *pstate)
207
0
{
208
0
  SeqScanState *node = castNode(SeqScanState, pstate);
209
210
0
  return ExecScan(&node->ss,
211
0
          (ExecScanAccessMtd) SeqNext,
212
0
          (ExecScanRecheckMtd) SeqRecheck);
213
0
}
214
215
/* ----------------------------------------------------------------
216
 *    ExecInitSeqScan
217
 * ----------------------------------------------------------------
218
 */
219
SeqScanState *
220
ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
221
0
{
222
0
  SeqScanState *scanstate;
223
224
  /*
225
   * Once upon a time it was possible to have an outerPlan of a SeqScan, but
226
   * not any more.
227
   */
228
0
  Assert(outerPlan(node) == NULL);
229
0
  Assert(innerPlan(node) == NULL);
230
231
  /*
232
   * create state structure
233
   */
234
0
  scanstate = makeNode(SeqScanState);
235
0
  scanstate->ss.ps.plan = (Plan *) node;
236
0
  scanstate->ss.ps.state = estate;
237
238
  /*
239
   * Miscellaneous initialization
240
   *
241
   * create expression context for node
242
   */
243
0
  ExecAssignExprContext(estate, &scanstate->ss.ps);
244
245
  /*
246
   * open the scan relation
247
   */
248
0
  scanstate->ss.ss_currentRelation =
249
0
    ExecOpenScanRelation(estate,
250
0
               node->scan.scanrelid,
251
0
               eflags);
252
253
  /* and create slot with the appropriate rowtype */
254
0
  ExecInitScanTupleSlot(estate, &scanstate->ss,
255
0
              RelationGetDescr(scanstate->ss.ss_currentRelation),
256
0
              table_slot_callbacks(scanstate->ss.ss_currentRelation),
257
0
              TTS_FLAG_OBEYS_NOT_NULL_CONSTRAINTS);
258
259
  /*
260
   * Initialize result type and projection.
261
   */
262
0
  ExecInitResultTypeTL(&scanstate->ss.ps);
263
0
  ExecAssignScanProjectionInfo(&scanstate->ss);
264
265
  /*
266
   * initialize child expressions
267
   */
268
0
  scanstate->ss.ps.qual =
269
0
    ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
270
271
  /*
272
   * When EvalPlanQual() is not in use, assign ExecProcNode for this node
273
   * based on the presence of qual and projection. Each ExecSeqScan*()
274
   * variant is optimized for the specific combination of these conditions.
275
   */
276
0
  if (scanstate->ss.ps.state->es_epq_active != NULL)
277
0
    scanstate->ss.ps.ExecProcNode = ExecSeqScanEPQ;
278
0
  else if (scanstate->ss.ps.qual == NULL)
279
0
  {
280
0
    if (scanstate->ss.ps.ps_ProjInfo == NULL)
281
0
      scanstate->ss.ps.ExecProcNode = ExecSeqScan;
282
0
    else
283
0
      scanstate->ss.ps.ExecProcNode = ExecSeqScanWithProject;
284
0
  }
285
0
  else
286
0
  {
287
0
    if (scanstate->ss.ps.ps_ProjInfo == NULL)
288
0
      scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQual;
289
0
    else
290
0
      scanstate->ss.ps.ExecProcNode = ExecSeqScanWithQualProject;
291
0
  }
292
293
0
  return scanstate;
294
0
}
295
296
/* ----------------------------------------------------------------
297
 *    ExecEndSeqScan
298
 *
299
 *    frees any storage allocated through C routines.
300
 * ----------------------------------------------------------------
301
 */
302
void
303
ExecEndSeqScan(SeqScanState *node)
304
0
{
305
0
  TableScanDesc scanDesc;
306
307
  /*
308
   * get information from node
309
   */
310
0
  scanDesc = node->ss.ss_currentScanDesc;
311
312
  /*
313
   * Collect I/O stats for this process into shared instrumentation.
314
   */
315
0
  if (node->sinstrument != NULL && IsParallelWorker())
316
0
  {
317
0
    SeqScanInstrumentation *si;
318
319
0
    Assert(ParallelWorkerNumber < node->sinstrument->num_workers);
320
0
    si = &node->sinstrument->sinstrument[ParallelWorkerNumber];
321
322
0
    if (scanDesc && scanDesc->rs_instrument)
323
0
    {
324
0
      AccumulateIOStats(&si->stats.io, &scanDesc->rs_instrument->io);
325
0
    }
326
0
  }
327
328
  /*
329
   * close heap scan
330
   */
331
0
  if (scanDesc != NULL)
332
0
    table_endscan(scanDesc);
333
0
}
334
335
/* ----------------------------------------------------------------
336
 *            Join Support
337
 * ----------------------------------------------------------------
338
 */
339
340
/* ----------------------------------------------------------------
341
 *    ExecReScanSeqScan
342
 *
343
 *    Rescans the relation.
344
 * ----------------------------------------------------------------
345
 */
346
void
347
ExecReScanSeqScan(SeqScanState *node)
348
0
{
349
0
  TableScanDesc scan;
350
351
0
  scan = node->ss.ss_currentScanDesc;
352
353
0
  if (scan != NULL)
354
0
    table_rescan(scan,    /* scan desc */
355
0
           NULL);   /* new scan keys */
356
357
0
  ExecScanReScan((ScanState *) node);
358
0
}
359
360
/* ----------------------------------------------------------------
361
 *            Parallel Scan Support
362
 * ----------------------------------------------------------------
363
 */
364
365
/* ----------------------------------------------------------------
366
 *    ExecSeqScanEstimate
367
 *
368
 *    Compute the amount of space we'll need in the parallel
369
 *    query DSM, and inform pcxt->estimator about our needs.
370
 * ----------------------------------------------------------------
371
 */
372
void
373
ExecSeqScanEstimate(SeqScanState *node,
374
          ParallelContext *pcxt)
375
0
{
376
0
  EState     *estate = node->ss.ps.state;
377
378
0
  node->pscan_len = table_parallelscan_estimate(node->ss.ss_currentRelation,
379
0
                          estate->es_snapshot);
380
0
  shm_toc_estimate_chunk(&pcxt->estimator, node->pscan_len);
381
0
  shm_toc_estimate_keys(&pcxt->estimator, 1);
382
0
}
383
384
/* ----------------------------------------------------------------
385
 *    ExecSeqScanInitializeDSM
386
 *
387
 *    Set up a parallel heap scan descriptor.
388
 * ----------------------------------------------------------------
389
 */
390
void
391
ExecSeqScanInitializeDSM(SeqScanState *node,
392
             ParallelContext *pcxt)
393
0
{
394
0
  EState     *estate = node->ss.ps.state;
395
0
  ParallelTableScanDesc pscan;
396
0
  uint32    flags = SO_NONE;
397
398
0
  if (ScanRelIsReadOnly(&node->ss))
399
0
    flags |= SO_HINT_REL_READ_ONLY;
400
401
0
  if (estate->es_instrument & INSTRUMENT_IO)
402
0
    flags |= SO_SCAN_INSTRUMENT;
403
404
0
  pscan = shm_toc_allocate(pcxt->toc, node->pscan_len);
405
0
  table_parallelscan_initialize(node->ss.ss_currentRelation,
406
0
                  pscan,
407
0
                  estate->es_snapshot);
408
0
  shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
409
410
0
  node->ss.ss_currentScanDesc =
411
0
    table_beginscan_parallel(node->ss.ss_currentRelation, pscan, flags);
412
0
}
413
414
/* ----------------------------------------------------------------
415
 *    ExecSeqScanReInitializeDSM
416
 *
417
 *    Reset shared state before beginning a fresh scan.
418
 * ----------------------------------------------------------------
419
 */
420
void
421
ExecSeqScanReInitializeDSM(SeqScanState *node,
422
               ParallelContext *pcxt)
423
0
{
424
0
  ParallelTableScanDesc pscan;
425
426
0
  pscan = node->ss.ss_currentScanDesc->rs_parallel;
427
0
  table_parallelscan_reinitialize(node->ss.ss_currentRelation, pscan);
428
0
}
429
430
/* ----------------------------------------------------------------
431
 *    ExecSeqScanInitializeWorker
432
 *
433
 *    Copy relevant information from TOC into planstate.
434
 * ----------------------------------------------------------------
435
 */
436
void
437
ExecSeqScanInitializeWorker(SeqScanState *node,
438
              ParallelWorkerContext *pwcxt)
439
0
{
440
0
  ParallelTableScanDesc pscan;
441
0
  uint32    flags = SO_NONE;
442
443
0
  if (ScanRelIsReadOnly(&node->ss))
444
0
    flags |= SO_HINT_REL_READ_ONLY;
445
446
0
  if (node->ss.ps.state->es_instrument & INSTRUMENT_IO)
447
0
    flags |= SO_SCAN_INSTRUMENT;
448
449
0
  pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
450
0
  node->ss.ss_currentScanDesc =
451
0
    table_beginscan_parallel(node->ss.ss_currentRelation, pscan, flags);
452
0
}
453
454
/*
455
 * Compute the amount of space we'll need for the shared instrumentation and
456
 * inform pcxt->estimator.
457
 */
458
void
459
ExecSeqScanInstrumentEstimate(SeqScanState *node, ParallelContext *pcxt)
460
0
{
461
0
  EState     *estate = node->ss.ps.state;
462
0
  Size    size;
463
464
0
  if ((estate->es_instrument & INSTRUMENT_IO) == 0 || pcxt->nworkers == 0)
465
0
    return;
466
467
0
  size = add_size(offsetof(SharedSeqScanInstrumentation, sinstrument),
468
0
          mul_size(pcxt->nworkers, sizeof(SeqScanInstrumentation)));
469
470
0
  shm_toc_estimate_chunk(&pcxt->estimator, size);
471
0
  shm_toc_estimate_keys(&pcxt->estimator, 1);
472
0
}
473
474
/*
475
 * Set up parallel sequential scan instrumentation.
476
 */
477
void
478
ExecSeqScanInstrumentInitDSM(SeqScanState *node, ParallelContext *pcxt)
479
0
{
480
0
  EState     *estate = node->ss.ps.state;
481
0
  SharedSeqScanInstrumentation *sinstrument;
482
0
  Size    size;
483
484
0
  if ((estate->es_instrument & INSTRUMENT_IO) == 0 || pcxt->nworkers == 0)
485
0
    return;
486
487
0
  size = add_size(offsetof(SharedSeqScanInstrumentation, sinstrument),
488
0
          mul_size(pcxt->nworkers, sizeof(SeqScanInstrumentation)));
489
0
  sinstrument = shm_toc_allocate(pcxt->toc, size);
490
0
  memset(sinstrument, 0, size);
491
0
  sinstrument->num_workers = pcxt->nworkers;
492
0
  shm_toc_insert(pcxt->toc,
493
0
           node->ss.ps.plan->plan_node_id +
494
0
           PARALLEL_KEY_SCAN_INSTRUMENT_OFFSET,
495
0
           sinstrument);
496
0
  node->sinstrument = sinstrument;
497
0
}
498
499
/*
500
 * Look up and save the location of the shared instrumentation.
501
 */
502
void
503
ExecSeqScanInstrumentInitWorker(SeqScanState *node,
504
                ParallelWorkerContext *pwcxt)
505
0
{
506
0
  EState     *estate = node->ss.ps.state;
507
508
0
  if ((estate->es_instrument & INSTRUMENT_IO) == 0)
509
0
    return;
510
511
0
  node->sinstrument = shm_toc_lookup(pwcxt->toc,
512
0
                     node->ss.ps.plan->plan_node_id +
513
0
                     PARALLEL_KEY_SCAN_INSTRUMENT_OFFSET,
514
0
                     false);
515
0
}
516
517
/*
518
 * Transfer sequential scan instrumentation from DSM to private memory.
519
 */
520
void
521
ExecSeqScanRetrieveInstrumentation(SeqScanState *node)
522
0
{
523
0
  SharedSeqScanInstrumentation *sinstrument = node->sinstrument;
524
0
  Size    size;
525
526
0
  if (sinstrument == NULL)
527
0
    return;
528
529
0
  size = offsetof(SharedSeqScanInstrumentation, sinstrument)
530
0
    + sinstrument->num_workers * sizeof(SeqScanInstrumentation);
531
532
0
  node->sinstrument = palloc(size);
533
0
  memcpy(node->sinstrument, sinstrument, size);
534
0
}