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