Branch data 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 */
40 : 1934446 : ExecMaterial(PlanState *pstate)
41 : : {
42 : 1934446 : 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 : :
50 [ - + ]: 1934446 : CHECK_FOR_INTERRUPTS();
51 : :
52 : : /*
53 : : * get state info from node
54 : : */
55 : 1934446 : estate = node->ss.ps.state;
56 : 1934446 : dir = estate->es_direction;
57 : 1934446 : forward = ScanDirectionIsForward(dir);
58 : 1934446 : tuplestorestate = node->tuplestorestate;
59 : :
60 : : /*
61 : : * If first time through, and we need a tuplestore, initialize it.
62 : : */
63 [ + + + + ]: 1934446 : if (tuplestorestate == NULL && node->eflags != 0)
64 : : {
65 : 2014 : tuplestorestate = tuplestore_begin_heap(true, false, work_mem);
66 : 2014 : tuplestore_set_eflags(tuplestorestate, node->eflags);
67 [ + + ]: 2014 : 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 : :
75 : 84 : ptrno = tuplestore_alloc_read_pointer(tuplestorestate,
76 : : node->eflags);
77 : : Assert(ptrno == 1);
78 : : }
79 : 2014 : 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 : : */
86 [ + + + + ]: 3868827 : eof_tuplestore = (tuplestorestate == NULL) ||
87 : 1934381 : tuplestore_ateof(tuplestorestate);
88 : :
89 [ + + + + ]: 1934446 : 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 : : */
99 [ # # ]: 0 : if (!tuplestore_advance(tuplestorestate, forward))
100 : 0 : return NULL; /* the tuplestore must be empty */
101 : : }
102 : 8 : eof_tuplestore = false;
103 : : }
104 : :
105 : : /*
106 : : * If we can fetch another tuple from the tuplestore, return it.
107 : : */
108 : 1934446 : slot = node->ss.ps.ps_ResultTupleSlot;
109 [ + + ]: 1934446 : if (!eof_tuplestore)
110 : : {
111 [ + + ]: 1915834 : if (tuplestore_gettupleslot(tuplestorestate, forward, false, slot))
112 : 1777770 : return slot;
113 [ + + ]: 138064 : if (forward)
114 : 138056 : 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 [ + + + + ]: 156676 : 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 : 24723 : outerNode = outerPlanState(node);
135 : 24723 : outerslot = ExecProcNode(outerNode);
136 [ + + + + ]: 24723 : if (TupIsNull(outerslot))
137 : : {
138 : 1873 : node->eof_underlying = true;
139 : 1873 : 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 : : */
147 [ + + ]: 22850 : if (tuplestorestate)
148 : 22788 : tuplestore_puttupleslot(tuplestorestate, outerslot);
149 : :
150 : 22850 : ExecCopySlot(slot, outerslot);
151 : 22850 : return slot;
152 : : }
153 : :
154 : : /*
155 : : * Nothing left ...
156 : : */
157 : 131953 : return ExecClearTuple(slot);
158 : : }
159 : :
160 : : /* ----------------------------------------------------------------
161 : : * ExecInitMaterial
162 : : * ----------------------------------------------------------------
163 : : */
164 : : MaterialState *
165 : 3070 : ExecInitMaterial(Material *node, EState *estate, int eflags)
166 : : {
167 : : MaterialState *matstate;
168 : : Plan *outerPlan;
169 : :
170 : : /*
171 : : * create state structure
172 : : */
173 : 3070 : matstate = makeNode(MaterialState);
174 : 3070 : matstate->ss.ps.plan = (Plan *) node;
175 : 3070 : matstate->ss.ps.state = estate;
176 : 3070 : 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 : : */
184 : 3070 : 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 : : */
195 [ + + ]: 3070 : if (eflags & EXEC_FLAG_BACKWARD)
196 : 13 : matstate->eflags |= EXEC_FLAG_REWIND;
197 : :
198 : 3070 : matstate->eof_underlying = false;
199 : 3070 : matstate->tuplestorestate = NULL;
200 : 3070 : 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 : : */
215 : 3070 : eflags &= ~(EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK);
216 : :
217 : 3070 : outerPlan = outerPlan(node);
218 : 3070 : 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 : : */
226 : 3070 : ExecInitResultTupleSlotTL(&matstate->ss.ps, &TTSOpsMinimalTuple);
227 : 3070 : matstate->ss.ps.ps_ProjInfo = NULL;
228 : :
229 : : /*
230 : : * initialize tuple type.
231 : : */
232 : 3070 : ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss, &TTSOpsMinimalTuple);
233 : :
234 : 3070 : return matstate;
235 : : }
236 : :
237 : : /* ----------------------------------------------------------------
238 : : * ExecEndMaterial
239 : : * ----------------------------------------------------------------
240 : : */
241 : : void
242 : 3026 : 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 : : */
251 [ - + - - ]: 3026 : if (node->shared_info != NULL && IsParallelWorker() &&
252 [ # # ]: 0 : node->tuplestorestate != NULL)
253 : : {
254 : : MaterialInstrumentation *si;
255 : : bool usedDisk;
256 : : int64 maxSpaceUsed;
257 : :
258 : : 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 : : */
270 [ + + ]: 3026 : if (node->tuplestorestate != NULL)
271 : 1970 : tuplestore_end(node->tuplestorestate);
272 : 3026 : node->tuplestorestate = NULL;
273 : :
274 : : /*
275 : : * shut down the subplan
276 : : */
277 : 3026 : ExecEndNode(outerPlanState(node));
278 : 3026 : }
279 : :
280 : : /* ----------------------------------------------------------------
281 : : * ExecMaterialMarkPos
282 : : *
283 : : * Calls tuplestore to save the current position in the stored file.
284 : : * ----------------------------------------------------------------
285 : : */
286 : : void
287 : 4341 : ExecMaterialMarkPos(MaterialState *node)
288 : : {
289 : : Assert(node->eflags & EXEC_FLAG_MARK);
290 : :
291 : : /*
292 : : * if we haven't materialized yet, just return.
293 : : */
294 [ + + ]: 4341 : if (!node->tuplestorestate)
295 : 8 : return;
296 : :
297 : : /*
298 : : * copy the active read pointer to the mark.
299 : : */
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 : : */
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
315 : 36132 : ExecMaterialRestrPos(MaterialState *node)
316 : : {
317 : : Assert(node->eflags & EXEC_FLAG_MARK);
318 : :
319 : : /*
320 : : * if we haven't materialized yet, just return.
321 : : */
322 [ - + ]: 36132 : if (!node->tuplestorestate)
323 : 0 : return;
324 : :
325 : : /*
326 : : * copy the mark to the active read pointer.
327 : : */
328 : 36132 : tuplestore_copy_read_pointer(node->tuplestorestate, 1, 0);
329 : : }
330 : :
331 : : /* ----------------------------------------------------------------
332 : : * ExecReScanMaterial
333 : : *
334 : : * Rescans the materialized relation.
335 : : * ----------------------------------------------------------------
336 : : */
337 : : void
338 : 139026 : ExecReScanMaterial(MaterialState *node)
339 : : {
340 : 139026 : PlanState *outerPlan = outerPlanState(node);
341 : :
342 : 139026 : ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
343 : :
344 [ + + ]: 139026 : 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 : : */
351 [ + + ]: 139024 : if (!node->tuplestorestate)
352 : 1921 : 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 : : */
364 [ + + ]: 137103 : if (outerPlan->chgParam != NULL ||
365 [ - + ]: 137095 : (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 : : */
371 : 8 : tuplestore_clear(node->tuplestorestate);
372 [ - + ]: 8 : if (outerPlan->chgParam == NULL)
373 : 0 : ExecReScan(outerPlan);
374 : 8 : node->eof_underlying = false;
375 : : }
376 : : else
377 : 137095 : 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 : : */
387 [ - + ]: 2 : if (outerPlan->chgParam == NULL)
388 : 0 : ExecReScan(outerPlan);
389 : 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
405 : 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 : : }
|