From cd4e379b8f9714d3e9663b45947f10080261f27a Mon Sep 17 00:00:00 2001 From: David Rowley Date: Wed, 30 Sep 2026 17:29:16 +1300 Subject: [PATCH v1] Use tuplestore_clear instead of tuplestore_end in nodeMaterial.c Clearing the tuplestore rather than ending it avoids the need for various memory allocations, so is slightly more efficient. However, the main reason to do this is regarding tracking of the maximum storage used by the Material node. 40708acd6 added additional EXPLAIN output for Material nodes and that output does claim to be showing "Maximum Storage", which is not true, as the maximums could be lost after tuplestore_end() is called during a rescan. This causes misreporting when the final rescan of a Material node uses less storage than some previous rescan. One example is: EXPLAIN ANALYZE SELECT count(*) FROM (VALUES(10000),(1)) v1(r1) LEFT JOIN LATERAL ( SELECT * FROM generate_series(1, 2) gs0 LEFT JOIN LATERAL ( SELECT * FROM generate_series(1, v1.r1) gs1 FULL JOIN generate_series(1, v1.r1) gs4 ON false ) ss on true ) q on true; Without this fix, the repored Material storage is for the v1.r1 = 1 case, whereas it should consider how much was used with the v1.r1 = 10000 case and show the maximum of each. --- src/backend/executor/nodeMaterial.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c index e5f387612bc..e571f8e54ad 100644 --- a/src/backend/executor/nodeMaterial.c +++ b/src/backend/executor/nodeMaterial.c @@ -328,9 +328,9 @@ ExecReScanMaterial(MaterialState *node) return; /* - * If subnode is to be rescanned then we forget previous stored - * results; we have to re-read the subplan and re-store. Also, if we - * told tuplestore it needn't support rescan, we lose and must + * If subnode is to be rescanned then we clear out the previously + * stored results; we have to re-read the subplan and re-store. Also, + * if we told tuplestore it needn't support rescan, we lose and must * re-read. (This last should not happen in common cases; else our * caller lied by not passing EXEC_FLAG_REWIND to us.) * @@ -340,8 +340,11 @@ ExecReScanMaterial(MaterialState *node) if (outerPlan->chgParam != NULL || (node->eflags & EXEC_FLAG_REWIND) == 0) { - tuplestore_end(node->tuplestorestate); - node->tuplestorestate = NULL; + /* + * It's important to use tuplestore_clear() here so that we keep + * track of the maximum storage used by each rescan. + */ + tuplestore_clear(node->tuplestorestate); if (outerPlan->chgParam == NULL) ExecReScan(outerPlan); node->eof_underlying = false; -- 2.53.0