From b7f7eb1154ec292d7ef464850b98709607c6bd51 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Thu, 26 Mar 2026 01:21:20 +0100
Subject: [PATCH v5 6/6] adjust explain output

- Don't use groups in non-text formats.
- Don't condition fields based on (io_count > 0).
- Print complete labels in non-text formats.
---
 src/backend/commands/explain.c        |  35 ++--
 src/test/regress/expected/explain.out | 220 ++++++++++++++------------
 src/test/regress/sql/explain.sql      |   4 +-
 3 files changed, 132 insertions(+), 127 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 5933993533e..36ddb5e5852 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4018,8 +4018,8 @@ print_io_usage(ExplainState *es, IOStats *stats)
 			if (stats->io_count > 0)
 			{
 				ExplainIndentText(es);
-				appendStringInfo(es->str, "I/O: stalls=%" PRIu64,
-								 stats->stall_count);
+				appendStringInfo(es->str, "I/O: count=%" PRIu64 " stalls=%" PRIu64,
+								 stats->io_count, stats->stall_count);
 
 				appendStringInfo(es->str, " size=%.3f inprogress=%.3f",
 								 (stats->io_nblocks * 1.0 / stats->io_count),
@@ -4030,30 +4030,21 @@ print_io_usage(ExplainState *es, IOStats *stats)
 		}
 		else
 		{
-			ExplainOpenGroup("Prefetch", "Prefetch", true, es);
-
-			ExplainPropertyFloat("Average Distance", NULL,
+			ExplainPropertyFloat("Average Prefetch Distance", NULL,
 								 (stats->distance_sum * 1.0 / stats->prefetch_count), 3, es);
-			ExplainPropertyInteger("Max Distance", NULL,
+			ExplainPropertyInteger("Max Prefetch Distance", NULL,
 								   stats->distance_max, es);
-			ExplainPropertyInteger("Capacity", NULL,
+			ExplainPropertyInteger("Prefetch Capacity", NULL,
 								   stats->distance_capacity, es);
 
-			ExplainCloseGroup("Prefetch", "Prefetch", true, es);
-
-			if (stats->io_count > 0)
-			{
-				ExplainOpenGroup("I/O", "I/O", true, es);
-
-				ExplainPropertyUInteger("Stalls", NULL,
-										stats->stall_count, es);
-				ExplainPropertyFloat("Average IO Size", NULL,
-									 (stats->io_nblocks * 1.0 / stats->io_count), 3, es);
-				ExplainPropertyFloat("Average IOs In Progress", NULL,
-									 (stats->io_in_progress * 1.0 / stats->io_count), 3, es);
-
-				ExplainCloseGroup("I/O", "I/O", true, es);
-			}
+			ExplainPropertyUInteger("I/O Count", NULL,
+									stats->io_count, es);
+			ExplainPropertyUInteger("I/O Stalls", NULL,
+									stats->stall_count, es);
+			ExplainPropertyFloat("Average I/O Size", NULL,
+								 (stats->io_nblocks * 1.0 / Max(1, stats->io_count)), 3, es);
+			ExplainPropertyFloat("Average I/Os In Progress", NULL,
+								 (stats->io_in_progress * 1.0 / Max(1, stats->io_count)), 3, es);
 		}
 	}
 }
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 97cb7206676..3fa6d838545 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -93,112 +93,126 @@ select explain_filter('explain (analyze, buffers, io off, format text) select *
  Execution Time: N.N ms
 (3 rows)
 
-select explain_filter('explain (analyze, buffers, io off, format xml) select * from int8_tbl i8');
-                     explain_filter                     
---------------------------------------------------------
- <explain xmlns="http://www.postgresql.org/N/explain"> +
-   <Query>                                             +
-     <Plan>                                            +
-       <Node-Type>Seq Scan</Node-Type>                 +
-       <Parallel-Aware>false</Parallel-Aware>          +
-       <Async-Capable>false</Async-Capable>            +
-       <Relation-Name>int8_tbl</Relation-Name>         +
-       <Alias>i8</Alias>                               +
-       <Startup-Cost>N.N</Startup-Cost>                +
-       <Total-Cost>N.N</Total-Cost>                    +
-       <Plan-Rows>N</Plan-Rows>                        +
-       <Plan-Width>N</Plan-Width>                      +
-       <Actual-Startup-Time>N.N</Actual-Startup-Time>  +
-       <Actual-Total-Time>N.N</Actual-Total-Time>      +
-       <Actual-Rows>N.N</Actual-Rows>                  +
-       <Actual-Loops>N</Actual-Loops>                  +
-       <Disabled>false</Disabled>                      +
-       <Shared-Hit-Blocks>N</Shared-Hit-Blocks>        +
-       <Shared-Read-Blocks>N</Shared-Read-Blocks>      +
-       <Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>+
-       <Shared-Written-Blocks>N</Shared-Written-Blocks>+
-       <Local-Hit-Blocks>N</Local-Hit-Blocks>          +
-       <Local-Read-Blocks>N</Local-Read-Blocks>        +
-       <Local-Dirtied-Blocks>N</Local-Dirtied-Blocks>  +
-       <Local-Written-Blocks>N</Local-Written-Blocks>  +
-       <Temp-Read-Blocks>N</Temp-Read-Blocks>          +
-       <Temp-Written-Blocks>N</Temp-Written-Blocks>    +
-     </Plan>                                           +
-     <Planning>                                        +
-       <Shared-Hit-Blocks>N</Shared-Hit-Blocks>        +
-       <Shared-Read-Blocks>N</Shared-Read-Blocks>      +
-       <Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>+
-       <Shared-Written-Blocks>N</Shared-Written-Blocks>+
-       <Local-Hit-Blocks>N</Local-Hit-Blocks>          +
-       <Local-Read-Blocks>N</Local-Read-Blocks>        +
-       <Local-Dirtied-Blocks>N</Local-Dirtied-Blocks>  +
-       <Local-Written-Blocks>N</Local-Written-Blocks>  +
-       <Temp-Read-Blocks>N</Temp-Read-Blocks>          +
-       <Temp-Written-Blocks>N</Temp-Written-Blocks>    +
-     </Planning>                                       +
-     <Planning-Time>N.N</Planning-Time>                +
-     <Triggers>                                        +
-     </Triggers>                                       +
-     <Execution-Time>N.N</Execution-Time>              +
-   </Query>                                            +
+select explain_filter('explain (analyze, buffers, io, format xml) select * from int8_tbl i8');
+                          explain_filter                          
+------------------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/N/explain">           +
+   <Query>                                                       +
+     <Plan>                                                      +
+       <Node-Type>Seq Scan</Node-Type>                           +
+       <Parallel-Aware>false</Parallel-Aware>                    +
+       <Async-Capable>false</Async-Capable>                      +
+       <Relation-Name>int8_tbl</Relation-Name>                   +
+       <Alias>i8</Alias>                                         +
+       <Startup-Cost>N.N</Startup-Cost>                          +
+       <Total-Cost>N.N</Total-Cost>                              +
+       <Plan-Rows>N</Plan-Rows>                                  +
+       <Plan-Width>N</Plan-Width>                                +
+       <Actual-Startup-Time>N.N</Actual-Startup-Time>            +
+       <Actual-Total-Time>N.N</Actual-Total-Time>                +
+       <Actual-Rows>N.N</Actual-Rows>                            +
+       <Actual-Loops>N</Actual-Loops>                            +
+       <Disabled>false</Disabled>                                +
+       <Average-Prefetch-Distance>N.N</Average-Prefetch-Distance>+
+       <Max-Prefetch-Distance>N</Max-Prefetch-Distance>          +
+       <Prefetch-Capacity>N</Prefetch-Capacity>                  +
+       <I-O-Count>N</I-O-Count>                                  +
+       <I-O-Stalls>N</I-O-Stalls>                                +
+       <Average-I-O-Size>N.N</Average-I-O-Size>                  +
+       <Average-I-Os-In-Progress>N.N</Average-I-Os-In-Progress>  +
+       <Shared-Hit-Blocks>N</Shared-Hit-Blocks>                  +
+       <Shared-Read-Blocks>N</Shared-Read-Blocks>                +
+       <Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>          +
+       <Shared-Written-Blocks>N</Shared-Written-Blocks>          +
+       <Local-Hit-Blocks>N</Local-Hit-Blocks>                    +
+       <Local-Read-Blocks>N</Local-Read-Blocks>                  +
+       <Local-Dirtied-Blocks>N</Local-Dirtied-Blocks>            +
+       <Local-Written-Blocks>N</Local-Written-Blocks>            +
+       <Temp-Read-Blocks>N</Temp-Read-Blocks>                    +
+       <Temp-Written-Blocks>N</Temp-Written-Blocks>              +
+     </Plan>                                                     +
+     <Planning>                                                  +
+       <Shared-Hit-Blocks>N</Shared-Hit-Blocks>                  +
+       <Shared-Read-Blocks>N</Shared-Read-Blocks>                +
+       <Shared-Dirtied-Blocks>N</Shared-Dirtied-Blocks>          +
+       <Shared-Written-Blocks>N</Shared-Written-Blocks>          +
+       <Local-Hit-Blocks>N</Local-Hit-Blocks>                    +
+       <Local-Read-Blocks>N</Local-Read-Blocks>                  +
+       <Local-Dirtied-Blocks>N</Local-Dirtied-Blocks>            +
+       <Local-Written-Blocks>N</Local-Written-Blocks>            +
+       <Temp-Read-Blocks>N</Temp-Read-Blocks>                    +
+       <Temp-Written-Blocks>N</Temp-Written-Blocks>              +
+     </Planning>                                                 +
+     <Planning-Time>N.N</Planning-Time>                          +
+     <Triggers>                                                  +
+     </Triggers>                                                 +
+     <Execution-Time>N.N</Execution-Time>                        +
+   </Query>                                                      +
  </explain>
 (1 row)
 
-select explain_filter('explain (analyze, serialize, buffers, io off, format yaml) select * from int8_tbl i8');
-        explain_filter         
--------------------------------
- - Plan:                      +
-     Node Type: "Seq Scan"    +
-     Parallel Aware: false    +
-     Async Capable: false     +
-     Relation Name: "int8_tbl"+
-     Alias: "i8"              +
-     Startup Cost: N.N        +
-     Total Cost: N.N          +
-     Plan Rows: N             +
-     Plan Width: N            +
-     Actual Startup Time: N.N +
-     Actual Total Time: N.N   +
-     Actual Rows: N.N         +
-     Actual Loops: N          +
-     Disabled: false          +
-     Shared Hit Blocks: N     +
-     Shared Read Blocks: N    +
-     Shared Dirtied Blocks: N +
-     Shared Written Blocks: N +
-     Local Hit Blocks: N      +
-     Local Read Blocks: N     +
-     Local Dirtied Blocks: N  +
-     Local Written Blocks: N  +
-     Temp Read Blocks: N      +
-     Temp Written Blocks: N   +
-   Planning:                  +
-     Shared Hit Blocks: N     +
-     Shared Read Blocks: N    +
-     Shared Dirtied Blocks: N +
-     Shared Written Blocks: N +
-     Local Hit Blocks: N      +
-     Local Read Blocks: N     +
-     Local Dirtied Blocks: N  +
-     Local Written Blocks: N  +
-     Temp Read Blocks: N      +
-     Temp Written Blocks: N   +
-   Planning Time: N.N         +
-   Triggers:                  +
-   Serialization:             +
-     Time: N.N                +
-     Output Volume: N         +
-     Format: "text"           +
-     Shared Hit Blocks: N     +
-     Shared Read Blocks: N    +
-     Shared Dirtied Blocks: N +
-     Shared Written Blocks: N +
-     Local Hit Blocks: N      +
-     Local Read Blocks: N     +
-     Local Dirtied Blocks: N  +
-     Local Written Blocks: N  +
-     Temp Read Blocks: N      +
-     Temp Written Blocks: N   +
+select explain_filter('explain (analyze, serialize, buffers, io, format yaml) select * from int8_tbl i8');
+           explain_filter           
+------------------------------------
+ - Plan:                           +
+     Node Type: "Seq Scan"         +
+     Parallel Aware: false         +
+     Async Capable: false          +
+     Relation Name: "int8_tbl"     +
+     Alias: "i8"                   +
+     Startup Cost: N.N             +
+     Total Cost: N.N               +
+     Plan Rows: N                  +
+     Plan Width: N                 +
+     Actual Startup Time: N.N      +
+     Actual Total Time: N.N        +
+     Actual Rows: N.N              +
+     Actual Loops: N               +
+     Disabled: false               +
+     Average Prefetch Distance: N.N+
+     Max Prefetch Distance: N      +
+     Prefetch Capacity: N          +
+     I/O Count: N                  +
+     I/O Stalls: N                 +
+     Average I/O Size: N.N         +
+     Average I/Os In Progress: N.N +
+     Shared Hit Blocks: N          +
+     Shared Read Blocks: N         +
+     Shared Dirtied Blocks: N      +
+     Shared Written Blocks: N      +
+     Local Hit Blocks: N           +
+     Local Read Blocks: N          +
+     Local Dirtied Blocks: N       +
+     Local Written Blocks: N       +
+     Temp Read Blocks: N           +
+     Temp Written Blocks: N        +
+   Planning:                       +
+     Shared Hit Blocks: N          +
+     Shared Read Blocks: N         +
+     Shared Dirtied Blocks: N      +
+     Shared Written Blocks: N      +
+     Local Hit Blocks: N           +
+     Local Read Blocks: N          +
+     Local Dirtied Blocks: N       +
+     Local Written Blocks: N       +
+     Temp Read Blocks: N           +
+     Temp Written Blocks: N        +
+   Planning Time: N.N              +
+   Triggers:                       +
+   Serialization:                  +
+     Time: N.N                     +
+     Output Volume: N              +
+     Format: "text"                +
+     Shared Hit Blocks: N          +
+     Shared Read Blocks: N         +
+     Shared Dirtied Blocks: N      +
+     Shared Written Blocks: N      +
+     Local Hit Blocks: N           +
+     Local Read Blocks: N          +
+     Local Dirtied Blocks: N       +
+     Local Written Blocks: N       +
+     Temp Read Blocks: N           +
+     Temp Written Blocks: N        +
    Execution Time: N.N
 (1 row)
 
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index 5ee453a0a96..7acd52da0db 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -66,8 +66,8 @@ 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, 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 (analyze, buffers, io, format xml) select * from int8_tbl i8');
+select explain_filter('explain (analyze, serialize, buffers, io, format yaml) select * from int8_tbl i8');
 select explain_filter('explain (buffers, format text) select * from int8_tbl i8');
 select explain_filter('explain (buffers, format json) select * from int8_tbl i8');
 
-- 
2.53.0

