v4-0003-show-prefetch-stats-for-SeqScan.patch
text/x-patch
Filename: v4-0003-show-prefetch-stats-for-SeqScan.patch
Type: text/x-patch
Part: 2
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v4-0003
Subject: show prefetch stats for SeqScan
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/postgres_fdw.out | 4 | 4 |
| contrib/postgres_fdw/sql/postgres_fdw.sql | 4 | 4 |
| src/backend/commands/explain.c | 27 | 0 |
| src/backend/executor/execParallel.c | 3 | 0 |
| src/backend/executor/nodeSeqscan.c | 107 | 3 |
| src/include/executor/instrument_node.h | 15 | 1 |
| src/include/executor/nodeSeqscan.h | 1 | 0 |
| src/include/nodes/execnodes.h | 1 | 0 |
| src/test/isolation/expected/merge-update.out | 1 | 1 |
| src/test/isolation/specs/merge-update.spec | 4 | 1 |
| src/test/regress/expected/explain.out | 6 | 6 |
| src/test/regress/expected/partition_prune.out | 17 | 17 |
| src/test/regress/sql/explain.sql | 6 | 6 |
| src/test/regress/sql/partition_prune.sql | 17 | 17 |
From 27b92895be48cbe8bd90d78efb7586d784aa52ed Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Thu, 19 Mar 2026 14:29:52 +0100
Subject: [PATCH v4 3/4] show prefetch stats for SeqScan
- enable show_scan_io_usage for SeqScan
- add infrastructure to allocate/collect instrumentation from parallel workers
---
.../postgres_fdw/expected/postgres_fdw.out | 8 +-
contrib/postgres_fdw/sql/postgres_fdw.sql | 8 +-
src/backend/commands/explain.c | 27 +++++
src/backend/executor/execParallel.c | 3 +
src/backend/executor/nodeSeqscan.c | 110 +++++++++++++++++-
src/include/executor/instrument_node.h | 16 ++-
src/include/executor/nodeSeqscan.h | 1 +
src/include/nodes/execnodes.h | 1 +
src/test/isolation/expected/merge-update.out | 2 +-
src/test/isolation/specs/merge-update.spec | 5 +-
src/test/regress/expected/explain.out | 12 +-
src/test/regress/expected/partition_prune.out | 34 +++---
src/test/regress/sql/explain.sql | 12 +-
src/test/regress/sql/partition_prune.sql | 34 +++---
14 files changed, 213 insertions(+), 60 deletions(-)
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0f5271d476e..3f178eda913 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -11937,7 +11937,7 @@ SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c
Filter: (async_pt_3.a = local_tbl.a)
(15 rows)
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar';
QUERY PLAN
----------------------------------------------------------------------------------
@@ -12183,7 +12183,7 @@ SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt W
Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < 3000))
(20 rows)
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a;
QUERY PLAN
--------------------------------------------------------------------------------------------
@@ -12227,7 +12227,7 @@ SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1;
Filter: (t1_3.b === 505)
(14 rows)
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1;
QUERY PLAN
----------------------------------------------------------------------------
@@ -12387,7 +12387,7 @@ DELETE FROM async_pt WHERE b = 0 RETURNING *;
DELETE FROM async_p1;
DELETE FROM async_p2;
DELETE FROM async_p3;
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM async_pt;
QUERY PLAN
----------------------------------------------------------------------------
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 49ed797e8ef..2aee5bb3fe9 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -4059,7 +4059,7 @@ ALTER FOREIGN TABLE async_p2 OPTIONS (use_remote_estimate 'true');
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar';
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar';
SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar';
@@ -4134,13 +4134,13 @@ ANALYZE local_tbl;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a;
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a;
SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1;
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1;
SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1;
@@ -4192,7 +4192,7 @@ DELETE FROM async_p1;
DELETE FROM async_p2;
DELETE FROM async_p3;
-EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF)
+EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF, IO OFF)
SELECT * FROM async_pt;
-- Clean up
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index d98962f50b3..07a418e50cc 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4082,6 +4082,23 @@ show_scan_io_usage(ScanState *planstate, ExplainState *es)
/* Initialize counters with stats from the local process first */
switch (nodeTag(plan))
{
+ case T_SeqScan:
+ {
+ SharedSeqScanInstrumentation *sinstrument
+ = ((SeqScanState *) planstate)->sinstrument;
+
+ /* get the sum of the counters set within each and every process */
+ if (sinstrument)
+ {
+ for (int i = 0; i < sinstrument->num_workers; ++i)
+ {
+ SeqScanInstrumentation *winstrument = &sinstrument->sinstrument[i];
+ ACCUMULATE_IO_STATS(&stats, &winstrument->io);
+ }
+ }
+
+ break;
+ }
case T_BitmapHeapScan:
{
SharedBitmapHeapInstrumentation *sinstrument
@@ -4125,6 +4142,16 @@ show_io_usage(PlanState *planstate, ExplainState *es, int worker)
/* get instrumentation for the given worker */
switch (nodeTag(plan))
{
+ case T_SeqScan:
+ {
+ SeqScanState *state = ((SeqScanState *) planstate);
+ SharedSeqScanInstrumentation *sinstrument = state->sinstrument;
+ SeqScanInstrumentation *instrument = &sinstrument->sinstrument[worker];
+
+ stats = &instrument->io;
+
+ break;
+ }
case T_BitmapHeapScan:
{
BitmapHeapScanState *state = ((BitmapHeapScanState *) planstate);
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ac84af294c9..ffc708ed6be 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -1119,6 +1119,9 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
case T_BitmapHeapScanState:
ExecBitmapHeapRetrieveInstrumentation((BitmapHeapScanState *) planstate);
break;
+ case T_SeqScanState:
+ ExecSeqScanRetrieveInstrumentation((SeqScanState *) planstate);
+ break;
default:
break;
}
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 8f219f60a93..2ecc0e3054d 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -295,6 +295,30 @@ ExecEndSeqScan(SeqScanState *node)
{
TableScanDesc scanDesc;
+ /*
+ * When ending a parallel worker, copy the statistics gathered by the
+ * worker back into shared memory so that it can be picked up by the main
+ * process to report in EXPLAIN ANALYZE.
+ */
+ if (node->sinstrument != NULL && IsParallelWorker())
+ {
+ SeqScanInstrumentation *si;
+
+ Assert(ParallelWorkerNumber < node->sinstrument->num_workers);
+ si = &node->sinstrument->sinstrument[ParallelWorkerNumber];
+
+ /*
+ * Here we accumulate the stats rather than performing memcpy on
+ * node->stats into si. When a Gather/GatherMerge node finishes it
+ * will perform planner shutdown on the workers. On rescan it will
+ * spin up new workers which will have a new SeqScanState and
+ * zeroed stats.
+ */
+
+ /* collect prefetch info for this process from the read_stream */
+ ACCUMULATE_IO_STATS(&si->io, &node->ss.ss_currentScanDesc->rs_iostats);
+ }
+
/*
* get information from node
*/
@@ -349,10 +373,23 @@ ExecSeqScanEstimate(SeqScanState *node,
ParallelContext *pcxt)
{
EState *estate = node->ss.ps.state;
+ Size size;
- node->pscan_len = table_parallelscan_estimate(node->ss.ss_currentRelation,
+ size = table_parallelscan_estimate(node->ss.ss_currentRelation,
estate->es_snapshot);
- shm_toc_estimate_chunk(&pcxt->estimator, node->pscan_len);
+ node->pscan_len = size;
+
+ /* make sure the instrumentation is properly aligned */
+ size = MAXALIGN(size);
+
+ /* account for instrumentation, if required */
+ if (node->ss.ps.instrument && pcxt->nworkers > 0)
+ {
+ size = add_size(size, offsetof(SharedSeqScanInstrumentation, sinstrument));
+ size = add_size(size, mul_size(pcxt->nworkers, sizeof(SeqScanInstrumentation)));
+ }
+
+ shm_toc_estimate_chunk(&pcxt->estimator, size);
shm_toc_estimate_keys(&pcxt->estimator, 1);
}
@@ -368,14 +405,42 @@ ExecSeqScanInitializeDSM(SeqScanState *node,
{
EState *estate = node->ss.ps.state;
ParallelTableScanDesc pscan;
+ SharedSeqScanInstrumentation *sinstrument = NULL;
+ Size size;
+ char *ptr;
+
+ /* Recalculate the size. This needs to match ExecSeqScanEstimate. */
+ size = MAXALIGN(node->pscan_len);
+ if (node->ss.ps.instrument && pcxt->nworkers > 0)
+ {
+ size = add_size(size, offsetof(SharedSeqScanInstrumentation, sinstrument));
+ size = add_size(size, mul_size(pcxt->nworkers, sizeof(SeqScanInstrumentation)));
+ }
- pscan = shm_toc_allocate(pcxt->toc, node->pscan_len);
+ pscan = shm_toc_allocate(pcxt->toc, size);
table_parallelscan_initialize(node->ss.ss_currentRelation,
pscan,
estate->es_snapshot);
shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
node->ss.ss_currentScanDesc =
table_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+
+ /* initialize the shared instrumentation (with correct alignment) */
+ ptr = (char *) pscan;
+ ptr += MAXALIGN(node->pscan_len);
+ if (node->ss.ps.instrument && pcxt->nworkers > 0)
+ sinstrument = (SharedSeqScanInstrumentation *) ptr;
+
+ if (sinstrument)
+ {
+ sinstrument->num_workers = pcxt->nworkers;
+
+ /* ensure any unfilled slots will contain zeroes */
+ memset(sinstrument->sinstrument, 0,
+ pcxt->nworkers * sizeof(SeqScanInstrumentation));
+ }
+
+ node->sinstrument = sinstrument;
}
/* ----------------------------------------------------------------
@@ -404,9 +469,48 @@ void
ExecSeqScanInitializeWorker(SeqScanState *node,
ParallelWorkerContext *pwcxt)
{
+ EState *estate = node->ss.ps.state;
ParallelTableScanDesc pscan;
+ char *ptr;
+ Size size;
pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
node->ss.ss_currentScanDesc =
table_beginscan_parallel(node->ss.ss_currentRelation, pscan);
+
+ /*
+ * Workers don't get the pscan_len value in scan descriptor, so use the
+ * TAM callback again. The result has to match the earlier result in
+ * ExecSeqScanEstimate.
+ */
+ size = table_parallelscan_estimate(node->ss.ss_currentRelation,
+ estate->es_snapshot);
+
+ ptr = (char *) pscan;
+ ptr += MAXALIGN(size);
+
+ if (node->ss.ps.instrument)
+ node->sinstrument = (SharedSeqScanInstrumentation *) ptr;
+}
+
+/* ----------------------------------------------------------------
+ * ExecSeqScanRetrieveInstrumentation
+ *
+ * Transfer seq scan statistics from DSM to private memory.
+ * ----------------------------------------------------------------
+ */
+void
+ExecSeqScanRetrieveInstrumentation(SeqScanState *node)
+{
+ SharedSeqScanInstrumentation *sinstrument = node->sinstrument;
+ Size size;
+
+ if (sinstrument == NULL)
+ return;
+
+ size = offsetof(SharedSeqScanInstrumentation, sinstrument)
+ + sinstrument->num_workers * sizeof(SeqScanInstrumentation);
+
+ node->sinstrument = palloc(size);
+ memcpy(node->sinstrument, sinstrument, size);
}
diff --git a/src/include/executor/instrument_node.h b/src/include/executor/instrument_node.h
index e6c714aa662..ce643ef0635 100644
--- a/src/include/executor/instrument_node.h
+++ b/src/include/executor/instrument_node.h
@@ -82,9 +82,23 @@ typedef struct IOStats
} while (0)
/* ---------------------
- * Instrumentation information for indexscans (amgettuple and amgetbitmap)
+ * Instrumentation information for sequential scans
* ---------------------
*/
+typedef struct SeqScanInstrumentation
+{
+ IOStats io;
+} SeqScanInstrumentation;
+
+/*
+ * Shared memory container for per-worker information
+ */
+typedef struct SharedSeqScanInstrumentation
+{
+ int num_workers;
+ SeqScanInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
+} SharedSeqScanInstrumentation;
+
typedef struct IndexScanInstrumentation
{
/* Index search count (incremented with pgstat_count_index_scan call) */
diff --git a/src/include/executor/nodeSeqscan.h b/src/include/executor/nodeSeqscan.h
index 7a1490596fb..e2122bfffe3 100644
--- a/src/include/executor/nodeSeqscan.h
+++ b/src/include/executor/nodeSeqscan.h
@@ -27,5 +27,6 @@ extern void ExecSeqScanInitializeDSM(SeqScanState *node, ParallelContext *pcxt);
extern void ExecSeqScanReInitializeDSM(SeqScanState *node, ParallelContext *pcxt);
extern void ExecSeqScanInitializeWorker(SeqScanState *node,
ParallelWorkerContext *pwcxt);
+extern void ExecSeqScanRetrieveInstrumentation(SeqScanState *node);
#endif /* NODESEQSCAN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0716c5a9aed..f287f8fc813 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1644,6 +1644,7 @@ typedef struct SeqScanState
{
ScanState ss; /* its first field is NodeTag */
Size pscan_len; /* size of parallel heap scan descriptor */
+ SharedSeqScanInstrumentation *sinstrument;
} SeqScanState;
/* ----------------
diff --git a/src/test/isolation/expected/merge-update.out b/src/test/isolation/expected/merge-update.out
index 821565b4303..dd9c67c4dc1 100644
--- a/src/test/isolation/expected/merge-update.out
+++ b/src/test/isolation/expected/merge-update.out
@@ -358,7 +358,7 @@ step pa_merge1:
step explain_pa_merge2a:
SELECT explain_filter($$
- EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+ EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF, IO OFF)
MERGE INTO pa_target t
USING (SELECT 1 as key, 'pa_merge2a' as val) s
ON s.key = t.key
diff --git a/src/test/isolation/specs/merge-update.spec b/src/test/isolation/specs/merge-update.spec
index b902779edd6..cf387eccefc 100644
--- a/src/test/isolation/specs/merge-update.spec
+++ b/src/test/isolation/specs/merge-update.spec
@@ -33,6 +33,9 @@ setup
FOR ln IN EXECUTE $1 LOOP
-- Ignore hash memory usage because it varies depending on the system
CONTINUE WHEN (ln ~ 'Memory Usage');
+ -- Ignore prefetch and I/O because it varies depending on the system
+ CONTINUE WHEN (ln ~ 'Prefetch');
+ CONTINUE WHEN (ln ~ 'I/O');
RETURN NEXT ln;
END LOOP;
END;
@@ -165,7 +168,7 @@ step "pa_merge2a"
step "explain_pa_merge2a"
{
SELECT explain_filter($$
- EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF)
+ EXPLAIN (ANALYZE, COSTS OFF, TIMING OFF, SUMMARY OFF, BUFFERS OFF, IO OFF)
MERGE INTO pa_target t
USING (SELECT 1 as key, 'pa_merge2a' as val) s
ON s.key = t.key
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index bb9fbe4a407..97cb7206676 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -85,7 +85,7 @@ select explain_filter('explain (analyze, buffers off, io off, verbose) select *
Execution Time: N.N ms
(4 rows)
-select explain_filter('explain (analyze, buffers, format text) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, io off, format text) select * from int8_tbl i8');
explain_filter
-------------------------------------------------------------------------------------------------
Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N.N loops=N)
@@ -290,7 +290,7 @@ select explain_filter('explain verbose select sum(unique1) over w1, sum(unique2)
-- Check output including I/O timings. These fields are conditional
-- but always set in JSON format, so check them only in this case.
set track_io_timing = on;
-select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, io off, format json) select * from int8_tbl i8');
explain_filter
-------------------------------------
[ +
@@ -425,7 +425,7 @@ select explain_filter('explain (memory, summary, format yaml) select * from int8
Planning Time: N.N
(1 row)
-select explain_filter('explain (memory, analyze, format json) select * from int8_tbl i8');
+select explain_filter('explain (memory, analyze, io off, format json) select * from int8_tbl i8');
explain_filter
------------------------------------
[ +
@@ -528,7 +528,7 @@ set parallel_tuple_cost=0;
set min_parallel_table_scan_size=0;
set max_parallel_workers_per_gather=4;
select jsonb_pretty(
- explain_filter_to_json('explain (analyze, verbose, buffers, format json)
+ explain_filter_to_json('explain (analyze, verbose, buffers, io off, format json)
select * from tenk1 order by tenthous')
-- remove "Workers" node of the Seq Scan plan node
#- '{0,Plan,Plans,0,Plans,0,Workers}'
@@ -753,7 +753,7 @@ select explain_filter('explain (analyze,buffers off,io off,serialize) select * f
Execution Time: N.N ms
(4 rows)
-select explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8');
+select explain_filter('explain (analyze,serialize text,buffers,io off,timing off) select * from int8_tbl i8');
explain_filter
-----------------------------------------------------------------------------------
Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual rows=N.N loops=N)
@@ -762,7 +762,7 @@ select explain_filter('explain (analyze,serialize text,buffers,timing off) selec
Execution Time: N.N ms
(4 rows)
-select explain_filter('explain (analyze,serialize binary,buffers,timing) select * from int8_tbl i8');
+select explain_filter('explain (analyze,serialize binary,buffers,io off,timing) select * from int8_tbl i8');
explain_filter
-------------------------------------------------------------------------------------------------
Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N.N loops=N)
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 1012be91689..6bd15f26aec 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2315,7 +2315,7 @@ begin;
-- Test run-time pruning using stable functions
create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable;
-- Ensure pruning works using a stable function containing no Vars
-explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(1);
+explain (analyze, costs off, summary off, timing off, buffers off, io off) select * from list_part where a = list_part_fn(1);
QUERY PLAN
---------------------------------------------------------------------
Append (actual rows=1.00 loops=1)
@@ -2325,7 +2325,7 @@ explain (analyze, costs off, summary off, timing off, buffers off) select * from
(4 rows)
-- Ensure pruning does not take place when the function has a Var parameter
-explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(a);
+explain (analyze, costs off, summary off, timing off, buffers off, io off) select * from list_part where a = list_part_fn(a);
QUERY PLAN
---------------------------------------------------------------------
Append (actual rows=4.00 loops=1)
@@ -2340,7 +2340,7 @@ explain (analyze, costs off, summary off, timing off, buffers off) select * from
(9 rows)
-- Ensure pruning does not take place when the expression contains a Var.
-explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(1) + a;
+explain (analyze, costs off, summary off, timing off, buffers off, io off) select * from list_part where a = list_part_fn(1) + a;
QUERY PLAN
---------------------------------------------------------------------
Append (actual rows=0.00 loops=1)
@@ -2376,7 +2376,7 @@ declare
ln text;
begin
for ln in
- execute format('explain (analyze, costs off, summary off, timing off, buffers off) %s',
+ execute format('explain (analyze, costs off, summary off, timing off, buffers off, io off) %s',
$1)
loop
ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N');
@@ -2687,7 +2687,7 @@ reset parallel_tuple_cost;
reset min_parallel_table_scan_size;
reset max_parallel_workers_per_gather;
-- Test run-time partition pruning with an initplan
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a);
QUERY PLAN
----------------------------------------------------------------------------
@@ -2864,7 +2864,7 @@ union all
select tableoid::regclass,a,b from ab
) ab where a = $1 and b = (select -10);
-- Ensure the xy_1 subplan is not pruned.
-explain (analyze, costs off, summary off, timing off, buffers off) execute ab_q6(1);
+explain (analyze, costs off, summary off, timing off, buffers off, io off) execute ab_q6(1);
QUERY PLAN
-------------------------------------------------------------
Append (actual rows=0.00 loops=1)
@@ -3019,7 +3019,7 @@ create index tprt6_idx on tprt_6 (col1);
insert into tprt values (10), (20), (501), (502), (505), (1001), (4500);
set enable_hashjoin = off;
set enable_mergejoin = off;
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
QUERY PLAN
-----------------------------------------------------------------------------
@@ -3046,7 +3046,7 @@ select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
Index Searches: 0
(21 rows)
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
QUERY PLAN
-----------------------------------------------------------------------------
@@ -3097,7 +3097,7 @@ order by tbl1.col1, tprt.col1;
-- Multiple partitions
insert into tbl1 values (1001), (1010), (1011);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
QUERY PLAN
-----------------------------------------------------------------------------
@@ -3124,7 +3124,7 @@ select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
Index Searches: 0
(21 rows)
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
QUERY PLAN
-----------------------------------------------------------------------------
@@ -3194,7 +3194,7 @@ order by tbl1.col1, tprt.col1;
-- Last partition
delete from tbl1;
insert into tbl1 values (4400);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
QUERY PLAN
-----------------------------------------------------------------------------
@@ -3232,7 +3232,7 @@ order by tbl1.col1, tprt.col1;
-- No matching partition
delete from tbl1;
insert into tbl1 values (10000);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
QUERY PLAN
-------------------------------------------------------------------
@@ -3478,7 +3478,7 @@ create table mc3p1 partition of mc3p
create table mc3p2 partition of mc3p
for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue);
insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from mc3p where a < 3 and abs(b) = 1;
QUERY PLAN
-----------------------------------------------------------
@@ -3498,7 +3498,7 @@ select * from mc3p where a < 3 and abs(b) = 1;
--
prepare ps1 as
select * from mc3p where a = $1 and abs(b) < (select 3);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
execute ps1(1);
QUERY PLAN
------------------------------------------------------------------
@@ -3513,7 +3513,7 @@ execute ps1(1);
deallocate ps1;
prepare ps2 as
select * from mc3p where a <= $1 and abs(b) < (select 3);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
execute ps2(1);
QUERY PLAN
-------------------------------------------------------------------
@@ -3535,7 +3535,7 @@ insert into boolvalues values('t'),('f');
create table boolp (a bool) partition by list (a);
create table boolp_t partition of boolp for values in('t');
create table boolp_f partition of boolp for values in('f');
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from boolp where a = (select value from boolvalues where value);
QUERY PLAN
--------------------------------------------------------------
@@ -3550,7 +3550,7 @@ select * from boolp where a = (select value from boolvalues where value);
Filter: (a = (InitPlan expr_1).col1)
(9 rows)
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from boolp where a = (select value from boolvalues where not value);
QUERY PLAN
--------------------------------------------------------------
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index 0124c37ef15..5ee453a0a96 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -65,7 +65,7 @@ explain (costs off) select 1 as a, 2 as b having false;
select explain_filter('explain select * from int8_tbl i8');
select explain_filter('explain (analyze, buffers off, io off) select * from int8_tbl i8');
select explain_filter('explain (analyze, buffers off, io off, verbose) select * from int8_tbl i8');
-select explain_filter('explain (analyze, buffers, format text) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, io off, format text) select * from int8_tbl i8');
select explain_filter('explain (analyze, buffers, io off, format xml) select * from int8_tbl i8');
select explain_filter('explain (analyze, serialize, buffers, io off, format yaml) select * from int8_tbl i8');
select explain_filter('explain (buffers, format text) select * from int8_tbl i8');
@@ -79,7 +79,7 @@ select explain_filter('explain verbose select sum(unique1) over w1, sum(unique2)
-- Check output including I/O timings. These fields are conditional
-- but always set in JSON format, so check them only in this case.
set track_io_timing = on;
-select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8');
+select explain_filter('explain (analyze, buffers, io off, format json) select * from int8_tbl i8');
set track_io_timing = off;
-- SETTINGS option
@@ -104,7 +104,7 @@ select explain_filter('explain (analyze, generic_plan) select unique1 from tenk1
select explain_filter('explain (memory) select * from int8_tbl i8');
select explain_filter('explain (memory, analyze, buffers off, io off) select * from int8_tbl i8');
select explain_filter('explain (memory, summary, format yaml) select * from int8_tbl i8');
-select explain_filter('explain (memory, analyze, format json) select * from int8_tbl i8');
+select explain_filter('explain (memory, analyze, io off, format json) select * from int8_tbl i8');
prepare int8_query as select * from int8_tbl i8;
select explain_filter('explain (memory) execute int8_query');
@@ -144,7 +144,7 @@ set min_parallel_table_scan_size=0;
set max_parallel_workers_per_gather=4;
select jsonb_pretty(
- explain_filter_to_json('explain (analyze, verbose, buffers, format json)
+ explain_filter_to_json('explain (analyze, verbose, buffers, io off, format json)
select * from tenk1 order by tenthous')
-- remove "Workers" node of the Seq Scan plan node
#- '{0,Plan,Plans,0,Plans,0,Workers}'
@@ -175,8 +175,8 @@ select explain_filter('explain (verbose) create table test_ctas as select 1');
-- Test SERIALIZE option
select explain_filter('explain (analyze,buffers off,io off,serialize) select * from int8_tbl i8');
-select explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8');
-select explain_filter('explain (analyze,serialize binary,buffers,timing) select * from int8_tbl i8');
+select explain_filter('explain (analyze,serialize text,buffers,io off,timing off) select * from int8_tbl i8');
+select explain_filter('explain (analyze,serialize binary,buffers,io off,timing) select * from int8_tbl i8');
-- this tests an edge case where we have no data to return
select explain_filter('explain (analyze,buffers off,io off,serialize) create temp table explain_temp as select * from int8_tbl i8');
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 212de0e6285..c5968932aed 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -553,13 +553,13 @@ begin;
create function list_part_fn(int) returns int as $$ begin return $1; end;$$ language plpgsql stable;
-- Ensure pruning works using a stable function containing no Vars
-explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(1);
+explain (analyze, costs off, summary off, timing off, buffers off, io off) select * from list_part where a = list_part_fn(1);
-- Ensure pruning does not take place when the function has a Var parameter
-explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(a);
+explain (analyze, costs off, summary off, timing off, buffers off, io off) select * from list_part where a = list_part_fn(a);
-- Ensure pruning does not take place when the expression contains a Var.
-explain (analyze, costs off, summary off, timing off, buffers off) select * from list_part where a = list_part_fn(1) + a;
+explain (analyze, costs off, summary off, timing off, buffers off, io off) select * from list_part where a = list_part_fn(1) + a;
rollback;
@@ -582,7 +582,7 @@ declare
ln text;
begin
for ln in
- execute format('explain (analyze, costs off, summary off, timing off, buffers off) %s',
+ execute format('explain (analyze, costs off, summary off, timing off, buffers off, io off) %s',
$1)
loop
ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N');
@@ -669,7 +669,7 @@ reset min_parallel_table_scan_size;
reset max_parallel_workers_per_gather;
-- Test run-time partition pruning with an initplan
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from ab where a = (select max(a) from lprt_a) and b = (select max(a)-1 from lprt_a);
-- Test run-time partition pruning with UNION ALL parents
@@ -697,7 +697,7 @@ union all
) ab where a = $1 and b = (select -10);
-- Ensure the xy_1 subplan is not pruned.
-explain (analyze, costs off, summary off, timing off, buffers off) execute ab_q6(1);
+explain (analyze, costs off, summary off, timing off, buffers off, io off) execute ab_q6(1);
-- Ensure we see just the xy_1 row.
execute ab_q6(100);
@@ -752,10 +752,10 @@ insert into tprt values (10), (20), (501), (502), (505), (1001), (4500);
set enable_hashjoin = off;
set enable_mergejoin = off;
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 > tprt.col1;
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
select tbl1.col1, tprt.col1 from tbl1
@@ -768,10 +768,10 @@ order by tbl1.col1, tprt.col1;
-- Multiple partitions
insert into tbl1 values (1001), (1010), (1011);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 inner join tprt on tbl1.col1 > tprt.col1;
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 inner join tprt on tbl1.col1 = tprt.col1;
select tbl1.col1, tprt.col1 from tbl1
@@ -785,7 +785,7 @@ order by tbl1.col1, tprt.col1;
-- Last partition
delete from tbl1;
insert into tbl1 values (4400);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 < tprt.col1;
select tbl1.col1, tprt.col1 from tbl1
@@ -795,7 +795,7 @@ order by tbl1.col1, tprt.col1;
-- No matching partition
delete from tbl1;
insert into tbl1 values (10000);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from tbl1 join tprt on tbl1.col1 = tprt.col1;
select tbl1.col1, tprt.col1 from tbl1
@@ -917,7 +917,7 @@ create table mc3p2 partition of mc3p
for values from (2, minvalue, minvalue) to (3, maxvalue, maxvalue);
insert into mc3p values (0, 1, 1), (1, 1, 1), (2, 1, 1);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from mc3p where a < 3 and abs(b) = 1;
--
@@ -927,12 +927,12 @@ select * from mc3p where a < 3 and abs(b) = 1;
--
prepare ps1 as
select * from mc3p where a = $1 and abs(b) < (select 3);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
execute ps1(1);
deallocate ps1;
prepare ps2 as
select * from mc3p where a <= $1 and abs(b) < (select 3);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
execute ps2(1);
deallocate ps2;
@@ -946,10 +946,10 @@ create table boolp (a bool) partition by list (a);
create table boolp_t partition of boolp for values in('t');
create table boolp_f partition of boolp for values in('f');
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from boolp where a = (select value from boolvalues where value);
-explain (analyze, costs off, summary off, timing off, buffers off)
+explain (analyze, costs off, summary off, timing off, buffers off, io off)
select * from boolp where a = (select value from boolvalues where not value);
drop table boolp;
--
2.53.0