From 0b2742238c45dbc24b3a297bc33923aedbeb605d Mon Sep 17 00:00:00 2001 From: TatsuyaKawata Date: Sun, 4 Oct 2026 19:55:09 +0900 Subject: [PATCH v2 1/2] Keep Function Scan tuplestore statistics across rescans When a Function Scan is rescanned with changed parameters, ExecReScanFunctionScan() calls tuplestore_end() on the tuplestore of each affected function and lets the next FunctionNext() call build a fresh one. tuplestore_end() frees the Tuplestorestate together with the maximum storage statistics recorded in it, so only the statistics of the final tuplestore survive until EXPLAIN ANALYZE inspects the node. Unlike WindowAgg (908a96861), we cannot simply switch to tuplestore_clear() and reuse the tuplestore here: the tuplestore is not created by the node but by ExecMakeTableFunctionResult(), or by the function itself in materialize mode, and a new one is returned on every call. Instead, record the storage type and size of the largest tuplestore discarded so far in FunctionScanPerFuncState, just before it is ended. --- src/backend/executor/nodeFunctionscan.c | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 1416f1f09ae..bbdef76ca63 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -41,9 +41,17 @@ typedef struct FunctionScanPerFuncState 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 largest tuplestore discarded by a rescan so + * far. + */ + char *savedStorageType; + int64 savedSpaceUsed; } FunctionScanPerFuncState; static TupleTableSlot *FunctionNext(FunctionScanState *node); +static void save_tuplestore_stats(FunctionScanPerFuncState *fs); /* ---------------------------------------------------------------- @@ -358,6 +366,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) */ fs->tstore = NULL; fs->rowcount = -1; + fs->savedStorageType = NULL; + fs->savedSpaceUsed = -1; /* * Now build a tupdesc showing the result type we expect from the @@ -549,6 +559,27 @@ ExecEndFunctionScan(FunctionScanState *node) } } +/* + * save_tuplestore_stats + * Remember the storage statistics of the function's tuplestore, which is + * about to be discarded, so that EXPLAIN ANALYZE can report the maximum + * across all rescans. + */ +static void +save_tuplestore_stats(FunctionScanPerFuncState *fs) +{ + char *storageType; + int64 spaceUsed; + + tuplestore_get_stats(fs->tstore, &storageType, &spaceUsed); + + if (spaceUsed > fs->savedSpaceUsed) + { + fs->savedSpaceUsed = spaceUsed; + fs->savedStorageType = storageType; + } +} + /* ---------------------------------------------------------------- * ExecReScanFunctionScan * @@ -595,6 +626,13 @@ ExecReScanFunctionScan(FunctionScanState *node) { if (node->funcstates[i].tstore != NULL) { + /* + * We can't just tuplestore_clear() and reuse the + * tuplestore, since ExecMakeTableFunctionResult() always + * returns a new one. Remember its statistics before + * they're lost. + */ + save_tuplestore_stats(&node->funcstates[i]); tuplestore_end(node->funcstates[i].tstore); node->funcstates[i].tstore = NULL; } -- 2.34.1