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 : }
|