diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 1b7a3956e71..32b19d873c8 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -3580,9 +3580,35 @@ show_ctescan_info(CteScanState *ctescanstate, ExplainState *es) static void show_functionscan_info(FunctionScanState *fsstate, ExplainState *es) { + bool totalDiskUsed = false; + int64 totalSpaceUsed = 0; + if (!es->analyze) return; + /* + * Similar to the logic in ExecReScanFunctionScan(), we need to fetch the + * stats from the final rescan (or the only one) and use these values if + * they're higher than what's already stored in FunctionScanState.stats. + */ + for (int i = 0; i < fsstate->nfuncs; i++) + { + FunctionScanPerFuncState* fs = &fsstate->funcstates[i]; + + if (fs->tstore != NULL) + { + bool diskUsed; + int64 spaceUsed; + + tuplestore_get_stats(fs->tstore, &diskUsed, &spaceUsed); + totalSpaceUsed += spaceUsed; + totalDiskUsed |= diskUsed; + } + } + + fsstate->stats.maxSpaceUsed = Max(fsstate->stats.maxSpaceUsed, totalSpaceUsed); + fsstate->stats.usedDisk |= totalDiskUsed; + /* * Nothing to show for the leader if execution didn't get as far as * creating any tuplestore. diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 8b37a812fd0..d6d1777d5ac 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -29,27 +29,6 @@ #include "utils/memutils.h" #include "utils/tuplestore.h" - -/* - * Runtime data for each function being scanned. - */ -typedef struct FunctionScanPerFuncState -{ - SetExprState *setexpr; /* state of the expression being evaluated */ - TupleDesc tupdesc; /* desc of the function result type */ - int colcount; /* expected number of result columns */ - Tuplestorestate *tstore; /* holds the function result set */ - int64 rowcount; /* # of rows in result set, -1 if not known */ - TupleTableSlot *func_slot; /* function result slot (or NULL) */ - - /* - * Storage statistics of the current tuplestore, to report in EXPLAIN - * ANALYZE. Only meaningful while tstore is not NULL. - */ - bool usedDisk; - int64 spaceUsed; -} FunctionScanPerFuncState; - static TupleTableSlot *FunctionNext(FunctionScanState *node); static void update_storage_stats(FunctionScanState *node, FunctionScanPerFuncState *fs); @@ -113,10 +92,6 @@ FunctionNext(FunctionScanState *node) * call is fast, so the overhead shouldn't be an issue. */ tuplestore_rescan(tstore); - - /* Collect its storage statistics for EXPLAIN ANALYZE */ - if (node->ss.ps.instrument) - update_storage_stats(node, &node->funcstates[0]); } /* @@ -176,10 +151,6 @@ FunctionNext(FunctionScanState *node) * call is fast, so the overhead shouldn't be an issue. */ tuplestore_rescan(fs->tstore); - - /* Collect its storage statistics for EXPLAIN ANALYZE */ - if (node->ss.ps.instrument) - update_storage_stats(node, fs); } /* @@ -375,8 +346,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) */ fs->tstore = NULL; fs->rowcount = -1; - fs->usedDisk = false; - fs->spaceUsed = 0; /* * Now build a tupdesc showing the result type we expect from the @@ -565,11 +534,31 @@ ExecEndFunctionScan(FunctionScanState *node) if (node->shared_info != NULL && IsParallelWorker()) { FunctionScanInstrumentation *si; + bool totalDiskUsed = false; + int64 totalSpaceUsed = 0; + + for (int i = 0; i < node->nfuncs; i++) + { + FunctionScanPerFuncState* fs = &node->funcstates[i]; + + if (fs->tstore != NULL) + { + bool diskUsed; + int64 spaceUsed; + + tuplestore_get_stats(fs->tstore, &diskUsed, &spaceUsed); + totalSpaceUsed += spaceUsed; + totalDiskUsed |= diskUsed; + } + } + + totalSpaceUsed = Max(node->stats.maxSpaceUsed, totalSpaceUsed); + totalDiskUsed |= node->stats.usedDisk; Assert(ParallelWorkerNumber < node->shared_info->num_workers); si = &node->shared_info->sinstrument[ParallelWorkerNumber]; - si->maxSpaceUsed = Max(si->maxSpaceUsed, node->stats.maxSpaceUsed); - si->usedDisk |= node->stats.usedDisk; + si->maxSpaceUsed = Max(si->maxSpaceUsed, totalSpaceUsed); + si->usedDisk |= totalDiskUsed; } /* @@ -587,35 +576,6 @@ ExecEndFunctionScan(FunctionScanState *node) } } -/* - * update_storage_stats - * Update the storage statistics to report in EXPLAIN ANALYZE with the - * function's tuplestore, which already holds the whole result set. - * - * A FunctionScan node has one tuplestore per function. We report the peak - * of the total space used by the tuplestores existing at the same time. The - * storage type is reported as disk if any tuplestore, of any function and in - * any rescan, spilled to disk. - */ -static void -update_storage_stats(FunctionScanState *node, FunctionScanPerFuncState *fs) -{ - int64 totalSpaceUsed = 0; - - tuplestore_get_stats(fs->tstore, &fs->usedDisk, &fs->spaceUsed); - - for (int i = 0; i < node->nfuncs; i++) - { - FunctionScanPerFuncState *f = &node->funcstates[i]; - - if (f->tstore != NULL) - totalSpaceUsed += f->spaceUsed; - } - - node->stats.maxSpaceUsed = Max(node->stats.maxSpaceUsed, totalSpaceUsed); - node->stats.usedDisk |= fs->usedDisk; -} - /* ---------------------------------------------------------------- * ExecReScanFunctionScan * @@ -652,18 +612,35 @@ ExecReScanFunctionScan(FunctionScanState *node) if (chgparam) { ListCell *lc; + bool totalDiskUsed = false; + int64 totalSpaceUsed = 0; i = 0; foreach(lc, scan->functions) { RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + FunctionScanPerFuncState *fs = &node->funcstates[i]; if (bms_overlap(chgparam, rtfunc->funcparams)) { - if (node->funcstates[i].tstore != NULL) + if (fs->tstore != NULL) { + /* Collect its storage statistics for EXPLAIN ANALYZE */ + if (node->ss.ps.instrument) + { + bool diskUsed; + int64 spaceUsed; + + tuplestore_get_stats(fs->tstore, &diskUsed, &spaceUsed); + totalDiskUsed |= diskUsed; + totalSpaceUsed += spaceUsed; + } + tuplestore_end(node->funcstates[i].tstore); node->funcstates[i].tstore = NULL; + + node->stats.maxSpaceUsed = Max(node->stats.maxSpaceUsed, totalSpaceUsed); + node->stats.usedDisk |= totalDiskUsed; } node->funcstates[i].rowcount = -1; } diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 4017d8b9c0f..4823a36349c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1910,6 +1910,20 @@ typedef struct SubqueryScanState PlanState *subplan; } SubqueryScanState; +/* ---------------- +~* Runtime data for each function being scanned by a FunctionScan node. + * ---------------- + */ +typedef struct FunctionScanPerFuncState +{ + SetExprState *setexpr; /* state of the expression being evaluated */ + TupleDesc tupdesc; /* desc of the function result type */ + int colcount; /* expected number of result columns */ + Tuplestorestate *tstore; /* holds the function result set */ + int64 rowcount; /* # of rows in result set, -1 if not known */ + TupleTableSlot *func_slot; /* function result slot (or NULL) */ +} FunctionScanPerFuncState; + /* ---------------- * FunctionScanState information * @@ -1921,8 +1935,7 @@ typedef struct SubqueryScanState * simple true if we have 1 function and no ordinality * ordinal current ordinal column value * nfuncs number of functions being executed - * funcstates per-function execution states (private in - * nodeFunctionscan.c) + * funcstates per-function execution states * argcontext memory context to evaluate function arguments in * stats storage statistics to report in EXPLAIN ANALYZE * shared_info statistics for parallel workers diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out index 1603c806876..3d6b8d12bde 100644 --- a/src/test/regress/expected/explain.out +++ b/src/test/regress/expected/explain.out @@ -858,7 +858,7 @@ select explain_filter('explain (analyze,buffers off,costs off) select count(*) f -- Test tuplestore storage usage in Function Scan with ROWS FROM, which uses -- one tuplestore per function -select explain_filter('explain (analyze,buffers off,costs off) select count(*) from rows from (generate_series(1,2500), generate_series(1,2500)) a(n,m)'); +select explain_filter('explain (analyze,buffers off,costs off) select count(*) from rows from (generate_series(1,1250), generate_series(1,1250)) a(n,m)'); explain_filter ------------------------------------------------------------------ Aggregate (actual time=N.N..N.N rows=N.N loops=N) diff --git a/src/test/regress/expected/planner_est.out b/src/test/regress/expected/planner_est.out index fbe2d5ff1a1..6a8d223831d 100644 --- a/src/test/regress/expected/planner_est.out +++ b/src/test/regress/expected/planner_est.out @@ -29,8 +29,7 @@ BEGIN EXECUTE format('explain (analyze %s, costs on, summary off, timing off, buffers off) %s', analyze_str, query) LOOP - -- Ignore Storage output because the size varies depending on - -- the platform + -- ignore Storage output because the size varies depending on platform CONTINUE WHEN ln ~ '^\s*Storage: '; IF hide_costs = true THEN diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql index cb18f495343..aa58a548b6e 100644 --- a/src/test/regress/sql/explain.sql +++ b/src/test/regress/sql/explain.sql @@ -197,5 +197,5 @@ select explain_filter('explain (analyze,buffers off,costs off) select count(*) f select explain_filter('explain (analyze,buffers off,costs off) select count(*) from generate_series(1,2500) a(n)'); -- Test tuplestore storage usage in Function Scan with ROWS FROM, which uses -- one tuplestore per function -select explain_filter('explain (analyze,buffers off,costs off) select count(*) from rows from (generate_series(1,2500), generate_series(1,2500)) a(n,m)'); +select explain_filter('explain (analyze,buffers off,costs off) select count(*) from rows from (generate_series(1,1250), generate_series(1,1250)) a(n,m)'); reset work_mem; diff --git a/src/test/regress/sql/planner_est.sql b/src/test/regress/sql/planner_est.sql index 72aae8807ec..1da208eccce 100644 --- a/src/test/regress/sql/planner_est.sql +++ b/src/test/regress/sql/planner_est.sql @@ -30,8 +30,7 @@ BEGIN EXECUTE format('explain (analyze %s, costs on, summary off, timing off, buffers off) %s', analyze_str, query) LOOP - -- Ignore Storage output because the size varies depending on - -- the platform + -- ignore Storage output because the size varies depending on platform CONTINUE WHEN ln ~ '^\s*Storage: '; IF hide_costs = true THEN