/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 | } |