LCOV - code coverage report
Current view: top level - src/backend/commands - explain_format.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 248 326 76.1 %
Date: 2025-04-01 14:15:22 Functions: 22 22 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * explain_format.c
       4             :  *    Format routines for explaining query execution plans
       5             :  *
       6             :  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994-5, Regents of the University of California
       8             :  *
       9             :  * IDENTIFICATION
      10             :  *    src/backend/commands/explain_format.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : #include "postgres.h"
      15             : 
      16             : #include "commands/explain.h"
      17             : #include "commands/explain_format.h"
      18             : #include "commands/explain_state.h"
      19             : #include "utils/json.h"
      20             : #include "utils/xml.h"
      21             : 
      22             : /* OR-able flags for ExplainXMLTag() */
      23             : #define X_OPENING 0
      24             : #define X_CLOSING 1
      25             : #define X_CLOSE_IMMEDIATE 2
      26             : #define X_NOWHITESPACE 4
      27             : 
      28             : static void ExplainJSONLineEnding(ExplainState *es);
      29             : static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
      30             : static void ExplainYAMLLineStarting(ExplainState *es);
      31             : static void escape_yaml(StringInfo buf, const char *str);
      32             : 
      33             : /*
      34             :  * Explain a property, such as sort keys or targets, that takes the form of
      35             :  * a list of unlabeled items.  "data" is a list of C strings.
      36             :  */
      37             : void
      38       15906 : ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
      39             : {
      40             :     ListCell   *lc;
      41       15906 :     bool        first = true;
      42             : 
      43       15906 :     switch (es->format)
      44             :     {
      45       15740 :         case EXPLAIN_FORMAT_TEXT:
      46       15740 :             ExplainIndentText(es);
      47       15740 :             appendStringInfo(es->str, "%s: ", qlabel);
      48       47752 :             foreach(lc, data)
      49             :             {
      50       32012 :                 if (!first)
      51       16272 :                     appendStringInfoString(es->str, ", ");
      52       32012 :                 appendStringInfoString(es->str, (const char *) lfirst(lc));
      53       32012 :                 first = false;
      54             :             }
      55       15740 :             appendStringInfoChar(es->str, '\n');
      56       15740 :             break;
      57             : 
      58           4 :         case EXPLAIN_FORMAT_XML:
      59           4 :             ExplainXMLTag(qlabel, X_OPENING, es);
      60          10 :             foreach(lc, data)
      61             :             {
      62             :                 char       *str;
      63             : 
      64           6 :                 appendStringInfoSpaces(es->str, es->indent * 2 + 2);
      65           6 :                 appendStringInfoString(es->str, "<Item>");
      66           6 :                 str = escape_xml((const char *) lfirst(lc));
      67           6 :                 appendStringInfoString(es->str, str);
      68           6 :                 pfree(str);
      69           6 :                 appendStringInfoString(es->str, "</Item>\n");
      70             :             }
      71           4 :             ExplainXMLTag(qlabel, X_CLOSING, es);
      72           4 :             break;
      73             : 
      74         162 :         case EXPLAIN_FORMAT_JSON:
      75         162 :             ExplainJSONLineEnding(es);
      76         162 :             appendStringInfoSpaces(es->str, es->indent * 2);
      77         162 :             escape_json(es->str, qlabel);
      78         162 :             appendStringInfoString(es->str, ": [");
      79         666 :             foreach(lc, data)
      80             :             {
      81         504 :                 if (!first)
      82         342 :                     appendStringInfoString(es->str, ", ");
      83         504 :                 escape_json(es->str, (const char *) lfirst(lc));
      84         504 :                 first = false;
      85             :             }
      86         162 :             appendStringInfoChar(es->str, ']');
      87         162 :             break;
      88             : 
      89           0 :         case EXPLAIN_FORMAT_YAML:
      90           0 :             ExplainYAMLLineStarting(es);
      91           0 :             appendStringInfo(es->str, "%s: ", qlabel);
      92           0 :             foreach(lc, data)
      93             :             {
      94           0 :                 appendStringInfoChar(es->str, '\n');
      95           0 :                 appendStringInfoSpaces(es->str, es->indent * 2 + 2);
      96           0 :                 appendStringInfoString(es->str, "- ");
      97           0 :                 escape_yaml(es->str, (const char *) lfirst(lc));
      98             :             }
      99           0 :             break;
     100             :     }
     101       15906 : }
     102             : 
     103             : /*
     104             :  * Explain a property that takes the form of a list of unlabeled items within
     105             :  * another list.  "data" is a list of C strings.
     106             :  */
     107             : void
     108         578 : ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
     109             : {
     110             :     ListCell   *lc;
     111         578 :     bool        first = true;
     112             : 
     113         578 :     switch (es->format)
     114             :     {
     115         578 :         case EXPLAIN_FORMAT_TEXT:
     116             :         case EXPLAIN_FORMAT_XML:
     117         578 :             ExplainPropertyList(qlabel, data, es);
     118         578 :             return;
     119             : 
     120           0 :         case EXPLAIN_FORMAT_JSON:
     121           0 :             ExplainJSONLineEnding(es);
     122           0 :             appendStringInfoSpaces(es->str, es->indent * 2);
     123           0 :             appendStringInfoChar(es->str, '[');
     124           0 :             foreach(lc, data)
     125             :             {
     126           0 :                 if (!first)
     127           0 :                     appendStringInfoString(es->str, ", ");
     128           0 :                 escape_json(es->str, (const char *) lfirst(lc));
     129           0 :                 first = false;
     130             :             }
     131           0 :             appendStringInfoChar(es->str, ']');
     132           0 :             break;
     133             : 
     134           0 :         case EXPLAIN_FORMAT_YAML:
     135           0 :             ExplainYAMLLineStarting(es);
     136           0 :             appendStringInfoString(es->str, "- [");
     137           0 :             foreach(lc, data)
     138             :             {
     139           0 :                 if (!first)
     140           0 :                     appendStringInfoString(es->str, ", ");
     141           0 :                 escape_yaml(es->str, (const char *) lfirst(lc));
     142           0 :                 first = false;
     143             :             }
     144           0 :             appendStringInfoChar(es->str, ']');
     145           0 :             break;
     146             :     }
     147             : }
     148             : 
     149             : /*
     150             :  * Explain a simple property.
     151             :  *
     152             :  * If "numeric" is true, the value is a number (or other value that
     153             :  * doesn't need quoting in JSON).
     154             :  *
     155             :  * If unit is non-NULL the text format will display it after the value.
     156             :  *
     157             :  * This usually should not be invoked directly, but via one of the datatype
     158             :  * specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
     159             :  */
     160             : static void
     161       76872 : ExplainProperty(const char *qlabel, const char *unit, const char *value,
     162             :                 bool numeric, ExplainState *es)
     163             : {
     164       76872 :     switch (es->format)
     165             :     {
     166       47164 :         case EXPLAIN_FORMAT_TEXT:
     167       47164 :             ExplainIndentText(es);
     168       47164 :             if (unit)
     169        4740 :                 appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit);
     170             :             else
     171       42424 :                 appendStringInfo(es->str, "%s: %s\n", qlabel, value);
     172       47164 :             break;
     173             : 
     174         430 :         case EXPLAIN_FORMAT_XML:
     175             :             {
     176             :                 char       *str;
     177             : 
     178         430 :                 appendStringInfoSpaces(es->str, es->indent * 2);
     179         430 :                 ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
     180         430 :                 str = escape_xml(value);
     181         430 :                 appendStringInfoString(es->str, str);
     182         430 :                 pfree(str);
     183         430 :                 ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
     184         430 :                 appendStringInfoChar(es->str, '\n');
     185             :             }
     186         430 :             break;
     187             : 
     188       28906 :         case EXPLAIN_FORMAT_JSON:
     189       28906 :             ExplainJSONLineEnding(es);
     190       28906 :             appendStringInfoSpaces(es->str, es->indent * 2);
     191       28906 :             escape_json(es->str, qlabel);
     192       28906 :             appendStringInfoString(es->str, ": ");
     193       28906 :             if (numeric)
     194       25200 :                 appendStringInfoString(es->str, value);
     195             :             else
     196        3706 :                 escape_json(es->str, value);
     197       28906 :             break;
     198             : 
     199         372 :         case EXPLAIN_FORMAT_YAML:
     200         372 :             ExplainYAMLLineStarting(es);
     201         372 :             appendStringInfo(es->str, "%s: ", qlabel);
     202         372 :             if (numeric)
     203         330 :                 appendStringInfoString(es->str, value);
     204             :             else
     205          42 :                 escape_yaml(es->str, value);
     206         372 :             break;
     207             :     }
     208       76872 : }
     209             : 
     210             : /*
     211             :  * Explain a string-valued property.
     212             :  */
     213             : void
     214       41932 : ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
     215             : {
     216       41932 :     ExplainProperty(qlabel, NULL, value, false, es);
     217       41932 : }
     218             : 
     219             : /*
     220             :  * Explain an integer-valued property.
     221             :  */
     222             : void
     223       15336 : ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value,
     224             :                        ExplainState *es)
     225             : {
     226             :     char        buf[32];
     227             : 
     228       15336 :     snprintf(buf, sizeof(buf), INT64_FORMAT, value);
     229       15336 :     ExplainProperty(qlabel, unit, buf, true, es);
     230       15336 : }
     231             : 
     232             : /*
     233             :  * Explain an unsigned integer-valued property.
     234             :  */
     235             : void
     236        1522 : ExplainPropertyUInteger(const char *qlabel, const char *unit, uint64 value,
     237             :                         ExplainState *es)
     238             : {
     239             :     char        buf[32];
     240             : 
     241        1522 :     snprintf(buf, sizeof(buf), UINT64_FORMAT, value);
     242        1522 :     ExplainProperty(qlabel, unit, buf, true, es);
     243        1522 : }
     244             : 
     245             : /*
     246             :  * Explain a float-valued property, using the specified number of
     247             :  * fractional digits.
     248             :  */
     249             : void
     250       14196 : ExplainPropertyFloat(const char *qlabel, const char *unit, double value,
     251             :                      int ndigits, ExplainState *es)
     252             : {
     253             :     char       *buf;
     254             : 
     255       14196 :     buf = psprintf("%.*f", ndigits, value);
     256       14196 :     ExplainProperty(qlabel, unit, buf, true, es);
     257       14196 :     pfree(buf);
     258       14196 : }
     259             : 
     260             : /*
     261             :  * Explain a bool-valued property.
     262             :  */
     263             : void
     264        3886 : ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
     265             : {
     266        3886 :     ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es);
     267        3886 : }
     268             : 
     269             : /*
     270             :  * Open a group of related objects.
     271             :  *
     272             :  * objtype is the type of the group object, labelname is its label within
     273             :  * a containing object (if any).
     274             :  *
     275             :  * If labeled is true, the group members will be labeled properties,
     276             :  * while if it's false, they'll be unlabeled objects.
     277             :  */
     278             : void
     279      154678 : ExplainOpenGroup(const char *objtype, const char *labelname,
     280             :                  bool labeled, ExplainState *es)
     281             : {
     282      154678 :     switch (es->format)
     283             :     {
     284      151500 :         case EXPLAIN_FORMAT_TEXT:
     285             :             /* nothing to do */
     286      151500 :             break;
     287             : 
     288          54 :         case EXPLAIN_FORMAT_XML:
     289          54 :             ExplainXMLTag(objtype, X_OPENING, es);
     290          54 :             es->indent++;
     291          54 :             break;
     292             : 
     293        3076 :         case EXPLAIN_FORMAT_JSON:
     294        3076 :             ExplainJSONLineEnding(es);
     295        3076 :             appendStringInfoSpaces(es->str, 2 * es->indent);
     296        3076 :             if (labelname)
     297             :             {
     298        1974 :                 escape_json(es->str, labelname);
     299        1974 :                 appendStringInfoString(es->str, ": ");
     300             :             }
     301        3076 :             appendStringInfoChar(es->str, labeled ? '{' : '[');
     302             : 
     303             :             /*
     304             :              * In JSON format, the grouping_stack is an integer list.  0 means
     305             :              * we've emitted nothing at this grouping level, 1 means we've
     306             :              * emitted something (and so the next item needs a comma). See
     307             :              * ExplainJSONLineEnding().
     308             :              */
     309        3076 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
     310        3076 :             es->indent++;
     311        3076 :             break;
     312             : 
     313          48 :         case EXPLAIN_FORMAT_YAML:
     314             : 
     315             :             /*
     316             :              * In YAML format, the grouping stack is an integer list.  0 means
     317             :              * we've emitted nothing at this grouping level AND this grouping
     318             :              * level is unlabeled and must be marked with "- ".  See
     319             :              * ExplainYAMLLineStarting().
     320             :              */
     321          48 :             ExplainYAMLLineStarting(es);
     322          48 :             if (labelname)
     323             :             {
     324          36 :                 appendStringInfo(es->str, "%s: ", labelname);
     325          36 :                 es->grouping_stack = lcons_int(1, es->grouping_stack);
     326             :             }
     327             :             else
     328             :             {
     329          12 :                 appendStringInfoString(es->str, "- ");
     330          12 :                 es->grouping_stack = lcons_int(0, es->grouping_stack);
     331             :             }
     332          48 :             es->indent++;
     333          48 :             break;
     334             :     }
     335      154678 : }
     336             : 
     337             : /*
     338             :  * Close a group of related objects.
     339             :  * Parameters must match the corresponding ExplainOpenGroup call.
     340             :  */
     341             : void
     342      154678 : ExplainCloseGroup(const char *objtype, const char *labelname,
     343             :                   bool labeled, ExplainState *es)
     344             : {
     345      154678 :     switch (es->format)
     346             :     {
     347      151500 :         case EXPLAIN_FORMAT_TEXT:
     348             :             /* nothing to do */
     349      151500 :             break;
     350             : 
     351          54 :         case EXPLAIN_FORMAT_XML:
     352          54 :             es->indent--;
     353          54 :             ExplainXMLTag(objtype, X_CLOSING, es);
     354          54 :             break;
     355             : 
     356        3076 :         case EXPLAIN_FORMAT_JSON:
     357        3076 :             es->indent--;
     358        3076 :             appendStringInfoChar(es->str, '\n');
     359        3076 :             appendStringInfoSpaces(es->str, 2 * es->indent);
     360        3076 :             appendStringInfoChar(es->str, labeled ? '}' : ']');
     361        3076 :             es->grouping_stack = list_delete_first(es->grouping_stack);
     362        3076 :             break;
     363             : 
     364          48 :         case EXPLAIN_FORMAT_YAML:
     365          48 :             es->indent--;
     366          48 :             es->grouping_stack = list_delete_first(es->grouping_stack);
     367          48 :             break;
     368             :     }
     369      154678 : }
     370             : 
     371             : /*
     372             :  * Open a group of related objects, without emitting actual data.
     373             :  *
     374             :  * Prepare the formatting state as though we were beginning a group with
     375             :  * the identified properties, but don't actually emit anything.  Output
     376             :  * subsequent to this call can be redirected into a separate output buffer,
     377             :  * and then eventually appended to the main output buffer after doing a
     378             :  * regular ExplainOpenGroup call (with the same parameters).
     379             :  *
     380             :  * The extra "depth" parameter is the new group's depth compared to current.
     381             :  * It could be more than one, in case the eventual output will be enclosed
     382             :  * in additional nesting group levels.  We assume we don't need to track
     383             :  * formatting state for those levels while preparing this group's output.
     384             :  *
     385             :  * There is no ExplainCloseSetAsideGroup --- in current usage, we always
     386             :  * pop this state with ExplainSaveGroup.
     387             :  */
     388             : void
     389          72 : ExplainOpenSetAsideGroup(const char *objtype, const char *labelname,
     390             :                          bool labeled, int depth, ExplainState *es)
     391             : {
     392          72 :     switch (es->format)
     393             :     {
     394          24 :         case EXPLAIN_FORMAT_TEXT:
     395             :             /* nothing to do */
     396          24 :             break;
     397             : 
     398           0 :         case EXPLAIN_FORMAT_XML:
     399           0 :             es->indent += depth;
     400           0 :             break;
     401             : 
     402          48 :         case EXPLAIN_FORMAT_JSON:
     403          48 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
     404          48 :             es->indent += depth;
     405          48 :             break;
     406             : 
     407           0 :         case EXPLAIN_FORMAT_YAML:
     408           0 :             if (labelname)
     409           0 :                 es->grouping_stack = lcons_int(1, es->grouping_stack);
     410             :             else
     411           0 :                 es->grouping_stack = lcons_int(0, es->grouping_stack);
     412           0 :             es->indent += depth;
     413           0 :             break;
     414             :     }
     415          72 : }
     416             : 
     417             : /*
     418             :  * Pop one level of grouping state, allowing for a re-push later.
     419             :  *
     420             :  * This is typically used after ExplainOpenSetAsideGroup; pass the
     421             :  * same "depth" used for that.
     422             :  *
     423             :  * This should not emit any output.  If state needs to be saved,
     424             :  * save it at *state_save.  Currently, an integer save area is sufficient
     425             :  * for all formats, but we might need to revisit that someday.
     426             :  */
     427             : void
     428         144 : ExplainSaveGroup(ExplainState *es, int depth, int *state_save)
     429             : {
     430         144 :     switch (es->format)
     431             :     {
     432          24 :         case EXPLAIN_FORMAT_TEXT:
     433             :             /* nothing to do */
     434          24 :             break;
     435             : 
     436           0 :         case EXPLAIN_FORMAT_XML:
     437           0 :             es->indent -= depth;
     438           0 :             break;
     439             : 
     440         120 :         case EXPLAIN_FORMAT_JSON:
     441         120 :             es->indent -= depth;
     442         120 :             *state_save = linitial_int(es->grouping_stack);
     443         120 :             es->grouping_stack = list_delete_first(es->grouping_stack);
     444         120 :             break;
     445             : 
     446           0 :         case EXPLAIN_FORMAT_YAML:
     447           0 :             es->indent -= depth;
     448           0 :             *state_save = linitial_int(es->grouping_stack);
     449           0 :             es->grouping_stack = list_delete_first(es->grouping_stack);
     450           0 :             break;
     451             :     }
     452         144 : }
     453             : 
     454             : /*
     455             :  * Re-push one level of grouping state, undoing the effects of ExplainSaveGroup.
     456             :  */
     457             : void
     458          72 : ExplainRestoreGroup(ExplainState *es, int depth, int *state_save)
     459             : {
     460          72 :     switch (es->format)
     461             :     {
     462           0 :         case EXPLAIN_FORMAT_TEXT:
     463             :             /* nothing to do */
     464           0 :             break;
     465             : 
     466           0 :         case EXPLAIN_FORMAT_XML:
     467           0 :             es->indent += depth;
     468           0 :             break;
     469             : 
     470          72 :         case EXPLAIN_FORMAT_JSON:
     471          72 :             es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
     472          72 :             es->indent += depth;
     473          72 :             break;
     474             : 
     475           0 :         case EXPLAIN_FORMAT_YAML:
     476           0 :             es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
     477           0 :             es->indent += depth;
     478           0 :             break;
     479             :     }
     480          72 : }
     481             : 
     482             : /*
     483             :  * Emit a "dummy" group that never has any members.
     484             :  *
     485             :  * objtype is the type of the group object, labelname is its label within
     486             :  * a containing object (if any).
     487             :  */
     488             : void
     489          30 : ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
     490             : {
     491          30 :     switch (es->format)
     492             :     {
     493          30 :         case EXPLAIN_FORMAT_TEXT:
     494             :             /* nothing to do */
     495          30 :             break;
     496             : 
     497           0 :         case EXPLAIN_FORMAT_XML:
     498           0 :             ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
     499           0 :             break;
     500             : 
     501           0 :         case EXPLAIN_FORMAT_JSON:
     502           0 :             ExplainJSONLineEnding(es);
     503           0 :             appendStringInfoSpaces(es->str, 2 * es->indent);
     504           0 :             if (labelname)
     505             :             {
     506           0 :                 escape_json(es->str, labelname);
     507           0 :                 appendStringInfoString(es->str, ": ");
     508             :             }
     509           0 :             escape_json(es->str, objtype);
     510           0 :             break;
     511             : 
     512           0 :         case EXPLAIN_FORMAT_YAML:
     513           0 :             ExplainYAMLLineStarting(es);
     514           0 :             if (labelname)
     515             :             {
     516           0 :                 escape_yaml(es->str, labelname);
     517           0 :                 appendStringInfoString(es->str, ": ");
     518             :             }
     519             :             else
     520             :             {
     521           0 :                 appendStringInfoString(es->str, "- ");
     522             :             }
     523           0 :             escape_yaml(es->str, objtype);
     524           0 :             break;
     525             :     }
     526          30 : }
     527             : 
     528             : /*
     529             :  * Emit the start-of-output boilerplate.
     530             :  *
     531             :  * This is just enough different from processing a subgroup that we need
     532             :  * a separate pair of subroutines.
     533             :  */
     534             : void
     535       23488 : ExplainBeginOutput(ExplainState *es)
     536             : {
     537       23488 :     switch (es->format)
     538             :     {
     539       23182 :         case EXPLAIN_FORMAT_TEXT:
     540             :             /* nothing to do */
     541       23182 :             break;
     542             : 
     543           8 :         case EXPLAIN_FORMAT_XML:
     544           8 :             appendStringInfoString(es->str,
     545             :                                    "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
     546           8 :             es->indent++;
     547           8 :             break;
     548             : 
     549         286 :         case EXPLAIN_FORMAT_JSON:
     550             :             /* top-level structure is an array of plans */
     551         286 :             appendStringInfoChar(es->str, '[');
     552         286 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
     553         286 :             es->indent++;
     554         286 :             break;
     555             : 
     556          12 :         case EXPLAIN_FORMAT_YAML:
     557          12 :             es->grouping_stack = lcons_int(0, es->grouping_stack);
     558          12 :             break;
     559             :     }
     560       23488 : }
     561             : 
     562             : /*
     563             :  * Emit the end-of-output boilerplate.
     564             :  */
     565             : void
     566       23382 : ExplainEndOutput(ExplainState *es)
     567             : {
     568       23382 :     switch (es->format)
     569             :     {
     570       23076 :         case EXPLAIN_FORMAT_TEXT:
     571             :             /* nothing to do */
     572       23076 :             break;
     573             : 
     574           8 :         case EXPLAIN_FORMAT_XML:
     575           8 :             es->indent--;
     576           8 :             appendStringInfoString(es->str, "</explain>");
     577           8 :             break;
     578             : 
     579         286 :         case EXPLAIN_FORMAT_JSON:
     580         286 :             es->indent--;
     581         286 :             appendStringInfoString(es->str, "\n]");
     582         286 :             es->grouping_stack = list_delete_first(es->grouping_stack);
     583         286 :             break;
     584             : 
     585          12 :         case EXPLAIN_FORMAT_YAML:
     586          12 :             es->grouping_stack = list_delete_first(es->grouping_stack);
     587          12 :             break;
     588             :     }
     589       23382 : }
     590             : 
     591             : /*
     592             :  * Put an appropriate separator between multiple plans
     593             :  */
     594             : void
     595          12 : ExplainSeparatePlans(ExplainState *es)
     596             : {
     597          12 :     switch (es->format)
     598             :     {
     599          12 :         case EXPLAIN_FORMAT_TEXT:
     600             :             /* add a blank line */
     601          12 :             appendStringInfoChar(es->str, '\n');
     602          12 :             break;
     603             : 
     604           0 :         case EXPLAIN_FORMAT_XML:
     605             :         case EXPLAIN_FORMAT_JSON:
     606             :         case EXPLAIN_FORMAT_YAML:
     607             :             /* nothing to do */
     608           0 :             break;
     609             :     }
     610          12 : }
     611             : 
     612             : /*
     613             :  * Emit opening or closing XML tag.
     614             :  *
     615             :  * "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
     616             :  * Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
     617             :  * add.
     618             :  *
     619             :  * XML restricts tag names more than our other output formats, eg they can't
     620             :  * contain white space or slashes.  Replace invalid characters with dashes,
     621             :  * so that for example "I/O Read Time" becomes "I-O-Read-Time".
     622             :  */
     623             : static void
     624         976 : ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
     625             : {
     626             :     const char *s;
     627         976 :     const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
     628             : 
     629         976 :     if ((flags & X_NOWHITESPACE) == 0)
     630         116 :         appendStringInfoSpaces(es->str, 2 * es->indent);
     631         976 :     appendStringInfoCharMacro(es->str, '<');
     632         976 :     if ((flags & X_CLOSING) != 0)
     633         488 :         appendStringInfoCharMacro(es->str, '/');
     634       13224 :     for (s = tagname; *s; s++)
     635       12248 :         appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
     636         976 :     if ((flags & X_CLOSE_IMMEDIATE) != 0)
     637           0 :         appendStringInfoString(es->str, " /");
     638         976 :     appendStringInfoCharMacro(es->str, '>');
     639         976 :     if ((flags & X_NOWHITESPACE) == 0)
     640         116 :         appendStringInfoCharMacro(es->str, '\n');
     641         976 : }
     642             : 
     643             : /*
     644             :  * Indent a text-format line.
     645             :  *
     646             :  * We indent by two spaces per indentation level.  However, when emitting
     647             :  * data for a parallel worker there might already be data on the current line
     648             :  * (cf. ExplainOpenWorker); in that case, don't indent any more.
     649             :  */
     650             : void
     651      128696 : ExplainIndentText(ExplainState *es)
     652             : {
     653             :     Assert(es->format == EXPLAIN_FORMAT_TEXT);
     654      128696 :     if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n')
     655      128672 :         appendStringInfoSpaces(es->str, es->indent * 2);
     656      128696 : }
     657             : 
     658             : /*
     659             :  * Emit a JSON line ending.
     660             :  *
     661             :  * JSON requires a comma after each property but the last.  To facilitate this,
     662             :  * in JSON format, the text emitted for each property begins just prior to the
     663             :  * preceding line-break (and comma, if applicable).
     664             :  */
     665             : static void
     666       32144 : ExplainJSONLineEnding(ExplainState *es)
     667             : {
     668             :     Assert(es->format == EXPLAIN_FORMAT_JSON);
     669       32144 :     if (linitial_int(es->grouping_stack) != 0)
     670       29416 :         appendStringInfoChar(es->str, ',');
     671             :     else
     672        2728 :         linitial_int(es->grouping_stack) = 1;
     673       32144 :     appendStringInfoChar(es->str, '\n');
     674       32144 : }
     675             : 
     676             : /*
     677             :  * Indent a YAML line.
     678             :  *
     679             :  * YAML lines are ordinarily indented by two spaces per indentation level.
     680             :  * The text emitted for each property begins just prior to the preceding
     681             :  * line-break, except for the first property in an unlabeled group, for which
     682             :  * it begins immediately after the "- " that introduces the group.  The first
     683             :  * property of the group appears on the same line as the opening "- ".
     684             :  */
     685             : static void
     686         420 : ExplainYAMLLineStarting(ExplainState *es)
     687             : {
     688             :     Assert(es->format == EXPLAIN_FORMAT_YAML);
     689         420 :     if (linitial_int(es->grouping_stack) == 0)
     690             :     {
     691          24 :         linitial_int(es->grouping_stack) = 1;
     692             :     }
     693             :     else
     694             :     {
     695         396 :         appendStringInfoChar(es->str, '\n');
     696         396 :         appendStringInfoSpaces(es->str, es->indent * 2);
     697             :     }
     698         420 : }
     699             : 
     700             : /*
     701             :  * YAML is a superset of JSON; unfortunately, the YAML quoting rules are
     702             :  * ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
     703             :  * http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
     704             :  * Empty strings, strings with leading or trailing whitespace, and strings
     705             :  * containing a variety of special characters must certainly be quoted or the
     706             :  * output is invalid; and other seemingly harmless strings like "0xa" or
     707             :  * "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
     708             :  * constant rather than a string.
     709             :  */
     710             : static void
     711          42 : escape_yaml(StringInfo buf, const char *str)
     712             : {
     713          42 :     escape_json(buf, str);
     714          42 : }

Generated by: LCOV version 1.14