]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix pg_overexplain to emit valid output with RANGE_TABLE option.
authorAmit Langote <amitlan@postgresql.org>
Thu, 16 Apr 2026 04:49:39 +0000 (13:49 +0900)
committerAmit Langote <amitlan@postgresql.org>
Thu, 16 Apr 2026 04:49:39 +0000 (13:49 +0900)
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 <satyanarlapuram@gmail.com>
Author: Satyanarayana Narlapuram <satyanarlapuram@gmail.com>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CAHg+QDdDrdqMr98a_OBYDYmK3RaT7XwCEShZfvDYKZpZTfOEjQ@mail.gmail.com
Backpatch-through: 18

contrib/pg_overexplain/expected/pg_overexplain.out
contrib/pg_overexplain/pg_overexplain.c
contrib/pg_overexplain/sql/pg_overexplain.sql

index 6de02323d7cfc6eef94aaa1a21f65b5c0290e44d..0c1d32d386ee9ceee12e85b5df60c1b897cbd512 100644 (file)
@@ -291,13 +291,130 @@ $$);
          <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",                             +
+               "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;
index de824566f8c90978f8d41618521638b7c504ab4c..78f128562e6fa9eb69beaaf68118421103c189d4 100644 (file)
@@ -658,7 +658,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,
@@ -666,9 +673,6 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
        if (es->format != EXPLAIN_FORMAT_TEXT ||
                plannedstmt->resultRelations != NIL)
                overexplain_intlist("Result RTIs", plannedstmt->resultRelations, es);
-
-       /* Close group, we're all done */
-       ExplainCloseGroup("Range Table", "Range Table", false, es);
 }
 
 /*
index 42e275ac2f90698cb4ac0cf067da53a07fadb919..4703ac3e3d7cf2c20be460bce289e0ec1217a9fb 100644 (file)
@@ -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;