Coverage Report

Created: 2026-09-28 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/commands/explain_format.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * explain_format.c
4
 *    Format routines for explaining query execution plans
5
 *
6
 * Portions Copyright (c) 1996-2026, 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
0
#define X_OPENING 0
24
0
#define X_CLOSING 1
25
0
#define X_CLOSE_IMMEDIATE 2
26
0
#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
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
39
0
{
40
0
  ListCell   *lc;
41
0
  bool    first = true;
42
43
0
  switch (es->format)
44
0
  {
45
0
    case EXPLAIN_FORMAT_TEXT:
46
0
      ExplainIndentText(es);
47
0
      appendStringInfo(es->str, "%s: ", qlabel);
48
0
      foreach(lc, data)
49
0
      {
50
0
        if (!first)
51
0
          appendStringInfoString(es->str, ", ");
52
0
        appendStringInfoString(es->str, (const char *) lfirst(lc));
53
0
        first = false;
54
0
      }
55
0
      appendStringInfoChar(es->str, '\n');
56
0
      break;
57
58
0
    case EXPLAIN_FORMAT_XML:
59
0
      ExplainXMLTag(qlabel, X_OPENING, es);
60
0
      foreach(lc, data)
61
0
      {
62
0
        char     *str;
63
64
0
        appendStringInfoSpaces(es->str, es->indent * 2 + 2);
65
0
        appendStringInfoString(es->str, "<Item>");
66
0
        str = escape_xml((const char *) lfirst(lc));
67
0
        appendStringInfoString(es->str, str);
68
0
        pfree(str);
69
0
        appendStringInfoString(es->str, "</Item>\n");
70
0
      }
71
0
      ExplainXMLTag(qlabel, X_CLOSING, es);
72
0
      break;
73
74
0
    case EXPLAIN_FORMAT_JSON:
75
0
      ExplainJSONLineEnding(es);
76
0
      appendStringInfoSpaces(es->str, es->indent * 2);
77
0
      escape_json(es->str, qlabel);
78
0
      appendStringInfoString(es->str, ": [");
79
0
      foreach(lc, data)
80
0
      {
81
0
        if (!first)
82
0
          appendStringInfoString(es->str, ", ");
83
0
        escape_json(es->str, (const char *) lfirst(lc));
84
0
        first = false;
85
0
      }
86
0
      appendStringInfoChar(es->str, ']');
87
0
      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
0
      {
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
0
      }
99
0
      break;
100
0
  }
101
0
}
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
ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
109
0
{
110
0
  ListCell   *lc;
111
0
  bool    first = true;
112
113
0
  switch (es->format)
114
0
  {
115
0
    case EXPLAIN_FORMAT_TEXT:
116
0
    case EXPLAIN_FORMAT_XML:
117
0
      ExplainPropertyList(qlabel, data, es);
118
0
      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
0
      {
126
0
        if (!first)
127
0
          appendStringInfoString(es->str, ", ");
128
0
        escape_json(es->str, (const char *) lfirst(lc));
129
0
        first = false;
130
0
      }
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
0
      {
139
0
        if (!first)
140
0
          appendStringInfoString(es->str, ", ");
141
0
        escape_yaml(es->str, (const char *) lfirst(lc));
142
0
        first = false;
143
0
      }
144
0
      appendStringInfoChar(es->str, ']');
145
0
      break;
146
0
  }
147
0
}
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
ExplainProperty(const char *qlabel, const char *unit, const char *value,
162
        bool numeric, ExplainState *es)
163
0
{
164
0
  switch (es->format)
165
0
  {
166
0
    case EXPLAIN_FORMAT_TEXT:
167
0
      ExplainIndentText(es);
168
0
      if (unit)
169
0
        appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit);
170
0
      else
171
0
        appendStringInfo(es->str, "%s: %s\n", qlabel, value);
172
0
      break;
173
174
0
    case EXPLAIN_FORMAT_XML:
175
0
      {
176
0
        char     *str;
177
178
0
        appendStringInfoSpaces(es->str, es->indent * 2);
179
0
        ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
180
0
        str = escape_xml(value);
181
0
        appendStringInfoString(es->str, str);
182
0
        pfree(str);
183
0
        ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
184
0
        appendStringInfoChar(es->str, '\n');
185
0
      }
186
0
      break;
187
188
0
    case EXPLAIN_FORMAT_JSON:
189
0
      ExplainJSONLineEnding(es);
190
0
      appendStringInfoSpaces(es->str, es->indent * 2);
191
0
      escape_json(es->str, qlabel);
192
0
      appendStringInfoString(es->str, ": ");
193
0
      if (numeric)
194
0
        appendStringInfoString(es->str, value);
195
0
      else
196
0
        escape_json(es->str, value);
197
0
      break;
198
199
0
    case EXPLAIN_FORMAT_YAML:
200
0
      ExplainYAMLLineStarting(es);
201
0
      appendStringInfo(es->str, "%s: ", qlabel);
202
0
      if (numeric)
203
0
        appendStringInfoString(es->str, value);
204
0
      else
205
0
        escape_yaml(es->str, value);
206
0
      break;
207
0
  }
208
0
}
209
210
/*
211
 * Explain a string-valued property.
212
 */
213
void
214
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
215
0
{
216
0
  ExplainProperty(qlabel, NULL, value, false, es);
217
0
}
218
219
/*
220
 * Explain an integer-valued property.
221
 */
222
void
223
ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value,
224
             ExplainState *es)
225
0
{
226
0
  char    buf[32];
227
228
0
  snprintf(buf, sizeof(buf), INT64_FORMAT, value);
229
0
  ExplainProperty(qlabel, unit, buf, true, es);
230
0
}
231
232
/*
233
 * Explain an unsigned integer-valued property.
234
 */
235
void
236
ExplainPropertyUInteger(const char *qlabel, const char *unit, uint64 value,
237
            ExplainState *es)
238
0
{
239
0
  char    buf[32];
240
241
0
  snprintf(buf, sizeof(buf), UINT64_FORMAT, value);
242
0
  ExplainProperty(qlabel, unit, buf, true, es);
243
0
}
244
245
/*
246
 * Explain a float-valued property, using the specified number of
247
 * fractional digits.
248
 */
249
void
250
ExplainPropertyFloat(const char *qlabel, const char *unit, double value,
251
           int ndigits, ExplainState *es)
252
0
{
253
0
  char     *buf;
254
255
0
  buf = psprintf("%.*f", ndigits, value);
256
0
  ExplainProperty(qlabel, unit, buf, true, es);
257
0
  pfree(buf);
258
0
}
259
260
/*
261
 * Explain a bool-valued property.
262
 */
263
void
264
ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
265
0
{
266
0
  ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es);
267
0
}
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
ExplainOpenGroup(const char *objtype, const char *labelname,
280
         bool labeled, ExplainState *es)
281
0
{
282
0
  switch (es->format)
283
0
  {
284
0
    case EXPLAIN_FORMAT_TEXT:
285
      /* nothing to do */
286
0
      break;
287
288
0
    case EXPLAIN_FORMAT_XML:
289
0
      ExplainXMLTag(objtype, X_OPENING, es);
290
0
      es->indent++;
291
0
      break;
292
293
0
    case EXPLAIN_FORMAT_JSON:
294
0
      ExplainJSONLineEnding(es);
295
0
      appendStringInfoSpaces(es->str, 2 * es->indent);
296
0
      if (labelname)
297
0
      {
298
0
        escape_json(es->str, labelname);
299
0
        appendStringInfoString(es->str, ": ");
300
0
      }
301
0
      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
0
      es->grouping_stack = lcons_int(0, es->grouping_stack);
310
0
      es->indent++;
311
0
      break;
312
313
0
    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
0
      ExplainYAMLLineStarting(es);
322
0
      if (labelname)
323
0
      {
324
0
        appendStringInfo(es->str, "%s: ", labelname);
325
0
        es->grouping_stack = lcons_int(1, es->grouping_stack);
326
0
      }
327
0
      else
328
0
      {
329
0
        appendStringInfoString(es->str, "- ");
330
0
        es->grouping_stack = lcons_int(0, es->grouping_stack);
331
0
      }
332
0
      es->indent++;
333
0
      break;
334
0
  }
335
0
}
336
337
/*
338
 * Close a group of related objects.
339
 * Parameters must match the corresponding ExplainOpenGroup call.
340
 */
341
void
342
ExplainCloseGroup(const char *objtype, const char *labelname,
343
          bool labeled, ExplainState *es)
344
0
{
345
0
  switch (es->format)
346
0
  {
347
0
    case EXPLAIN_FORMAT_TEXT:
348
      /* nothing to do */
349
0
      break;
350
351
0
    case EXPLAIN_FORMAT_XML:
352
0
      es->indent--;
353
0
      ExplainXMLTag(objtype, X_CLOSING, es);
354
0
      break;
355
356
0
    case EXPLAIN_FORMAT_JSON:
357
0
      es->indent--;
358
0
      appendStringInfoChar(es->str, '\n');
359
0
      appendStringInfoSpaces(es->str, 2 * es->indent);
360
0
      appendStringInfoChar(es->str, labeled ? '}' : ']');
361
0
      es->grouping_stack = list_delete_first(es->grouping_stack);
362
0
      break;
363
364
0
    case EXPLAIN_FORMAT_YAML:
365
0
      es->indent--;
366
0
      es->grouping_stack = list_delete_first(es->grouping_stack);
367
0
      break;
368
0
  }
369
0
}
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
ExplainOpenSetAsideGroup(const char *objtype, const char *labelname,
390
             bool labeled, int depth, ExplainState *es)
391
0
{
392
0
  switch (es->format)
393
0
  {
394
0
    case EXPLAIN_FORMAT_TEXT:
395
      /* nothing to do */
396
0
      break;
397
398
0
    case EXPLAIN_FORMAT_XML:
399
0
      es->indent += depth;
400
0
      break;
401
402
0
    case EXPLAIN_FORMAT_JSON:
403
0
      es->grouping_stack = lcons_int(0, es->grouping_stack);
404
0
      es->indent += depth;
405
0
      break;
406
407
0
    case EXPLAIN_FORMAT_YAML:
408
0
      if (labelname)
409
0
        es->grouping_stack = lcons_int(1, es->grouping_stack);
410
0
      else
411
0
        es->grouping_stack = lcons_int(0, es->grouping_stack);
412
0
      es->indent += depth;
413
0
      break;
414
0
  }
415
0
}
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
ExplainSaveGroup(ExplainState *es, int depth, int *state_save)
429
0
{
430
0
  switch (es->format)
431
0
  {
432
0
    case EXPLAIN_FORMAT_TEXT:
433
      /* nothing to do */
434
0
      break;
435
436
0
    case EXPLAIN_FORMAT_XML:
437
0
      es->indent -= depth;
438
0
      break;
439
440
0
    case EXPLAIN_FORMAT_JSON:
441
0
      es->indent -= depth;
442
0
      *state_save = linitial_int(es->grouping_stack);
443
0
      es->grouping_stack = list_delete_first(es->grouping_stack);
444
0
      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
0
  }
452
0
}
453
454
/*
455
 * Re-push one level of grouping state, undoing the effects of ExplainSaveGroup.
456
 */
457
void
458
ExplainRestoreGroup(ExplainState *es, int depth, int *state_save)
459
0
{
460
0
  switch (es->format)
461
0
  {
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
0
    case EXPLAIN_FORMAT_JSON:
471
0
      es->grouping_stack = lcons_int(*state_save, es->grouping_stack);
472
0
      es->indent += depth;
473
0
      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
0
  }
480
0
}
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
ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
490
0
{
491
0
  switch (es->format)
492
0
  {
493
0
    case EXPLAIN_FORMAT_TEXT:
494
      /* nothing to do */
495
0
      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
0
      {
506
0
        escape_json(es->str, labelname);
507
0
        appendStringInfoString(es->str, ": ");
508
0
      }
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
0
      {
516
0
        escape_yaml(es->str, labelname);
517
0
        appendStringInfoString(es->str, ": ");
518
0
      }
519
0
      else
520
0
      {
521
0
        appendStringInfoString(es->str, "- ");
522
0
      }
523
0
      escape_yaml(es->str, objtype);
524
0
      break;
525
0
  }
526
0
}
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
ExplainBeginOutput(ExplainState *es)
536
0
{
537
0
  switch (es->format)
538
0
  {
539
0
    case EXPLAIN_FORMAT_TEXT:
540
      /* nothing to do */
541
0
      break;
542
543
0
    case EXPLAIN_FORMAT_XML:
544
0
      appendStringInfoString(es->str,
545
0
                   "<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
546
0
      es->indent++;
547
0
      break;
548
549
0
    case EXPLAIN_FORMAT_JSON:
550
      /* top-level structure is an array of plans */
551
0
      appendStringInfoChar(es->str, '[');
552
0
      es->grouping_stack = lcons_int(0, es->grouping_stack);
553
0
      es->indent++;
554
0
      break;
555
556
0
    case EXPLAIN_FORMAT_YAML:
557
0
      es->grouping_stack = lcons_int(0, es->grouping_stack);
558
0
      break;
559
0
  }
560
0
}
561
562
/*
563
 * Emit the end-of-output boilerplate.
564
 */
565
void
566
ExplainEndOutput(ExplainState *es)
567
0
{
568
0
  switch (es->format)
569
0
  {
570
0
    case EXPLAIN_FORMAT_TEXT:
571
      /* nothing to do */
572
0
      break;
573
574
0
    case EXPLAIN_FORMAT_XML:
575
0
      es->indent--;
576
0
      appendStringInfoString(es->str, "</explain>");
577
0
      break;
578
579
0
    case EXPLAIN_FORMAT_JSON:
580
0
      es->indent--;
581
0
      appendStringInfoString(es->str, "\n]");
582
0
      es->grouping_stack = list_delete_first(es->grouping_stack);
583
0
      break;
584
585
0
    case EXPLAIN_FORMAT_YAML:
586
0
      es->grouping_stack = list_delete_first(es->grouping_stack);
587
0
      break;
588
0
  }
589
0
}
590
591
/*
592
 * Put an appropriate separator between multiple plans
593
 */
594
void
595
ExplainSeparatePlans(ExplainState *es)
596
0
{
597
0
  switch (es->format)
598
0
  {
599
0
    case EXPLAIN_FORMAT_TEXT:
600
      /* add a blank line */
601
0
      appendStringInfoChar(es->str, '\n');
602
0
      break;
603
604
0
    case EXPLAIN_FORMAT_XML:
605
0
    case EXPLAIN_FORMAT_JSON:
606
0
    case EXPLAIN_FORMAT_YAML:
607
      /* nothing to do */
608
0
      break;
609
0
  }
610
0
}
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
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
625
0
{
626
0
  const char *s;
627
0
  const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
628
629
0
  if ((flags & X_NOWHITESPACE) == 0)
630
0
    appendStringInfoSpaces(es->str, 2 * es->indent);
631
0
  appendStringInfoCharMacro(es->str, '<');
632
0
  if ((flags & X_CLOSING) != 0)
633
0
    appendStringInfoCharMacro(es->str, '/');
634
0
  for (s = tagname; *s; s++)
635
0
    appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
636
0
  if ((flags & X_CLOSE_IMMEDIATE) != 0)
637
0
    appendStringInfoString(es->str, " /");
638
0
  appendStringInfoCharMacro(es->str, '>');
639
0
  if ((flags & X_NOWHITESPACE) == 0)
640
0
    appendStringInfoCharMacro(es->str, '\n');
641
0
}
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
ExplainIndentText(ExplainState *es)
652
0
{
653
0
  Assert(es->format == EXPLAIN_FORMAT_TEXT);
654
0
  if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n')
655
0
    appendStringInfoSpaces(es->str, es->indent * 2);
656
0
}
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
ExplainJSONLineEnding(ExplainState *es)
667
0
{
668
0
  Assert(es->format == EXPLAIN_FORMAT_JSON);
669
0
  if (linitial_int(es->grouping_stack) != 0)
670
0
    appendStringInfoChar(es->str, ',');
671
0
  else
672
0
    linitial_int(es->grouping_stack) = 1;
673
0
  appendStringInfoChar(es->str, '\n');
674
0
}
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
ExplainYAMLLineStarting(ExplainState *es)
687
0
{
688
0
  Assert(es->format == EXPLAIN_FORMAT_YAML);
689
0
  if (linitial_int(es->grouping_stack) == 0)
690
0
  {
691
0
    linitial_int(es->grouping_stack) = 1;
692
0
  }
693
0
  else
694
0
  {
695
0
    appendStringInfoChar(es->str, '\n');
696
0
    appendStringInfoSpaces(es->str, es->indent * 2);
697
0
  }
698
0
}
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
escape_yaml(StringInfo buf, const char *str)
712
0
{
713
0
  escape_json(buf, str);
714
0
}