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