public inbox for [email protected]  
help / color / mirror / Atom feed
pg_overexplain produces invalid JSON with RANGE_TABLE option
5+ messages / 3 participants
[nested] [flat]

* pg_overexplain produces invalid JSON with RANGE_TABLE option
@ 2026-04-15 21:35  SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-04-15 21:35 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

Hi Hackers,

It appears that pg_overexplain produces invalid JSON output when
EXPLAIN (FORMAT JSON, RANGE_TABLE) is used. The "Unprunable RTIs" and
"Result RTIs" properties are emitted as key:value pairs directly inside
the "Range Table" JSON array, which is structurally invalid.

Attempted to fix this by moving ExplainCloseGroup() before the two
overexplain_bitmapset()
calls, so the properties are emitted as siblings of "Range Table" in the
parent object rather
than inside the array.

Attached a patch to address this which also includes a test.

Repro:

LOAD 'pg_overexplain';
  CREATE TABLE t1 (id serial PRIMARY KEY, val text);
  CREATE TABLE t2 (id serial PRIMARY KEY, a_id int REFERENCES t1(id), data
text);
  INSERT INTO t1 VALUES (1, 'x'), (2, 'y');
  INSERT INTO t2 VALUES (1, 1, 'd1'), (2, 2, 'd2');

EXPLAIN (FORMAT JSON, RANGE_TABLE)
   EXPLAIN (FORMAT JSON, RANGE_TABLE)
    SELECT * FROM t1 a JOIN t2 b ON a.id = b.a_id WHERE a.id = 1; "
| tail -n +2 | python3 -c "import json,sys; json.loads(sys.stdin.read());
print('VALID JSON')"

Thanks,
Satya


Attachments:

  [application/octet-stream] 0001-Fix-pg_overexplain-invalid-JSON-with-RANGE_TABLE.patch (10.4K, 3-0001-Fix-pg_overexplain-invalid-JSON-with-RANGE_TABLE.patch)
  download | inline diff:
diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out
index 05c6686d..12ab9262 100644
--- a/contrib/pg_overexplain/expected/pg_overexplain.out
+++ b/contrib/pg_overexplain/expected/pg_overexplain.out
@@ -294,13 +294,131 @@ $$);
          <Security-Barrier>false</Security-Barrier>                 +
          <Lateral>false</Lateral>                                   +
        </Range-Table-Entry>                                         +
-       <Unprunable-RTIs>1 3 4</Unprunable-RTIs>                     +
-       <Result-RTIs>none</Result-RTIs>                              +
      </Range-Table>                                                 +
+     <Unprunable-RTIs>1 3 4</Unprunable-RTIs>                       +
+     <Result-RTIs>none</Result-RTIs>                                +
    </Query>                                                         +
  </explain>
 (1 row)
 
+-- Test JSON format with RANGE_TABLE to verify valid JSON structure.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF)
+SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
+$$);
+                         explain_filter                         
+----------------------------------------------------------------
+ [                                                             +
+   {                                                           +
+     "Plan": {                                                 +
+       "Node Type": "Aggregate",                               +
+       "Strategy": "Sorted",                                   +
+       "Partial Mode": "Simple",                               +
+       "Parallel Aware": false,                                +
+       "Async Capable": false,                                 +
+       "Disabled": false,                                      +
+       "Group Key": ["vegetables.genus"],                      +
+       "Plans": [                                              +
+         {                                                     +
+           "Node Type": "Sort",                                +
+           "Parent Relationship": "Outer",                     +
+           "Parallel Aware": false,                            +
+           "Async Capable": false,                             +
+           "Disabled": false,                                  +
+           "Sort Key": ["vegetables.genus", "vegetables.name"],+
+           "Plans": [                                          +
+             {                                                 +
+               "Node Type": "Append",                          +
+               "Parent Relationship": "Outer",                 +
+               "Parallel Aware": false,                        +
+               "Async Capable": false,                         +
+               "Disabled": false,                              +
+               "Append RTIs": "1",                             +
+               "Child Append RTIs": "none",                    +
+               "Subplans Removed": 0,                          +
+               "Plans": [                                      +
+                 {                                             +
+                   "Node Type": "Seq Scan",                    +
+                   "Parent Relationship": "Member",            +
+                   "Parallel Aware": false,                    +
+                   "Async Capable": false,                     +
+                   "Relation Name": "brassica",                +
+                   "Alias": "vegetables_1",                    +
+                   "Disabled": false,                          +
+                   "Scan RTI": 3                               +
+                 },                                            +
+                 {                                             +
+                   "Node Type": "Seq Scan",                    +
+                   "Parent Relationship": "Member",            +
+                   "Parallel Aware": false,                    +
+                   "Async Capable": false,                     +
+                   "Relation Name": "daucus",                  +
+                   "Alias": "vegetables_2",                    +
+                   "Disabled": false,                          +
+                   "Scan RTI": 4                               +
+                 }                                             +
+               ]                                               +
+             }                                                 +
+           ]                                                   +
+         }                                                     +
+       ]                                                       +
+     },                                                        +
+     "Range Table": [                                          +
+       {                                                       +
+         "RTI": 1,                                             +
+         "Kind": "relation",                                   +
+         "Inherited": true,                                    +
+         "In From Clause": true,                               +
+         "Eref": "vegetables (id, name, genus)",               +
+         "Relation": "vegetables",                             +
+         "Relation Kind": "partitioned_table",                 +
+         "Relation Lock Mode": "AccessShareLock",              +
+         "Permission Info Index": 1,                           +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       },                                                      +
+       {                                                       +
+         "RTI": 2,                                             +
+         "Kind": "group",                                      +
+         "Inherited": false,                                   +
+         "In From Clause": false,                              +
+         "Eref": "\"*GROUP*\" (genus)",                        +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       },                                                      +
+       {                                                       +
+         "RTI": 3,                                             +
+         "Kind": "relation",                                   +
+         "Inherited": false,                                   +
+         "In From Clause": true,                               +
+         "Alias": "vegetables (id, name, genus)",              +
+         "Eref": "vegetables (id, name, genus)",               +
+         "Relation": "brassica",                               +
+         "Relation Kind": "relation",                          +
+         "Relation Lock Mode": "AccessShareLock",              +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       },                                                      +
+       {                                                       +
+         "RTI": 4,                                             +
+         "Kind": "relation",                                   +
+         "Inherited": false,                                   +
+         "In From Clause": true,                               +
+         "Alias": "vegetables (id, name, genus)",              +
+         "Eref": "vegetables (id, name, genus)",               +
+         "Relation": "daucus",                                 +
+         "Relation Kind": "relation",                          +
+         "Relation Lock Mode": "AccessShareLock",              +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       }                                                       +
+     ],                                                        +
+     "Unprunable RTIs": "1 3 4",                               +
+     "Result RTIs": "none"                                     +
+   }                                                           +
+ ]
+(1 row)
+
 -- Test just the DEBUG option. Verify that it shows information about
 -- disabled nodes, parallel safety, and the parallelModeNeeded flag.
 SET enable_seqscan = false;
diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c
index 715eda8d..fb277e02 100644
--- a/contrib/pg_overexplain/pg_overexplain.c
+++ b/contrib/pg_overexplain/pg_overexplain.c
@@ -776,7 +776,14 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
 		ExplainCloseGroup("Range Table Entry", NULL, true, es);
 	}
 
-	/* Print PlannedStmt fields that contain RTIs. */
+	/* Close the Range Table array before emitting PlannedStmt-level fields. */
+	ExplainCloseGroup("Range Table", "Range Table", false, es);
+
+	/*
+	 * Print PlannedStmt fields that contain RTIs.  These are properties of
+	 * the PlannedStmt, not of individual RTEs, so they belong outside the
+	 * Range Table array.
+	 */
 	if (es->format != EXPLAIN_FORMAT_TEXT ||
 		!bms_is_empty(plannedstmt->unprunableRelids))
 		overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
@@ -785,9 +792,6 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
 		!bms_is_empty(plannedstmt->resultRelationRelids))
 		overexplain_bitmapset("Result RTIs", plannedstmt->resultRelationRelids,
 							  es);
-
-	/* Close group, we're all done */
-	ExplainCloseGroup("Range Table", "Range Table", false, es);
 }
 
 /*
diff --git a/contrib/pg_overexplain/sql/pg_overexplain.sql b/contrib/pg_overexplain/sql/pg_overexplain.sql
index d07f9368..3f17b61a 100644
--- a/contrib/pg_overexplain/sql/pg_overexplain.sql
+++ b/contrib/pg_overexplain/sql/pg_overexplain.sql
@@ -66,6 +66,12 @@ EXPLAIN (DEBUG, RANGE_TABLE, FORMAT XML, COSTS OFF)
 SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
 $$);
 
+-- Test JSON format with RANGE_TABLE to verify valid JSON structure.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF)
+SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
+$$);
+
 -- Test just the DEBUG option. Verify that it shows information about
 -- disabled nodes, parallel safety, and the parallelModeNeeded flag.
 SET enable_seqscan = false;


^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: pg_overexplain produces invalid JSON with RANGE_TABLE option
@ 2026-04-16 00:06  Amit Langote <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 2 replies; 5+ messages in thread

From: Amit Langote @ 2026-04-16 00:06 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Hi,

On Thu, Apr 16, 2026 at 6:36 AM SATYANARAYANA NARLAPURAM
<[email protected]> wrote:
>
> Hi Hackers,
>
> It appears that pg_overexplain produces invalid JSON output when
> EXPLAIN (FORMAT JSON, RANGE_TABLE) is used. The "Unprunable RTIs" and
> "Result RTIs" properties are emitted as key:value pairs directly inside
> the "Range Table" JSON array, which is structurally invalid.

Thanks for the report and the patch.  That makes sense.

> Attempted to fix this by moving ExplainCloseGroup() before the two overexplain_bitmapset()
> calls, so the properties are emitted as siblings of "Range Table" in the parent object rather
> than inside the array.
>
> Attached a patch to address this which also includes a test.

I have added a commit message.  Will commit shortly to master and v18.

-- 
Thanks, Amit Langote


Attachments:

  [application/octet-stream] v1-0001-Fix-pg_overexplain-to-emit-valid-output-with-RANG.patch (12.0K, 2-v1-0001-Fix-pg_overexplain-to-emit-valid-output-with-RANG.patch)
  download | inline diff:
From 33157b6dc653f81a543bd7fdc38a3ae62b9af0ca Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Thu, 16 Apr 2026 08:54:18 +0900
Subject: [PATCH v1] Fix pg_overexplain to emit valid output with RANGE_TABLE
 option.

overexplain_range_table() emitted the "Unprunable RTIs" and "Result
RTIs" properties before closing the "Range Table" group.  In the JSON
and YAML formats the Range Table group is rendered as an array of RTE
objects, so emitting key/value pairs inside it produced structurally
invalid output.  The XML format had a related oddity, with these
elements nested inside <Range-Table> rather than appearing as its
siblings.

These fields are properties of the PlannedStmt as a whole, not of any
individual RTE, so close the Range Table group before emitting them.
They now appear as siblings of "Range Table" in the parent Query
object, which is what was intended.

Also add a test exercising FORMAT JSON with RANGE_TABLE so that any
future regression in the output structure is caught.

Reported-by: Satyanarayana Narlapuram <[email protected]>
Author: Satyanarayana Narlapuram <[email protected]>
Reviewed-by: Amit Langote <[email protected]>
Discussion: https://postgr.es/m/CAHg+QDdDrdqMr98a_OBYDYmK3RaT7XwCEShZfvDYKZpZTfOEjQ@mail.gmail.com
Backpatch-through: 18
---
 .../expected/pg_overexplain.out               | 122 +++++++++++++++++-
 contrib/pg_overexplain/pg_overexplain.c       |  12 +-
 contrib/pg_overexplain/sql/pg_overexplain.sql |   6 +
 3 files changed, 134 insertions(+), 6 deletions(-)

diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out
index 05c6686d677..12ab92629ab 100644
--- a/contrib/pg_overexplain/expected/pg_overexplain.out
+++ b/contrib/pg_overexplain/expected/pg_overexplain.out
@@ -294,13 +294,131 @@ $$);
          <Security-Barrier>false</Security-Barrier>                 +
          <Lateral>false</Lateral>                                   +
        </Range-Table-Entry>                                         +
-       <Unprunable-RTIs>1 3 4</Unprunable-RTIs>                     +
-       <Result-RTIs>none</Result-RTIs>                              +
      </Range-Table>                                                 +
+     <Unprunable-RTIs>1 3 4</Unprunable-RTIs>                       +
+     <Result-RTIs>none</Result-RTIs>                                +
    </Query>                                                         +
  </explain>
 (1 row)
 
+-- Test JSON format with RANGE_TABLE to verify valid JSON structure.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF)
+SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
+$$);
+                         explain_filter                         
+----------------------------------------------------------------
+ [                                                             +
+   {                                                           +
+     "Plan": {                                                 +
+       "Node Type": "Aggregate",                               +
+       "Strategy": "Sorted",                                   +
+       "Partial Mode": "Simple",                               +
+       "Parallel Aware": false,                                +
+       "Async Capable": false,                                 +
+       "Disabled": false,                                      +
+       "Group Key": ["vegetables.genus"],                      +
+       "Plans": [                                              +
+         {                                                     +
+           "Node Type": "Sort",                                +
+           "Parent Relationship": "Outer",                     +
+           "Parallel Aware": false,                            +
+           "Async Capable": false,                             +
+           "Disabled": false,                                  +
+           "Sort Key": ["vegetables.genus", "vegetables.name"],+
+           "Plans": [                                          +
+             {                                                 +
+               "Node Type": "Append",                          +
+               "Parent Relationship": "Outer",                 +
+               "Parallel Aware": false,                        +
+               "Async Capable": false,                         +
+               "Disabled": false,                              +
+               "Append RTIs": "1",                             +
+               "Child Append RTIs": "none",                    +
+               "Subplans Removed": 0,                          +
+               "Plans": [                                      +
+                 {                                             +
+                   "Node Type": "Seq Scan",                    +
+                   "Parent Relationship": "Member",            +
+                   "Parallel Aware": false,                    +
+                   "Async Capable": false,                     +
+                   "Relation Name": "brassica",                +
+                   "Alias": "vegetables_1",                    +
+                   "Disabled": false,                          +
+                   "Scan RTI": 3                               +
+                 },                                            +
+                 {                                             +
+                   "Node Type": "Seq Scan",                    +
+                   "Parent Relationship": "Member",            +
+                   "Parallel Aware": false,                    +
+                   "Async Capable": false,                     +
+                   "Relation Name": "daucus",                  +
+                   "Alias": "vegetables_2",                    +
+                   "Disabled": false,                          +
+                   "Scan RTI": 4                               +
+                 }                                             +
+               ]                                               +
+             }                                                 +
+           ]                                                   +
+         }                                                     +
+       ]                                                       +
+     },                                                        +
+     "Range Table": [                                          +
+       {                                                       +
+         "RTI": 1,                                             +
+         "Kind": "relation",                                   +
+         "Inherited": true,                                    +
+         "In From Clause": true,                               +
+         "Eref": "vegetables (id, name, genus)",               +
+         "Relation": "vegetables",                             +
+         "Relation Kind": "partitioned_table",                 +
+         "Relation Lock Mode": "AccessShareLock",              +
+         "Permission Info Index": 1,                           +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       },                                                      +
+       {                                                       +
+         "RTI": 2,                                             +
+         "Kind": "group",                                      +
+         "Inherited": false,                                   +
+         "In From Clause": false,                              +
+         "Eref": "\"*GROUP*\" (genus)",                        +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       },                                                      +
+       {                                                       +
+         "RTI": 3,                                             +
+         "Kind": "relation",                                   +
+         "Inherited": false,                                   +
+         "In From Clause": true,                               +
+         "Alias": "vegetables (id, name, genus)",              +
+         "Eref": "vegetables (id, name, genus)",               +
+         "Relation": "brassica",                               +
+         "Relation Kind": "relation",                          +
+         "Relation Lock Mode": "AccessShareLock",              +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       },                                                      +
+       {                                                       +
+         "RTI": 4,                                             +
+         "Kind": "relation",                                   +
+         "Inherited": false,                                   +
+         "In From Clause": true,                               +
+         "Alias": "vegetables (id, name, genus)",              +
+         "Eref": "vegetables (id, name, genus)",               +
+         "Relation": "daucus",                                 +
+         "Relation Kind": "relation",                          +
+         "Relation Lock Mode": "AccessShareLock",              +
+         "Security Barrier": false,                            +
+         "Lateral": false                                      +
+       }                                                       +
+     ],                                                        +
+     "Unprunable RTIs": "1 3 4",                               +
+     "Result RTIs": "none"                                     +
+   }                                                           +
+ ]
+(1 row)
+
 -- Test just the DEBUG option. Verify that it shows information about
 -- disabled nodes, parallel safety, and the parallelModeNeeded flag.
 SET enable_seqscan = false;
diff --git a/contrib/pg_overexplain/pg_overexplain.c b/contrib/pg_overexplain/pg_overexplain.c
index 715eda8dc56..fb277e02308 100644
--- a/contrib/pg_overexplain/pg_overexplain.c
+++ b/contrib/pg_overexplain/pg_overexplain.c
@@ -776,7 +776,14 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
 		ExplainCloseGroup("Range Table Entry", NULL, true, es);
 	}
 
-	/* Print PlannedStmt fields that contain RTIs. */
+	/* Close the Range Table array before emitting PlannedStmt-level fields. */
+	ExplainCloseGroup("Range Table", "Range Table", false, es);
+
+	/*
+	 * Print PlannedStmt fields that contain RTIs.  These are properties of
+	 * the PlannedStmt, not of individual RTEs, so they belong outside the
+	 * Range Table array.
+	 */
 	if (es->format != EXPLAIN_FORMAT_TEXT ||
 		!bms_is_empty(plannedstmt->unprunableRelids))
 		overexplain_bitmapset("Unprunable RTIs", plannedstmt->unprunableRelids,
@@ -785,9 +792,6 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
 		!bms_is_empty(plannedstmt->resultRelationRelids))
 		overexplain_bitmapset("Result RTIs", plannedstmt->resultRelationRelids,
 							  es);
-
-	/* Close group, we're all done */
-	ExplainCloseGroup("Range Table", "Range Table", false, es);
 }
 
 /*
diff --git a/contrib/pg_overexplain/sql/pg_overexplain.sql b/contrib/pg_overexplain/sql/pg_overexplain.sql
index d07f93688a9..3f17b61a2da 100644
--- a/contrib/pg_overexplain/sql/pg_overexplain.sql
+++ b/contrib/pg_overexplain/sql/pg_overexplain.sql
@@ -66,6 +66,12 @@ EXPLAIN (DEBUG, RANGE_TABLE, FORMAT XML, COSTS OFF)
 SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
 $$);
 
+-- Test JSON format with RANGE_TABLE to verify valid JSON structure.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF)
+SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
+$$);
+
 -- Test just the DEBUG option. Verify that it shows information about
 -- disabled nodes, parallel safety, and the parallelModeNeeded flag.
 SET enable_seqscan = false;
-- 
2.47.3



^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: pg_overexplain produces invalid JSON with RANGE_TABLE option
@ 2026-04-16 01:22  Chao Li <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 1 reply; 5+ messages in thread

From: Chao Li @ 2026-04-16 01:22 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>



> On Apr 16, 2026, at 08:06, Amit Langote <[email protected]> wrote:
> 
> Hi,
> 
> On Thu, Apr 16, 2026 at 6:36 AM SATYANARAYANA NARLAPURAM
> <[email protected]> wrote:
>> 
>> Hi Hackers,
>> 
>> It appears that pg_overexplain produces invalid JSON output when
>> EXPLAIN (FORMAT JSON, RANGE_TABLE) is used. The "Unprunable RTIs" and
>> "Result RTIs" properties are emitted as key:value pairs directly inside
>> the "Range Table" JSON array, which is structurally invalid.
> 
> Thanks for the report and the patch.  That makes sense.
> 
>> Attempted to fix this by moving ExplainCloseGroup() before the two overexplain_bitmapset()
>> calls, so the properties are emitted as siblings of "Range Table" in the parent object rather
>> than inside the array.
>> 
>> Attached a patch to address this which also includes a test.
> 
> I have added a commit message.  Will commit shortly to master and v18.
> 
> -- 
> Thanks, Amit Langote
> <v1-0001-Fix-pg_overexplain-to-emit-valid-output-with-RANG.patch>

Hi Amit, as the commit message mentions YAML format as well, but I don’t find a test case in pg_overexplain.sql, would it make sense to also add a test case for YAML. I tried to add one, see the attached diff file.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






Attachments:

  [application/octet-stream] yaml_test.diff (5.7K, 2-yaml_test.diff)
  download | inline diff:
diff --git a/contrib/pg_overexplain/expected/pg_overexplain.out b/contrib/pg_overexplain/expected/pg_overexplain.out
index 12ab92629ab..f331c236ae4 100644
--- a/contrib/pg_overexplain/expected/pg_overexplain.out
+++ b/contrib/pg_overexplain/expected/pg_overexplain.out
@@ -419,6 +419,102 @@ $$);
  ]
 (1 row)
 
+-- Test YAML format with RANGE_TABLE to verify valid YAML structure.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, FORMAT YAML, COSTS OFF)
+SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
+$$);
+                explain_filter                 
+-----------------------------------------------
+ - Plan:                                      +
+     Node Type: "Aggregate"                   +
+     Strategy: "Sorted"                       +
+     Partial Mode: "Simple"                   +
+     Parallel Aware: false                    +
+     Async Capable: false                     +
+     Disabled: false                          +
+     Group Key:                               +
+       - "vegetables.genus"                   +
+     Plans:                                   +
+       - Node Type: "Sort"                    +
+         Parent Relationship: "Outer"         +
+         Parallel Aware: false                +
+         Async Capable: false                 +
+         Disabled: false                      +
+         Sort Key:                            +
+           - "vegetables.genus"               +
+           - "vegetables.name"                +
+         Plans:                               +
+           - Node Type: "Append"              +
+             Parent Relationship: "Outer"     +
+             Parallel Aware: false            +
+             Async Capable: false             +
+             Disabled: false                  +
+             Append RTIs: "1"                 +
+             Child Append RTIs: "none"        +
+             Subplans Removed: 0              +
+             Plans:                           +
+               - Node Type: "Seq Scan"        +
+                 Parent Relationship: "Member"+
+                 Parallel Aware: false        +
+                 Async Capable: false         +
+                 Relation Name: "brassica"    +
+                 Alias: "vegetables_1"        +
+                 Disabled: false              +
+                 Scan RTI: 3                  +
+               - Node Type: "Seq Scan"        +
+                 Parent Relationship: "Member"+
+                 Parallel Aware: false        +
+                 Async Capable: false         +
+                 Relation Name: "daucus"      +
+                 Alias: "vegetables_2"        +
+                 Disabled: false              +
+                 Scan RTI: 4                  +
+   Range Table:                               +
+     - RTI: 1                                 +
+       Kind: "relation"                       +
+       Inherited: true                        +
+       In From Clause: true                   +
+       Eref: "vegetables (id, name, genus)"   +
+       Relation: "vegetables"                 +
+       Relation Kind: "partitioned_table"     +
+       Relation Lock Mode: "AccessShareLock"  +
+       Permission Info Index: 1               +
+       Security Barrier: false                +
+       Lateral: false                         +
+     - RTI: 2                                 +
+       Kind: "group"                          +
+       Inherited: false                       +
+       In From Clause: false                  +
+       Eref: "\"*GROUP*\" (genus)"            +
+       Security Barrier: false                +
+       Lateral: false                         +
+     - RTI: 3                                 +
+       Kind: "relation"                       +
+       Inherited: false                       +
+       In From Clause: true                   +
+       Alias: "vegetables (id, name, genus)"  +
+       Eref: "vegetables (id, name, genus)"   +
+       Relation: "brassica"                   +
+       Relation Kind: "relation"              +
+       Relation Lock Mode: "AccessShareLock"  +
+       Security Barrier: false                +
+       Lateral: false                         +
+     - RTI: 4                                 +
+       Kind: "relation"                       +
+       Inherited: false                       +
+       In From Clause: true                   +
+       Alias: "vegetables (id, name, genus)"  +
+       Eref: "vegetables (id, name, genus)"   +
+       Relation: "daucus"                     +
+       Relation Kind: "relation"              +
+       Relation Lock Mode: "AccessShareLock"  +
+       Security Barrier: false                +
+       Lateral: false                         +
+   Unprunable RTIs: "1 3 4"                   +
+   Result RTIs: "none"
+(1 row)
+
 -- Test just the DEBUG option. Verify that it shows information about
 -- disabled nodes, parallel safety, and the parallelModeNeeded flag.
 SET enable_seqscan = false;
diff --git a/contrib/pg_overexplain/sql/pg_overexplain.sql b/contrib/pg_overexplain/sql/pg_overexplain.sql
index 3f17b61a2da..8cb6f2718fb 100644
--- a/contrib/pg_overexplain/sql/pg_overexplain.sql
+++ b/contrib/pg_overexplain/sql/pg_overexplain.sql
@@ -72,6 +72,12 @@ EXPLAIN (RANGE_TABLE, FORMAT JSON, COSTS OFF)
 SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
 $$);
 
+-- Test YAML format with RANGE_TABLE to verify valid YAML structure.
+SELECT explain_filter($$
+EXPLAIN (RANGE_TABLE, FORMAT YAML, COSTS OFF)
+SELECT genus, array_agg(name ORDER BY name) FROM vegetables GROUP BY genus
+$$);
+
 -- Test just the DEBUG option. Verify that it shows information about
 -- disabled nodes, parallel safety, and the parallelModeNeeded flag.
 SET enable_seqscan = false;


^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: pg_overexplain produces invalid JSON with RANGE_TABLE option
@ 2026-04-16 03:08  Amit Langote <[email protected]>
  parent: Chao Li <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: Amit Langote @ 2026-04-16 03:08 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On Thu, Apr 16, 2026 at 10:22 AM Chao Li <[email protected]> wrote:
> > On Apr 16, 2026, at 08:06, Amit Langote <[email protected]> wrote:
> > On Thu, Apr 16, 2026 at 6:36 AM SATYANARAYANA NARLAPURAM
> > <[email protected]> wrote:
> >>
> >> Hi Hackers,
> >>
> >> It appears that pg_overexplain produces invalid JSON output when
> >> EXPLAIN (FORMAT JSON, RANGE_TABLE) is used. The "Unprunable RTIs" and
> >> "Result RTIs" properties are emitted as key:value pairs directly inside
> >> the "Range Table" JSON array, which is structurally invalid.
> >
> > Thanks for the report and the patch.  That makes sense.
> >
> >> Attempted to fix this by moving ExplainCloseGroup() before the two overexplain_bitmapset()
> >> calls, so the properties are emitted as siblings of "Range Table" in the parent object rather
> >> than inside the array.
> >>
> >> Attached a patch to address this which also includes a test.
> >
> > I have added a commit message.  Will commit shortly to master and v18.
>
> Hi Amit, as the commit message mentions YAML format as well, but I don’t find a test case in pg_overexplain.sql, would it make sense to also add a test case for YAML. I tried to add one, see the attached diff file.

Thanks, but I think the JSON test is sufficient here. The fix is in
format-agnostic code (the ordering of ExplainCloseGroup relative to
the bitmapset calls), so any regression that affects YAML would show
up in the JSON test too.  Generally, we should avoid adding test cases
that only duplicate existing coverage (though I'm sure I've been
guilty of it myself), as this increases the maintenance burden on
expected-output files without catching anything new.

-- 
Thanks, Amit Langote





^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: pg_overexplain produces invalid JSON with RANGE_TABLE option
@ 2026-04-16 05:17  Amit Langote <[email protected]>
  parent: Amit Langote <[email protected]>
  1 sibling, 0 replies; 5+ messages in thread

From: Amit Langote @ 2026-04-16 05:17 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Thu, Apr 16, 2026 at 9:06 AM Amit Langote <[email protected]> wrote:
> On Thu, Apr 16, 2026 at 6:36 AM SATYANARAYANA NARLAPURAM
> <[email protected]> wrote:
> >
> > Hi Hackers,
> >
> > It appears that pg_overexplain produces invalid JSON output when
> > EXPLAIN (FORMAT JSON, RANGE_TABLE) is used. The "Unprunable RTIs" and
> > "Result RTIs" properties are emitted as key:value pairs directly inside
> > the "Range Table" JSON array, which is structurally invalid.
>
> Thanks for the report and the patch.  That makes sense.
>
> > Attempted to fix this by moving ExplainCloseGroup() before the two overexplain_bitmapset()
> > calls, so the properties are emitted as siblings of "Range Table" in the parent object rather
> > than inside the array.
> >
> > Attached a patch to address this which also includes a test.
>
> I have added a commit message.  Will commit shortly to master and v18.

Pushed.

-- 
Thanks, Amit Langote





^ permalink  raw  reply  [nested|flat] 5+ messages in thread


end of thread, other threads:[~2026-04-16 05:17 UTC | newest]

Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-04-15 21:35 pg_overexplain produces invalid JSON with RANGE_TABLE option SATYANARAYANA NARLAPURAM <[email protected]>
2026-04-16 00:06 ` Amit Langote <[email protected]>
2026-04-16 01:22   ` Chao Li <[email protected]>
2026-04-16 03:08     ` Amit Langote <[email protected]>
2026-04-16 05:17   ` Amit Langote <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox