Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeMaterial.c
4 : : * Routines to handle materialization nodes.
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/nodeMaterial.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : /*
16 : : * INTERFACE ROUTINES
17 : : * ExecMaterial - materialize the result of a subplan
18 : : * ExecInitMaterial - initialize node and subnodes
19 : : * ExecEndMaterial - shutdown node and subnodes
20 : : *
21 : : */
22 : : #include "postgres.h"
23 : :
24 : : #include "executor/executor.h"
25 : : #include "executor/nodeMaterial.h"
26 : : #include "miscadmin.h"
27 : : #include "utils/tuplestore.h"
28 : :
29 : : /* ----------------------------------------------------------------
30 : : * ExecMaterial
31 : : *
32 : : * As long as we are at the end of the data collected in the tuplestore,
33 : : * we collect one new row from the subplan on each call, and stash it
34 : : * aside in the tuplestore before returning it. The tuplestore is
35 : : * only read if we are asked to scan backwards, rescan, or mark/restore.
36 : : *
37 : : * ----------------------------------------------------------------
38 : : */
39 : : static TupleTableSlot * /* result tuple from subplan */
3374 andres@anarazel.de 40 :CBC 1772499 : ExecMaterial(PlanState *pstate)
41 : : {
42 : 1772499 : MaterialState *node = castNode(MaterialState, pstate);
43 : : EState *estate;
44 : : ScanDirection dir;
45 : : bool forward;
46 : : Tuplestorestate *tuplestorestate;
47 : : bool eof_tuplestore;
48 : : TupleTableSlot *slot;
49 : :
3366 50 [ - + ]: 1772499 : CHECK_FOR_INTERRUPTS();
51 : :
52 : : /*
53 : : * get state info from node
54 : : */
8712 tgl@sss.pgh.pa.us 55 : 1772499 : estate = node->ss.ps.state;
10627 bruce@momjian.us 56 : 1772499 : dir = estate->es_direction;
8618 tgl@sss.pgh.pa.us 57 : 1772499 : forward = ScanDirectionIsForward(dir);
6585 58 : 1772499 : tuplestorestate = node->tuplestorestate;
59 : :
60 : : /*
61 : : * If first time through, and we need a tuplestore, initialize it.
62 : : */
7084 63 [ + + + + ]: 1772499 : if (tuplestorestate == NULL && node->eflags != 0)
64 : : {
8287 65 : 2051 : tuplestorestate = tuplestore_begin_heap(true, false, work_mem);
7084 66 : 2051 : tuplestore_set_eflags(tuplestorestate, node->eflags);
6585 67 [ + + ]: 2051 : if (node->eflags & EXEC_FLAG_MARK)
68 : : {
69 : : /*
70 : : * Allocate a second read pointer to serve as the mark. We know it
71 : : * must have index 1, so needn't store that.
72 : : */
73 : : int ptrno PG_USED_FOR_ASSERTS_ONLY;
74 : :
6498 75 : 84 : ptrno = tuplestore_alloc_read_pointer(tuplestorestate,
76 : : node->eflags);
77 [ - + ]: 84 : Assert(ptrno == 1);
78 : : }
6585 79 : 2051 : node->tuplestorestate = tuplestorestate;
80 : : }
81 : :
82 : : /*
83 : : * If we are not at the end of the tuplestore, or are going backwards, try
84 : : * to fetch a tuple from tuplestore.
85 : : */
7531 86 [ + + + + ]: 3544933 : eof_tuplestore = (tuplestorestate == NULL) ||
87 : 1772434 : tuplestore_ateof(tuplestorestate);
88 : :
8618 89 [ + + + + ]: 1772499 : if (!forward && eof_tuplestore)
90 : : {
91 [ - + ]: 8 : if (!node->eof_underlying)
92 : : {
93 : : /*
94 : : * When reversing direction at tuplestore EOF, the first
95 : : * gettupleslot call will fetch the last-added tuple; but we want
96 : : * to return the one before that, if possible. So do an extra
97 : : * fetch.
98 : : */
7412 tgl@sss.pgh.pa.us 99 [ # # ]:UBC 0 : if (!tuplestore_advance(tuplestorestate, forward))
8470 bruce@momjian.us 100 : 0 : return NULL; /* the tuplestore must be empty */
101 : : }
8618 tgl@sss.pgh.pa.us 102 :CBC 8 : eof_tuplestore = false;
103 : : }
104 : :
105 : : /*
106 : : * If we can fetch another tuple from the tuplestore, return it.
107 : : */
7412 108 : 1772499 : slot = node->ss.ps.ps_ResultTupleSlot;
8618 109 [ + + ]: 1772499 : if (!eof_tuplestore)
110 : : {
6408 111 [ + + ]: 1749580 : if (tuplestore_gettupleslot(tuplestorestate, forward, false, slot))
7412 112 : 1617941 : return slot;
113 [ + + ]: 131639 : if (forward)
8618 114 : 131631 : eof_tuplestore = true;
115 : : }
116 : :
117 : : /*
118 : : * If necessary, try to fetch another row from the subplan.
119 : : *
120 : : * Note: the eof_underlying state variable exists to short-circuit further
121 : : * subplan calls. It's not optional, unfortunately, because some plan
122 : : * node types are not robust about being called again when they've already
123 : : * returned NULL.
124 : : */
125 [ + + + + ]: 154558 : if (eof_tuplestore && !node->eof_underlying)
126 : : {
127 : : PlanState *outerNode;
128 : : TupleTableSlot *outerslot;
129 : :
130 : : /*
131 : : * We can only get here with forward==true, so no need to worry about
132 : : * which direction the subplan will go.
133 : : */
134 : 29064 : outerNode = outerPlanState(node);
135 : 29064 : outerslot = ExecProcNode(outerNode);
136 [ + + + + ]: 29064 : if (TupIsNull(outerslot))
137 : : {
138 : 1913 : node->eof_underlying = true;
139 : 1913 : return NULL;
140 : : }
141 : :
142 : : /*
143 : : * Append a copy of the returned tuple to tuplestore. NOTE: because
144 : : * the tuplestore is certainly in EOF state, its read position will
145 : : * move forward over the added tuple. This is what we want.
146 : : */
7531 147 [ + + ]: 27151 : if (tuplestorestate)
7412 148 : 27089 : tuplestore_puttupleslot(tuplestorestate, outerslot);
149 : :
2888 andres@anarazel.de 150 : 27151 : ExecCopySlot(slot, outerslot);
151 : 27151 : return slot;
152 : : }
153 : :
154 : : /*
155 : : * Nothing left ...
156 : : */
7412 tgl@sss.pgh.pa.us 157 : 125494 : return ExecClearTuple(slot);
158 : : }
159 : :
160 : : /* ----------------------------------------------------------------
161 : : * ExecInitMaterial
162 : : * ----------------------------------------------------------------
163 : : */
164 : : MaterialState *
7531 165 : 3075 : ExecInitMaterial(Material *node, EState *estate, int eflags)
166 : : {
167 : : MaterialState *matstate;
168 : : Plan *outerPlan;
169 : :
170 : : /*
171 : : * create state structure
172 : : */
10627 bruce@momjian.us 173 : 3075 : matstate = makeNode(MaterialState);
8712 tgl@sss.pgh.pa.us 174 : 3075 : matstate->ss.ps.plan = (Plan *) node;
175 : 3075 : matstate->ss.ps.state = estate;
3374 andres@anarazel.de 176 : 3075 : matstate->ss.ps.ExecProcNode = ExecMaterial;
177 : :
178 : : /*
179 : : * We must have a tuplestore buffering the subplan output to do backward
180 : : * scan or mark/restore. We also prefer to materialize the subplan output
181 : : * if we might be called on to rewind and replay it many times. However,
182 : : * if none of these cases apply, we can skip storing the data.
183 : : */
7084 tgl@sss.pgh.pa.us 184 : 3075 : matstate->eflags = (eflags & (EXEC_FLAG_REWIND |
185 : : EXEC_FLAG_BACKWARD |
186 : : EXEC_FLAG_MARK));
187 : :
188 : : /*
189 : : * Tuplestore's interpretation of the flag bits is subtly different from
190 : : * the general executor meaning: it doesn't think BACKWARD necessarily
191 : : * means "backwards all the way to start". If told to support BACKWARD we
192 : : * must include REWIND in the tuplestore eflags, else tuplestore_trim
193 : : * might throw away too much.
194 : : */
6498 195 [ + + ]: 3075 : if (eflags & EXEC_FLAG_BACKWARD)
196 : 13 : matstate->eflags |= EXEC_FLAG_REWIND;
197 : :
8618 198 : 3075 : matstate->eof_underlying = false;
7531 199 : 3075 : matstate->tuplestorestate = NULL;
0 drowley@postgresql.o 200 :GNC 3075 : matstate->shared_info = NULL;
201 : :
202 : : /*
203 : : * Miscellaneous initialization
204 : : *
205 : : * Materialization nodes don't need ExprContexts because they never call
206 : : * ExecQual or ExecProject.
207 : : */
208 : :
209 : : /*
210 : : * initialize child nodes
211 : : *
212 : : * We shield the child node from the need to support REWIND, BACKWARD, or
213 : : * MARK/RESTORE.
214 : : */
7531 tgl@sss.pgh.pa.us 215 :CBC 3075 : eflags &= ~(EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK);
216 : :
8712 217 : 3075 : outerPlan = outerPlan(node);
7531 218 : 3075 : outerPlanState(matstate) = ExecInitNode(outerPlan, estate, eflags);
219 : :
220 : : /*
221 : : * Initialize result type and slot. No need to initialize projection info
222 : : * because this node doesn't do projections.
223 : : *
224 : : * material nodes only return tuples from their materialized relation.
225 : : */
2888 andres@anarazel.de 226 : 3075 : ExecInitResultTupleSlotTL(&matstate->ss.ps, &TTSOpsMinimalTuple);
8712 tgl@sss.pgh.pa.us 227 : 3075 : matstate->ss.ps.ps_ProjInfo = NULL;
228 : :
229 : : /*
230 : : * initialize tuple type.
231 : : */
2888 andres@anarazel.de 232 : 3075 : ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss, &TTSOpsMinimalTuple);
233 : :
8712 tgl@sss.pgh.pa.us 234 : 3075 : return matstate;
235 : : }
236 : :
237 : : /* ----------------------------------------------------------------
238 : : * ExecEndMaterial
239 : : * ----------------------------------------------------------------
240 : : */
241 : : void
242 : 3031 : ExecEndMaterial(MaterialState *node)
243 : : {
244 : : /*
245 : : * When ending a parallel worker, copy the tuplestore usage details into
246 : : * shared memory so that they can be picked up by the main process to
247 : : * report in EXPLAIN ANALYZE. The Gather node above us may be rescanned,
248 : : * in which case a new set of workers will write to the same slots, so
249 : : * merge with any existing values rather than overwriting them.
250 : : */
0 drowley@postgresql.o 251 [ - + - - ]:GNC 3031 : if (node->shared_info != NULL && IsParallelWorker() &&
0 drowley@postgresql.o 252 [ # # ]:UNC 0 : node->tuplestorestate != NULL)
253 : : {
254 : : MaterialInstrumentation *si;
255 : : bool usedDisk;
256 : : int64 maxSpaceUsed;
257 : :
258 [ # # ]: 0 : Assert(ParallelWorkerNumber < node->shared_info->num_workers);
259 : :
260 : 0 : tuplestore_get_stats(node->tuplestorestate, &usedDisk, &maxSpaceUsed);
261 : :
262 : 0 : si = &node->shared_info->sinstrument[ParallelWorkerNumber];
263 : 0 : si->maxSpaceUsed = Max(si->maxSpaceUsed, maxSpaceUsed);
264 : 0 : si->usedDisk |= usedDisk;
265 : : }
266 : :
267 : : /*
268 : : * Release tuplestore resources
269 : : */
8712 tgl@sss.pgh.pa.us 270 [ + + ]:CBC 3031 : if (node->tuplestorestate != NULL)
6585 271 : 2007 : tuplestore_end(node->tuplestorestate);
8712 272 : 3031 : node->tuplestorestate = NULL;
273 : :
274 : : /*
275 : : * shut down the subplan
276 : : */
8702 277 : 3031 : ExecEndNode(outerPlanState(node));
10627 bruce@momjian.us 278 : 3031 : }
279 : :
280 : : /* ----------------------------------------------------------------
281 : : * ExecMaterialMarkPos
282 : : *
283 : : * Calls tuplestore to save the current position in the stored file.
284 : : * ----------------------------------------------------------------
285 : : */
286 : : void
8712 tgl@sss.pgh.pa.us 287 : 4341 : ExecMaterialMarkPos(MaterialState *node)
288 : : {
7084 289 [ - + ]: 4341 : Assert(node->eflags & EXEC_FLAG_MARK);
290 : :
291 : : /*
292 : : * if we haven't materialized yet, just return.
293 : : */
8712 294 [ + + ]: 4341 : if (!node->tuplestorestate)
10468 vadim4o@yahoo.com 295 : 8 : return;
296 : :
297 : : /*
298 : : * copy the active read pointer to the mark.
299 : : */
6585 tgl@sss.pgh.pa.us 300 : 4333 : tuplestore_copy_read_pointer(node->tuplestorestate, 0, 1);
301 : :
302 : : /*
303 : : * since we may have advanced the mark, try to truncate the tuplestore.
304 : : */
6498 305 : 4333 : tuplestore_trim(node->tuplestorestate);
306 : : }
307 : :
308 : : /* ----------------------------------------------------------------
309 : : * ExecMaterialRestrPos
310 : : *
311 : : * Calls tuplestore to restore the last saved file position.
312 : : * ----------------------------------------------------------------
313 : : */
314 : : void
8712 315 : 36132 : ExecMaterialRestrPos(MaterialState *node)
316 : : {
7084 317 [ - + ]: 36132 : Assert(node->eflags & EXEC_FLAG_MARK);
318 : :
319 : : /*
320 : : * if we haven't materialized yet, just return.
321 : : */
8712 322 [ - + ]: 36132 : if (!node->tuplestorestate)
9612 tgl@sss.pgh.pa.us 323 :UBC 0 : return;
324 : :
325 : : /*
326 : : * copy the mark to the active read pointer.
327 : : */
6585 tgl@sss.pgh.pa.us 328 :CBC 36132 : tuplestore_copy_read_pointer(node->tuplestorestate, 1, 0);
329 : : }
330 : :
331 : : /* ----------------------------------------------------------------
332 : : * ExecReScanMaterial
333 : : *
334 : : * Rescans the materialized relation.
335 : : * ----------------------------------------------------------------
336 : : */
337 : : void
5936 338 : 132353 : ExecReScanMaterial(MaterialState *node)
339 : : {
4160 bruce@momjian.us 340 : 132353 : PlanState *outerPlan = outerPlanState(node);
341 : :
8712 tgl@sss.pgh.pa.us 342 : 132353 : ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
343 : :
7084 344 [ + + ]: 132353 : if (node->eflags != 0)
345 : : {
346 : : /*
347 : : * If we haven't materialized yet, just return. If outerplan's
348 : : * chgParam is not NULL then it will be re-scanned by ExecProcNode,
349 : : * else no reason to re-scan it at all.
350 : : */
7531 351 [ + + ]: 132351 : if (!node->tuplestorestate)
352 : 1958 : return;
353 : :
354 : : /*
355 : : * If subnode is to be rescanned then we clear out the previously
356 : : * stored results; we have to re-read the subplan and re-store. Also,
357 : : * if we told tuplestore it needn't support rescan, we lose and must
358 : : * re-read. (This last should not happen in common cases; else our
359 : : * caller lied by not passing EXEC_FLAG_REWIND to us.)
360 : : *
361 : : * Otherwise we can just rewind and rescan the stored output. The
362 : : * state of the subnode does not change.
363 : : */
4179 rhaas@postgresql.org 364 [ + + ]: 130393 : if (outerPlan->chgParam != NULL ||
7084 tgl@sss.pgh.pa.us 365 [ - + ]: 130385 : (node->eflags & EXEC_FLAG_REWIND) == 0)
366 : : {
367 : : /*
368 : : * It's important to use tuplestore_clear() here so that we keep
369 : : * track of the maximum storage used by all rescans.
370 : : */
7 drowley@postgresql.o 371 :GNC 8 : tuplestore_clear(node->tuplestorestate);
4179 rhaas@postgresql.org 372 [ - + ]:CBC 8 : if (outerPlan->chgParam == NULL)
4179 rhaas@postgresql.org 373 :UBC 0 : ExecReScan(outerPlan);
7531 tgl@sss.pgh.pa.us 374 :CBC 8 : node->eof_underlying = false;
375 : : }
376 : : else
6585 377 : 130385 : tuplestore_rescan(node->tuplestorestate);
378 : : }
379 : : else
380 : : {
381 : : /* In this case we are just passing on the subquery's output */
382 : :
383 : : /*
384 : : * if chgParam of subnode is not null then plan will be re-scanned by
385 : : * first ExecProcNode.
386 : : */
4179 rhaas@postgresql.org 387 [ - + ]: 2 : if (outerPlan->chgParam == NULL)
4179 rhaas@postgresql.org 388 :UBC 0 : ExecReScan(outerPlan);
7531 tgl@sss.pgh.pa.us 389 :CBC 2 : node->eof_underlying = false;
390 : : }
391 : : }
392 : :
393 : : /* ----------------------------------------------------------------
394 : : * Parallel Query Support
395 : : * ----------------------------------------------------------------
396 : : */
397 : :
398 : : /* ----------------------------------------------------------------
399 : : * ExecMaterialEstimate
400 : : *
401 : : * Estimate space required to propagate material statistics.
402 : : * ----------------------------------------------------------------
403 : : */
404 : : void
0 drowley@postgresql.o 405 :UNC 0 : ExecMaterialEstimate(MaterialState *node, ParallelContext *pcxt)
406 : : {
407 : : Size size;
408 : :
409 : : /* don't need this if not instrumenting or no workers */
410 [ # # # # ]: 0 : if (!node->ss.ps.instrument || pcxt->nworkers == 0)
411 : 0 : return;
412 : :
413 : 0 : size = mul_size(pcxt->nworkers, sizeof(MaterialInstrumentation));
414 : 0 : size = add_size(size, offsetof(SharedMaterialInfo, sinstrument));
415 : 0 : shm_toc_estimate_chunk(&pcxt->estimator, size);
416 : 0 : shm_toc_estimate_keys(&pcxt->estimator, 1);
417 : : }
418 : :
419 : : /* ----------------------------------------------------------------
420 : : * ExecMaterialInitializeDSM
421 : : *
422 : : * Initialize DSM space for material statistics.
423 : : * ----------------------------------------------------------------
424 : : */
425 : : void
426 : 0 : ExecMaterialInitializeDSM(MaterialState *node, ParallelContext *pcxt)
427 : : {
428 : : Size size;
429 : :
430 : : /* don't need this if not instrumenting or no workers */
431 [ # # # # ]: 0 : if (!node->ss.ps.instrument || pcxt->nworkers == 0)
432 : 0 : return;
433 : :
434 : 0 : size = offsetof(SharedMaterialInfo, sinstrument)
435 : 0 : + pcxt->nworkers * sizeof(MaterialInstrumentation);
436 : 0 : node->shared_info = shm_toc_allocate(pcxt->toc, size);
437 : : /* ensure any unfilled slots will contain zeroes */
438 : 0 : memset(node->shared_info, 0, size);
439 : 0 : node->shared_info->num_workers = pcxt->nworkers;
440 : 0 : shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id,
441 : 0 : node->shared_info);
442 : : }
443 : :
444 : : /* ----------------------------------------------------------------
445 : : * ExecMaterialInitializeWorker
446 : : *
447 : : * Attach worker to DSM space for material statistics.
448 : : * ----------------------------------------------------------------
449 : : */
450 : : void
451 : 0 : ExecMaterialInitializeWorker(MaterialState *node, ParallelWorkerContext *pwcxt)
452 : : {
453 : 0 : node->shared_info =
454 : 0 : shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
455 : 0 : }
456 : :
457 : : /* ----------------------------------------------------------------
458 : : * ExecMaterialRetrieveInstrumentation
459 : : *
460 : : * Transfer material statistics from DSM to private memory.
461 : : * ----------------------------------------------------------------
462 : : */
463 : : void
464 : 0 : ExecMaterialRetrieveInstrumentation(MaterialState *node)
465 : : {
466 : : Size size;
467 : : SharedMaterialInfo *si;
468 : :
469 [ # # ]: 0 : if (node->shared_info == NULL)
470 : 0 : return;
471 : :
472 : 0 : size = offsetof(SharedMaterialInfo, sinstrument)
473 : 0 : + node->shared_info->num_workers * sizeof(MaterialInstrumentation);
474 : 0 : si = palloc(size);
475 : 0 : memcpy(si, node->shared_info, size);
476 : 0 : node->shared_info = si;
477 : : }
|