Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeFunctionscan.c
4 : : * Support routines for scanning RangeFunctions (functions in rangetable).
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/nodeFunctionscan.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : /*
16 : : * INTERFACE ROUTINES
17 : : * ExecFunctionScan scans a function.
18 : : * ExecFunctionNext retrieve next tuple in sequential order.
19 : : * ExecInitFunctionScan creates and initializes a functionscan node.
20 : : * ExecEndFunctionScan releases any storage allocated.
21 : : * ExecReScanFunctionScan rescans the function
22 : : */
23 : : #include "postgres.h"
24 : :
25 : : #include "catalog/pg_type.h"
26 : : #include "executor/nodeFunctionscan.h"
27 : : #include "funcapi.h"
28 : : #include "nodes/nodeFuncs.h"
29 : : #include "utils/memutils.h"
30 : : #include "utils/tuplestore.h"
31 : :
32 : : static TupleTableSlot *FunctionNext(FunctionScanState *node);
33 : : static void update_storage_stats(FunctionScanState *node);
34 : :
35 : :
36 : : /* ----------------------------------------------------------------
37 : : * Scan Support
38 : : * ----------------------------------------------------------------
39 : : */
40 : : /* ----------------------------------------------------------------
41 : : * FunctionNext
42 : : *
43 : : * This is a workhorse for ExecFunctionScan
44 : : * ----------------------------------------------------------------
45 : : */
46 : : static TupleTableSlot *
47 : 11609999 : FunctionNext(FunctionScanState *node)
48 : : {
49 : : EState *estate;
50 : : ScanDirection direction;
51 : : TupleTableSlot *scanslot;
52 : : bool alldone;
53 : : int64 oldpos;
54 : : int funcno;
55 : : int att;
56 : :
57 : : /*
58 : : * get information from the estate and scan state
59 : : */
60 : 11609999 : estate = node->ss.ps.state;
61 : 11609999 : direction = estate->es_direction;
62 : 11609999 : scanslot = node->ss.ss_ScanTupleSlot;
63 : :
64 [ + + ]: 11609999 : if (node->simple)
65 : : {
66 : : /*
67 : : * Fast path for the trivial case: the function return type and scan
68 : : * result type are the same, so we fetch the function result straight
69 : : * into the scan result slot. No need to update ordinality or
70 : : * rowcounts either.
71 : : */
72 : 11584903 : Tuplestorestate *tstore = node->funcstates[0].tstore;
73 : :
74 : : /*
75 : : * If first time through, read all tuples from function and put them
76 : : * in a tuplestore. Subsequent calls just fetch tuples from
77 : : * tuplestore.
78 : : */
79 [ + + ]: 11584903 : if (tstore == NULL)
80 : : {
81 : 99324 : node->funcstates[0].tstore = tstore =
82 : 102152 : ExecMakeTableFunctionResult(node->funcstates[0].setexpr,
83 : : node->ss.ps.ps_ExprContext,
84 : : node->argcontext,
85 : 102152 : node->funcstates[0].tupdesc,
86 : 102152 : node->eflags & EXEC_FLAG_BACKWARD);
87 : :
88 : : /*
89 : : * paranoia - cope if the function, which may have constructed the
90 : : * tuplestore itself, didn't leave it pointing at the start. This
91 : : * call is fast, so the overhead shouldn't be an issue.
92 : : */
93 : 99324 : tuplestore_rescan(tstore);
94 : : }
95 : :
96 : : /*
97 : : * Get the next tuple from tuplestore.
98 : : */
99 : 11582075 : (void) tuplestore_gettupleslot(tstore,
100 : : ScanDirectionIsForward(direction),
101 : : false,
102 : : scanslot);
103 : 11582075 : return scanslot;
104 : : }
105 : :
106 : : /*
107 : : * Increment or decrement ordinal counter before checking for end-of-data,
108 : : * so that we can move off either end of the result by 1 (and no more than
109 : : * 1) without losing correct count. See PortalRunSelect for why we can
110 : : * assume that we won't be called repeatedly in the end-of-data state.
111 : : */
112 : 25096 : oldpos = node->ordinal;
113 [ + + ]: 25096 : if (ScanDirectionIsForward(direction))
114 : 25056 : node->ordinal++;
115 : : else
116 : 40 : node->ordinal--;
117 : :
118 : : /*
119 : : * Main loop over functions.
120 : : *
121 : : * We fetch the function results into func_slots (which match the function
122 : : * return types), and then copy the values to scanslot (which matches the
123 : : * scan result type), setting the ordinal column (if any) as well.
124 : : */
125 : 25096 : ExecClearTuple(scanslot);
126 : 25096 : att = 0;
127 : 25096 : alldone = true;
128 [ + + ]: 66938 : for (funcno = 0; funcno < node->nfuncs; funcno++)
129 : : {
130 : 41842 : FunctionScanPerFuncState *fs = &node->funcstates[funcno];
131 : : int i;
132 : :
133 : : /*
134 : : * If first time through, read all tuples from function and put them
135 : : * in a tuplestore. Subsequent calls just fetch tuples from
136 : : * tuplestore.
137 : : */
138 [ + + ]: 41842 : if (fs->tstore == NULL)
139 : : {
140 : 1343 : fs->tstore =
141 : 1343 : ExecMakeTableFunctionResult(fs->setexpr,
142 : : node->ss.ps.ps_ExprContext,
143 : : node->argcontext,
144 : : fs->tupdesc,
145 : 1343 : node->eflags & EXEC_FLAG_BACKWARD);
146 : :
147 : : /*
148 : : * paranoia - cope if the function, which may have constructed the
149 : : * tuplestore itself, didn't leave it pointing at the start. This
150 : : * call is fast, so the overhead shouldn't be an issue.
151 : : */
152 : 1343 : tuplestore_rescan(fs->tstore);
153 : : }
154 : :
155 : : /*
156 : : * Get the next tuple from tuplestore.
157 : : *
158 : : * If we have a rowcount for the function, and we know the previous
159 : : * read position was out of bounds, don't try the read. This allows
160 : : * backward scan to work when there are mixed row counts present.
161 : : */
162 [ + + + + ]: 41842 : if (fs->rowcount != -1 && fs->rowcount < oldpos)
163 : 48 : ExecClearTuple(fs->func_slot);
164 : : else
165 : 41794 : (void) tuplestore_gettupleslot(fs->tstore,
166 : : ScanDirectionIsForward(direction),
167 : : false,
168 : : fs->func_slot);
169 : :
170 [ + - + + ]: 41842 : if (TupIsNull(fs->func_slot))
171 : : {
172 : : /*
173 : : * If we ran out of data for this function in the forward
174 : : * direction then we now know how many rows it returned. We need
175 : : * to know this in order to handle backwards scans. The row count
176 : : * we store is actually 1+ the actual number, because we have to
177 : : * position the tuplestore 1 off its end sometimes.
178 : : */
179 [ + + + + ]: 1575 : if (ScanDirectionIsForward(direction) && fs->rowcount == -1)
180 : 1327 : fs->rowcount = node->ordinal;
181 : :
182 : : /*
183 : : * populate the result cols with nulls
184 : : */
185 [ + + ]: 3808 : for (i = 0; i < fs->colcount; i++)
186 : : {
187 : 2233 : scanslot->tts_values[att] = (Datum) 0;
188 : 2233 : scanslot->tts_isnull[att] = true;
189 : 2233 : att++;
190 : : }
191 : : }
192 : : else
193 : : {
194 : : /*
195 : : * we have a result, so just copy it to the result cols.
196 : : */
197 : 40267 : slot_getallattrs(fs->func_slot);
198 : :
199 [ + + ]: 89632 : for (i = 0; i < fs->colcount; i++)
200 : : {
201 : 49365 : scanslot->tts_values[att] = fs->func_slot->tts_values[i];
202 : 49365 : scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i];
203 : 49365 : att++;
204 : : }
205 : :
206 : : /*
207 : : * We're not done until every function result is exhausted; we pad
208 : : * the shorter results with nulls until then.
209 : : */
210 : 40267 : alldone = false;
211 : : }
212 : : }
213 : :
214 : : /*
215 : : * ordinal col is always last, per spec.
216 : : */
217 [ + + ]: 25096 : if (node->ordinality)
218 : : {
219 : 11864 : scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
220 : 11864 : scanslot->tts_isnull[att] = false;
221 : : }
222 : :
223 : : /*
224 : : * If alldone, we just return the previously-cleared scanslot. Otherwise,
225 : : * finish creating the virtual tuple.
226 : : */
227 [ + + ]: 25096 : if (!alldone)
228 : 23955 : ExecStoreVirtualTuple(scanslot);
229 : :
230 : 25096 : return scanslot;
231 : : }
232 : :
233 : : /*
234 : : * FunctionRecheck -- access method routine to recheck a tuple in EvalPlanQual
235 : : */
236 : : static bool
237 : 0 : FunctionRecheck(FunctionScanState *node, TupleTableSlot *slot)
238 : : {
239 : : /* nothing to check */
240 : 0 : return true;
241 : : }
242 : :
243 : : /* ----------------------------------------------------------------
244 : : * ExecFunctionScan(node)
245 : : *
246 : : * Scans the function sequentially and returns the next qualifying
247 : : * tuple.
248 : : * We call the ExecScan() routine and pass it the appropriate
249 : : * access method functions.
250 : : * ----------------------------------------------------------------
251 : : */
252 : : static TupleTableSlot *
253 : 10371593 : ExecFunctionScan(PlanState *pstate)
254 : : {
255 : 10371593 : FunctionScanState *node = castNode(FunctionScanState, pstate);
256 : :
257 : 10371593 : return ExecScan(&node->ss,
258 : : (ExecScanAccessMtd) FunctionNext,
259 : : (ExecScanRecheckMtd) FunctionRecheck);
260 : : }
261 : :
262 : : /* ----------------------------------------------------------------
263 : : * ExecInitFunctionScan
264 : : * ----------------------------------------------------------------
265 : : */
266 : : FunctionScanState *
267 : 44476 : ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
268 : : {
269 : : FunctionScanState *scanstate;
270 : 44476 : int nfuncs = list_length(node->functions);
271 : : TupleDesc scan_tupdesc;
272 : : int i,
273 : : natts;
274 : : ListCell *lc;
275 : :
276 : : /* check for unsupported flags */
277 : : Assert(!(eflags & EXEC_FLAG_MARK));
278 : :
279 : : /*
280 : : * FunctionScan should not have any children.
281 : : */
282 : : Assert(outerPlan(node) == NULL);
283 : : Assert(innerPlan(node) == NULL);
284 : :
285 : : /*
286 : : * create new ScanState for node
287 : : */
288 : 44476 : scanstate = makeNode(FunctionScanState);
289 : 44476 : scanstate->ss.ps.plan = (Plan *) node;
290 : 44476 : scanstate->ss.ps.state = estate;
291 : 44476 : scanstate->ss.ps.ExecProcNode = ExecFunctionScan;
292 : 44476 : scanstate->eflags = eflags;
293 : :
294 : : /*
295 : : * are we adding an ordinality column?
296 : : */
297 : 44476 : scanstate->ordinality = node->funcordinality;
298 : :
299 : 44476 : scanstate->nfuncs = nfuncs;
300 [ + + + + ]: 44476 : if (nfuncs == 1 && !node->funcordinality)
301 : 43692 : scanstate->simple = true;
302 : : else
303 : 784 : scanstate->simple = false;
304 : :
305 : : /*
306 : : * Ordinal 0 represents the "before the first row" position.
307 : : *
308 : : * We need to track ordinal position even when not adding an ordinality
309 : : * column to the result, in order to handle backwards scanning properly
310 : : * with multiple functions with different result sizes. (We can't position
311 : : * any individual function's tuplestore any more than 1 place beyond its
312 : : * end, so when scanning backwards, we need to know when to start
313 : : * including the function in the scan again.)
314 : : */
315 : 44476 : scanstate->ordinal = 0;
316 : :
317 : : /*
318 : : * Miscellaneous initialization
319 : : *
320 : : * create expression context for node
321 : : */
322 : 44476 : ExecAssignExprContext(estate, &scanstate->ss.ps);
323 : :
324 : 44476 : scanstate->funcstates = palloc_array(FunctionScanPerFuncState, nfuncs);
325 : :
326 : 44476 : natts = 0;
327 : 44476 : i = 0;
328 [ + - + + : 89167 : foreach(lc, node->functions)
+ + ]
329 : : {
330 : 44695 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
331 : 44695 : Node *funcexpr = rtfunc->funcexpr;
332 : 44695 : int colcount = rtfunc->funccolcount;
333 : 44695 : FunctionScanPerFuncState *fs = &scanstate->funcstates[i];
334 : : TupleDesc tupdesc;
335 : :
336 : 44691 : fs->setexpr =
337 : 44695 : ExecInitTableFunctionResult((Expr *) funcexpr,
338 : : scanstate->ss.ps.ps_ExprContext,
339 : : &scanstate->ss.ps);
340 : :
341 : : /*
342 : : * Don't allocate the tuplestores; the actual calls to the functions
343 : : * do that. NULL means that we have not called the function yet (or
344 : : * need to call it again after a rescan).
345 : : */
346 : 44691 : fs->tstore = NULL;
347 : 44691 : fs->rowcount = -1;
348 : :
349 : : /*
350 : : * Now build a tupdesc showing the result type we expect from the
351 : : * function. If we have a coldeflist then that takes priority (note
352 : : * the parser enforces that there is one if the function's nominal
353 : : * output type is RECORD). Otherwise use get_expr_result_type.
354 : : *
355 : : * Note that if the function returns a named composite type, that may
356 : : * now contain more or different columns than it did when the plan was
357 : : * made. For both that and the RECORD case, we need to check tuple
358 : : * compatibility. ExecMakeTableFunctionResult handles some of this,
359 : : * and CheckVarSlotCompatibility provides a backstop.
360 : : */
361 [ + + ]: 44691 : if (rtfunc->funccolnames != NIL)
362 : : {
363 : 980 : tupdesc = BuildDescFromLists(rtfunc->funccolnames,
364 : 490 : rtfunc->funccoltypes,
365 : 490 : rtfunc->funccoltypmods,
366 : 490 : rtfunc->funccolcollations);
367 : :
368 : : /*
369 : : * For RECORD results, make sure a typmod has been assigned. (The
370 : : * function should do this for itself, but let's cover things in
371 : : * case it doesn't.)
372 : : */
373 : 490 : BlessTupleDesc(tupdesc);
374 : : }
375 : : else
376 : : {
377 : : TypeFuncClass functypclass;
378 : : Oid funcrettype;
379 : :
380 : 44201 : functypclass = get_expr_result_type(funcexpr,
381 : : &funcrettype,
382 : : &tupdesc);
383 : :
384 [ + + + + ]: 44201 : if (functypclass == TYPEFUNC_COMPOSITE ||
385 : : functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
386 : : {
387 : : /* Composite data type, e.g. a table's row type */
388 : : Assert(tupdesc);
389 : : /* Must copy it out of typcache for safety */
390 : 22581 : tupdesc = CreateTupleDescCopy(tupdesc);
391 : : }
392 [ + - ]: 21620 : else if (functypclass == TYPEFUNC_SCALAR)
393 : : {
394 : : /* Base data type, i.e. scalar */
395 : 21620 : tupdesc = CreateTemplateTupleDesc(1);
396 : 21620 : TupleDescInitEntry(tupdesc,
397 : : (AttrNumber) 1,
398 : : NULL, /* don't care about the name here */
399 : : funcrettype,
400 : : -1,
401 : : 0);
402 : 21620 : TupleDescInitEntryCollation(tupdesc,
403 : : (AttrNumber) 1,
404 : : exprCollation(funcexpr));
405 : 21620 : TupleDescFinalize(tupdesc);
406 : : }
407 : : else
408 : : {
409 : : /* crummy error message, but parser should have caught this */
410 [ # # ]: 0 : elog(ERROR, "function in FROM has unsupported return type");
411 : : }
412 : : }
413 : :
414 : 44691 : fs->tupdesc = tupdesc;
415 : 44691 : fs->colcount = colcount;
416 : :
417 : : /*
418 : : * We only need separate slots for the function results if we are
419 : : * doing ordinality or multiple functions; otherwise, we'll fetch
420 : : * function results directly into the scan slot.
421 : : */
422 [ + + ]: 44691 : if (!scanstate->simple)
423 : : {
424 : 1003 : fs->func_slot = ExecInitExtraTupleSlot(estate, fs->tupdesc,
425 : : &TTSOpsMinimalTuple);
426 : : }
427 : : else
428 : 43688 : fs->func_slot = NULL;
429 : :
430 : 44691 : natts += colcount;
431 : 44691 : i++;
432 : : }
433 : :
434 : : /*
435 : : * Create the combined TupleDesc
436 : : *
437 : : * If there is just one function without ordinality, the scan result
438 : : * tupdesc is the same as the function result tupdesc --- except that we
439 : : * may stuff new names into it below, so drop any rowtype label.
440 : : */
441 [ + + ]: 44472 : if (scanstate->simple)
442 : : {
443 : 43688 : scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc);
444 : 43688 : scan_tupdesc->tdtypeid = RECORDOID;
445 : 43688 : scan_tupdesc->tdtypmod = -1;
446 : : }
447 : : else
448 : : {
449 : 784 : AttrNumber attno = 0;
450 : :
451 [ + + ]: 784 : if (node->funcordinality)
452 : 725 : natts++;
453 : :
454 : 784 : scan_tupdesc = CreateTemplateTupleDesc(natts);
455 : :
456 [ + + ]: 1787 : for (i = 0; i < nfuncs; i++)
457 : : {
458 : 1003 : TupleDesc tupdesc = scanstate->funcstates[i].tupdesc;
459 : 1003 : int colcount = scanstate->funcstates[i].colcount;
460 : : int j;
461 : :
462 [ + + ]: 2432 : for (j = 1; j <= colcount; j++)
463 : 1429 : TupleDescCopyEntry(scan_tupdesc, ++attno, tupdesc, j);
464 : : }
465 : :
466 : : /* If doing ordinality, add a column of type "bigint" at the end */
467 [ + + ]: 784 : if (node->funcordinality)
468 : : {
469 : 725 : TupleDescInitEntry(scan_tupdesc,
470 : : ++attno,
471 : : NULL, /* don't care about the name here */
472 : : INT8OID,
473 : : -1,
474 : : 0);
475 : : }
476 : :
477 : 784 : TupleDescFinalize(scan_tupdesc);
478 : : Assert(attno == natts);
479 : : }
480 : :
481 : : /*
482 : : * Initialize scan slot and type.
483 : : */
484 : 44472 : ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc,
485 : : &TTSOpsMinimalTuple, 0);
486 : :
487 : : /*
488 : : * Initialize result slot, type and projection.
489 : : */
490 : 44472 : ExecInitResultTypeTL(&scanstate->ss.ps);
491 : 44472 : ExecAssignScanProjectionInfo(&scanstate->ss);
492 : :
493 : : /*
494 : : * initialize child expressions
495 : : */
496 : 44472 : scanstate->ss.ps.qual =
497 : 44472 : ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
498 : :
499 : : /*
500 : : * Create a memory context that ExecMakeTableFunctionResult can use to
501 : : * evaluate function arguments in. We can't use the per-tuple context for
502 : : * this because it gets reset too often; but we don't want to leak
503 : : * evaluation results into the query-lifespan context either. We just
504 : : * need one context, because we evaluate each function separately.
505 : : */
506 : 44472 : scanstate->argcontext = AllocSetContextCreate(CurrentMemoryContext,
507 : : "Table function arguments",
508 : : ALLOCSET_DEFAULT_SIZES);
509 : :
510 : 44472 : scanstate->shared_info = NULL;
511 : :
512 : 44472 : return scanstate;
513 : : }
514 : :
515 : : /* ----------------------------------------------------------------
516 : : * ExecEndFunctionScan
517 : : *
518 : : * frees any storage allocated through C routines.
519 : : * ----------------------------------------------------------------
520 : : */
521 : : void
522 : 41494 : ExecEndFunctionScan(FunctionScanState *node)
523 : : {
524 : : int i;
525 : :
526 : : /*
527 : : * When ending a parallel worker, copy the tuplestore usage details into
528 : : * shared memory so that they can be picked up by the main process to
529 : : * report in EXPLAIN ANALYZE. The Gather node above us may be rescanned,
530 : : * in which case a new set of workers will write to the same slots, so
531 : : * merge with any existing values rather than overwriting them.
532 : : */
533 [ - + - - ]: 41494 : if (node->shared_info != NULL && IsParallelWorker())
534 : : {
535 : : FunctionScanInstrumentation *si;
536 : :
537 : : /* fetch storage info from tuplestores and store in node->stats */
538 : 0 : update_storage_stats(node);
539 : :
540 : : Assert(ParallelWorkerNumber < node->shared_info->num_workers);
541 : 0 : si = &node->shared_info->sinstrument[ParallelWorkerNumber];
542 : 0 : si->maxSpaceUsed = Max(si->maxSpaceUsed, node->stats.maxSpaceUsed);
543 : 0 : si->usedDisk |= node->stats.usedDisk;
544 : : }
545 : :
546 : : /*
547 : : * Release slots and tuplestore resources
548 : : */
549 [ + + ]: 83199 : for (i = 0; i < node->nfuncs; i++)
550 : : {
551 : 41705 : FunctionScanPerFuncState *fs = &node->funcstates[i];
552 : :
553 [ + + ]: 41705 : if (fs->tstore != NULL)
554 : : {
555 : 37245 : tuplestore_end(node->funcstates[i].tstore);
556 : 37245 : fs->tstore = NULL;
557 : : }
558 : : }
559 : 41494 : }
560 : :
561 : : /*
562 : : * update_storage_stats
563 : : * Update the storage statistics for EXPLAIN ANALYZE with the storage
564 : : * space used by each function's tuplestore.
565 : : *
566 : : * We expect this to be called before starting new scans so that the
567 : : * maxSpaceUsed and usedDisk fields are not forgotten between rescans. We
568 : : * calculate the total space used by all tuplestores at the end of each scan
569 : : * and store that if it's higher than any previous scan's. "usedDisk" is
570 : : * marked if any tuplestore spilled during any scan.
571 : : */
572 : : static void
573 : 0 : update_storage_stats(FunctionScanState *node)
574 : : {
575 : 0 : int64 totalSpaceUsed = 0;
576 : :
577 : : /* Calculate the sum of the space used by each function's tuplestore. */
578 [ # # ]: 0 : for (int i = 0; i < node->nfuncs; i++)
579 : : {
580 : 0 : FunctionScanPerFuncState *fs = &node->funcstates[i];
581 : : bool usedDisk;
582 : : int64 spaceUsed;
583 : :
584 [ # # ]: 0 : if (fs->tstore == NULL)
585 : 0 : continue;
586 : :
587 : 0 : tuplestore_get_stats(fs->tstore, &usedDisk, &spaceUsed);
588 : 0 : totalSpaceUsed += spaceUsed;
589 : 0 : node->stats.usedDisk |= usedDisk;
590 : : }
591 : :
592 : : /* Record this one if it beats the previous best. */
593 : 0 : node->stats.maxSpaceUsed = Max(node->stats.maxSpaceUsed, totalSpaceUsed);
594 : 0 : }
595 : :
596 : : /* ----------------------------------------------------------------
597 : : * ExecReScanFunctionScan
598 : : *
599 : : * Rescans the relation.
600 : : * ----------------------------------------------------------------
601 : : */
602 : : void
603 : 74459 : ExecReScanFunctionScan(FunctionScanState *node)
604 : : {
605 : 74459 : FunctionScan *scan = (FunctionScan *) node->ss.ps.plan;
606 : : int i;
607 : 74459 : Bitmapset *chgparam = node->ss.ps.chgParam;
608 : :
609 [ + + ]: 74459 : if (node->ss.ps.ps_ResultTupleSlot)
610 : 32110 : ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
611 [ + + ]: 148991 : for (i = 0; i < node->nfuncs; i++)
612 : : {
613 : 74532 : FunctionScanPerFuncState *fs = &node->funcstates[i];
614 : :
615 [ + + ]: 74532 : if (fs->func_slot)
616 : 734 : ExecClearTuple(fs->func_slot);
617 : : }
618 : :
619 : 74459 : ExecScanReScan(&node->ss);
620 : :
621 : : /*
622 : : * Here we have a choice whether to drop the tuplestores (and recompute
623 : : * the function outputs) or just rescan them. We must recompute if an
624 : : * expression contains changed parameters, else we rescan.
625 : : *
626 : : * XXX maybe we should recompute if the function is volatile? But in
627 : : * general the executor doesn't conditionalize its actions on that.
628 : : */
629 [ + + ]: 74459 : if (chgparam)
630 : : {
631 : : ListCell *lc;
632 : :
633 : : /* Track tuplestore storage stats, if needed */
634 [ - + ]: 72184 : if (node->ss.ps.instrument)
635 : 0 : update_storage_stats(node);
636 : :
637 : 72184 : i = 0;
638 [ + - + + : 144425 : foreach(lc, scan->functions)
+ + ]
639 : : {
640 : 72241 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
641 : :
642 [ + + ]: 72241 : if (bms_overlap(chgparam, rtfunc->funcparams))
643 : : {
644 [ + + ]: 69207 : if (node->funcstates[i].tstore != NULL)
645 : : {
646 : 63267 : tuplestore_end(node->funcstates[i].tstore);
647 : 63267 : node->funcstates[i].tstore = NULL;
648 : : }
649 : 69207 : node->funcstates[i].rowcount = -1;
650 : : }
651 : 72241 : i++;
652 : : }
653 : : }
654 : :
655 : : /* Reset ordinality counter */
656 : 74459 : node->ordinal = 0;
657 : :
658 : : /* Make sure we rewind any remaining tuplestores */
659 [ + + ]: 148991 : for (i = 0; i < node->nfuncs; i++)
660 : : {
661 [ + + ]: 74532 : if (node->funcstates[i].tstore != NULL)
662 : 4803 : tuplestore_rescan(node->funcstates[i].tstore);
663 : : }
664 : 74459 : }
665 : :
666 : : /* ----------------------------------------------------------------
667 : : * Parallel Query Support
668 : : * ----------------------------------------------------------------
669 : : */
670 : :
671 : : /* ----------------------------------------------------------------
672 : : * ExecFunctionScanEstimate
673 : : *
674 : : * Estimate space required to propagate function scan statistics.
675 : : * ----------------------------------------------------------------
676 : : */
677 : : void
678 : 0 : ExecFunctionScanEstimate(FunctionScanState *node, ParallelContext *pcxt)
679 : : {
680 : : Size size;
681 : :
682 : : /* don't need this if not instrumenting or no workers */
683 [ # # # # ]: 0 : if (!node->ss.ps.instrument || pcxt->nworkers == 0)
684 : 0 : return;
685 : :
686 : 0 : size = mul_size(pcxt->nworkers, sizeof(FunctionScanInstrumentation));
687 : 0 : size = add_size(size, offsetof(SharedFunctionScanInfo, sinstrument));
688 : 0 : shm_toc_estimate_chunk(&pcxt->estimator, size);
689 : 0 : shm_toc_estimate_keys(&pcxt->estimator, 1);
690 : : }
691 : :
692 : : /* ----------------------------------------------------------------
693 : : * ExecFunctionScanInitializeDSM
694 : : *
695 : : * Initialize DSM space for function scan statistics.
696 : : * ----------------------------------------------------------------
697 : : */
698 : : void
699 : 0 : ExecFunctionScanInitializeDSM(FunctionScanState *node, ParallelContext *pcxt)
700 : : {
701 : : Size size;
702 : :
703 : : /* don't need this if not instrumenting or no workers */
704 [ # # # # ]: 0 : if (!node->ss.ps.instrument || pcxt->nworkers == 0)
705 : 0 : return;
706 : :
707 : 0 : size = offsetof(SharedFunctionScanInfo, sinstrument)
708 : 0 : + pcxt->nworkers * sizeof(FunctionScanInstrumentation);
709 : 0 : node->shared_info = shm_toc_allocate(pcxt->toc, size);
710 : : /* ensure any unfilled slots will contain zeroes */
711 : 0 : memset(node->shared_info, 0, size);
712 : 0 : node->shared_info->num_workers = pcxt->nworkers;
713 : 0 : shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id,
714 : 0 : node->shared_info);
715 : : }
716 : :
717 : : /* ----------------------------------------------------------------
718 : : * ExecFunctionScanInitializeWorker
719 : : *
720 : : * Attach worker to DSM space for function scan statistics.
721 : : * ----------------------------------------------------------------
722 : : */
723 : : void
724 : 0 : ExecFunctionScanInitializeWorker(FunctionScanState *node,
725 : : ParallelWorkerContext *pwcxt)
726 : : {
727 : 0 : node->shared_info =
728 : 0 : shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, true);
729 : 0 : }
730 : :
731 : : /* ----------------------------------------------------------------
732 : : * ExecFunctionScanRetrieveInstrumentation
733 : : *
734 : : * Transfer function scan statistics from DSM to private memory.
735 : : * ----------------------------------------------------------------
736 : : */
737 : : void
738 : 0 : ExecFunctionScanRetrieveInstrumentation(FunctionScanState *node)
739 : : {
740 : : Size size;
741 : : SharedFunctionScanInfo *si;
742 : :
743 [ # # ]: 0 : if (node->shared_info == NULL)
744 : 0 : return;
745 : :
746 : 0 : size = offsetof(SharedFunctionScanInfo, sinstrument)
747 : 0 : + node->shared_info->num_workers * sizeof(FunctionScanInstrumentation);
748 : 0 : si = palloc(size);
749 : 0 : memcpy(si, node->shared_info, size);
750 : 0 : node->shared_info = si;
751 : : }
|