mirror of https://github.com/postgres/postgres
explain.c has grown rather large, so move various functions that are principally concerned with output generation to a new source file, explain_format.c, instead of lumping them in with everything else that is part of explain.c Reviewed-by: Peter Geoghegan <pg@bowt.ie> Discussion: http://postgr.es/m/CA+TgmoYutMw1Jgo8BWUmB3TqnOhsEAJiYO=rOQufF4gPLWmkLQ@mail.gmail.compull/203/head
parent
95dbd827f2
commit
9173e8b604
@ -0,0 +1,713 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* explain_format.c |
||||
* Format routines for explaining query execution plans |
||||
* |
||||
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994-5, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/commands/explain_format.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "commands/explain.h" |
||||
#include "commands/explain_format.h" |
||||
#include "utils/json.h" |
||||
#include "utils/xml.h" |
||||
|
||||
/* OR-able flags for ExplainXMLTag() */ |
||||
#define X_OPENING 0 |
||||
#define X_CLOSING 1 |
||||
#define X_CLOSE_IMMEDIATE 2 |
||||
#define X_NOWHITESPACE 4 |
||||
|
||||
static void ExplainJSONLineEnding(ExplainState *es); |
||||
static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); |
||||
static void ExplainYAMLLineStarting(ExplainState *es); |
||||
static void escape_yaml(StringInfo buf, const char *str); |
||||
|
||||
/*
|
||||
* Explain a property, such as sort keys or targets, that takes the form of |
||||
* a list of unlabeled items. "data" is a list of C strings. |
||||
*/ |
||||
void |
||||
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es) |
||||
{ |
||||
ListCell *lc; |
||||
bool first = true; |
||||
|
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
ExplainIndentText(es); |
||||
appendStringInfo(es->str, "%s: ", qlabel); |
||||
foreach(lc, data) |
||||
{ |
||||
if (!first) |
||||
appendStringInfoString(es->str, ", "); |
||||
appendStringInfoString(es->str, (const char *) lfirst(lc)); |
||||
first = false; |
||||
} |
||||
appendStringInfoChar(es->str, '\n'); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
ExplainXMLTag(qlabel, X_OPENING, es); |
||||
foreach(lc, data) |
||||
{ |
||||
char *str; |
||||
|
||||
appendStringInfoSpaces(es->str, es->indent * 2 + 2); |
||||
appendStringInfoString(es->str, "<Item>"); |
||||
str = escape_xml((const char *) lfirst(lc)); |
||||
appendStringInfoString(es->str, str); |
||||
pfree(str); |
||||
appendStringInfoString(es->str, "</Item>\n"); |
||||
} |
||||
ExplainXMLTag(qlabel, X_CLOSING, es); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
ExplainJSONLineEnding(es); |
||||
appendStringInfoSpaces(es->str, es->indent * 2); |
||||
escape_json(es->str, qlabel); |
||||
appendStringInfoString(es->str, ": ["); |
||||
foreach(lc, data) |
||||
{ |
||||
if (!first) |
||||
appendStringInfoString(es->str, ", "); |
||||
escape_json(es->str, (const char *) lfirst(lc)); |
||||
first = false; |
||||
} |
||||
appendStringInfoChar(es->str, ']'); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
ExplainYAMLLineStarting(es); |
||||
appendStringInfo(es->str, "%s: ", qlabel); |
||||
foreach(lc, data) |
||||
{ |
||||
appendStringInfoChar(es->str, '\n'); |
||||
appendStringInfoSpaces(es->str, es->indent * 2 + 2); |
||||
appendStringInfoString(es->str, "- "); |
||||
escape_yaml(es->str, (const char *) lfirst(lc)); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Explain a property that takes the form of a list of unlabeled items within |
||||
* another list. "data" is a list of C strings. |
||||
*/ |
||||
void |
||||
ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es) |
||||
{ |
||||
ListCell *lc; |
||||
bool first = true; |
||||
|
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
case EXPLAIN_FORMAT_XML: |
||||
ExplainPropertyList(qlabel, data, es); |
||||
return; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
ExplainJSONLineEnding(es); |
||||
appendStringInfoSpaces(es->str, es->indent * 2); |
||||
appendStringInfoChar(es->str, '['); |
||||
foreach(lc, data) |
||||
{ |
||||
if (!first) |
||||
appendStringInfoString(es->str, ", "); |
||||
escape_json(es->str, (const char *) lfirst(lc)); |
||||
first = false; |
||||
} |
||||
appendStringInfoChar(es->str, ']'); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
ExplainYAMLLineStarting(es); |
||||
appendStringInfoString(es->str, "- ["); |
||||
foreach(lc, data) |
||||
{ |
||||
if (!first) |
||||
appendStringInfoString(es->str, ", "); |
||||
escape_yaml(es->str, (const char *) lfirst(lc)); |
||||
first = false; |
||||
} |
||||
appendStringInfoChar(es->str, ']'); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Explain a simple property. |
||||
* |
||||
* If "numeric" is true, the value is a number (or other value that |
||||
* doesn't need quoting in JSON). |
||||
* |
||||
* If unit is non-NULL the text format will display it after the value. |
||||
* |
||||
* This usually should not be invoked directly, but via one of the datatype |
||||
* specific routines ExplainPropertyText, ExplainPropertyInteger, etc. |
||||
*/ |
||||
static void |
||||
ExplainProperty(const char *qlabel, const char *unit, const char *value, |
||||
bool numeric, ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
ExplainIndentText(es); |
||||
if (unit) |
||||
appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit); |
||||
else |
||||
appendStringInfo(es->str, "%s: %s\n", qlabel, value); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
{ |
||||
char *str; |
||||
|
||||
appendStringInfoSpaces(es->str, es->indent * 2); |
||||
ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es); |
||||
str = escape_xml(value); |
||||
appendStringInfoString(es->str, str); |
||||
pfree(str); |
||||
ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es); |
||||
appendStringInfoChar(es->str, '\n'); |
||||
} |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
ExplainJSONLineEnding(es); |
||||
appendStringInfoSpaces(es->str, es->indent * 2); |
||||
escape_json(es->str, qlabel); |
||||
appendStringInfoString(es->str, ": "); |
||||
if (numeric) |
||||
appendStringInfoString(es->str, value); |
||||
else |
||||
escape_json(es->str, value); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
ExplainYAMLLineStarting(es); |
||||
appendStringInfo(es->str, "%s: ", qlabel); |
||||
if (numeric) |
||||
appendStringInfoString(es->str, value); |
||||
else |
||||
escape_yaml(es->str, value); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Explain a string-valued property. |
||||
*/ |
||||
void |
||||
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es) |
||||
{ |
||||
ExplainProperty(qlabel, NULL, value, false, es); |
||||
} |
||||
|
||||
/*
|
||||
* Explain an integer-valued property. |
||||
*/ |
||||
void |
||||
ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value, |
||||
ExplainState *es) |
||||
{ |
||||
char buf[32]; |
||||
|
||||
snprintf(buf, sizeof(buf), INT64_FORMAT, value); |
||||
ExplainProperty(qlabel, unit, buf, true, es); |
||||
} |
||||
|
||||
/*
|
||||
* Explain an unsigned integer-valued property. |
||||
*/ |
||||
void |
||||
ExplainPropertyUInteger(const char *qlabel, const char *unit, uint64 value, |
||||
ExplainState *es) |
||||
{ |
||||
char buf[32]; |
||||
|
||||
snprintf(buf, sizeof(buf), UINT64_FORMAT, value); |
||||
ExplainProperty(qlabel, unit, buf, true, es); |
||||
} |
||||
|
||||
/*
|
||||
* Explain a float-valued property, using the specified number of |
||||
* fractional digits. |
||||
*/ |
||||
void |
||||
ExplainPropertyFloat(const char *qlabel, const char *unit, double value, |
||||
int ndigits, ExplainState *es) |
||||
{ |
||||
char *buf; |
||||
|
||||
buf = psprintf("%.*f", ndigits, value); |
||||
ExplainProperty(qlabel, unit, buf, true, es); |
||||
pfree(buf); |
||||
} |
||||
|
||||
/*
|
||||
* Explain a bool-valued property. |
||||
*/ |
||||
void |
||||
ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es) |
||||
{ |
||||
ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es); |
||||
} |
||||
|
||||
/*
|
||||
* Open a group of related objects. |
||||
* |
||||
* objtype is the type of the group object, labelname is its label within |
||||
* a containing object (if any). |
||||
* |
||||
* If labeled is true, the group members will be labeled properties, |
||||
* while if it's false, they'll be unlabeled objects. |
||||
*/ |
||||
void |
||||
ExplainOpenGroup(const char *objtype, const char *labelname, |
||||
bool labeled, ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
ExplainXMLTag(objtype, X_OPENING, es); |
||||
es->indent++; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
ExplainJSONLineEnding(es); |
||||
appendStringInfoSpaces(es->str, 2 * es->indent); |
||||
if (labelname) |
||||
{ |
||||
escape_json(es->str, labelname); |
||||
appendStringInfoString(es->str, ": "); |
||||
} |
||||
appendStringInfoChar(es->str, labeled ? '{' : '['); |
||||
|
||||
/*
|
||||
* In JSON format, the grouping_stack is an integer list. 0 means |
||||
* we've emitted nothing at this grouping level, 1 means we've |
||||
* emitted something (and so the next item needs a comma). See |
||||
* ExplainJSONLineEnding(). |
||||
*/ |
||||
es->grouping_stack = lcons_int(0, es->grouping_stack); |
||||
es->indent++; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
|
||||
/*
|
||||
* In YAML format, the grouping stack is an integer list. 0 means |
||||
* we've emitted nothing at this grouping level AND this grouping |
||||
* level is unlabeled and must be marked with "- ". See |
||||
* ExplainYAMLLineStarting(). |
||||
*/ |
||||
ExplainYAMLLineStarting(es); |
||||
if (labelname) |
||||
{ |
||||
appendStringInfo(es->str, "%s: ", labelname); |
||||
es->grouping_stack = lcons_int(1, es->grouping_stack); |
||||
} |
||||
else |
||||
{ |
||||
appendStringInfoString(es->str, "- "); |
||||
es->grouping_stack = lcons_int(0, es->grouping_stack); |
||||
} |
||||
es->indent++; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Close a group of related objects. |
||||
* Parameters must match the corresponding ExplainOpenGroup call. |
||||
*/ |
||||
void |
||||
ExplainCloseGroup(const char *objtype, const char *labelname, |
||||
bool labeled, ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
es->indent--; |
||||
ExplainXMLTag(objtype, X_CLOSING, es); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
es->indent--; |
||||
appendStringInfoChar(es->str, '\n'); |
||||
appendStringInfoSpaces(es->str, 2 * es->indent); |
||||
appendStringInfoChar(es->str, labeled ? '}' : ']'); |
||||
es->grouping_stack = list_delete_first(es->grouping_stack); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
es->indent--; |
||||
es->grouping_stack = list_delete_first(es->grouping_stack); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Open a group of related objects, without emitting actual data. |
||||
* |
||||
* Prepare the formatting state as though we were beginning a group with |
||||
* the identified properties, but don't actually emit anything. Output |
||||
* subsequent to this call can be redirected into a separate output buffer, |
||||
* and then eventually appended to the main output buffer after doing a |
||||
* regular ExplainOpenGroup call (with the same parameters). |
||||
* |
||||
* The extra "depth" parameter is the new group's depth compared to current. |
||||
* It could be more than one, in case the eventual output will be enclosed |
||||
* in additional nesting group levels. We assume we don't need to track |
||||
* formatting state for those levels while preparing this group's output. |
||||
* |
||||
* There is no ExplainCloseSetAsideGroup --- in current usage, we always |
||||
* pop this state with ExplainSaveGroup. |
||||
*/ |
||||
void |
||||
ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, |
||||
bool labeled, int depth, ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
es->indent += depth; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
es->grouping_stack = lcons_int(0, es->grouping_stack); |
||||
es->indent += depth; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
if (labelname) |
||||
es->grouping_stack = lcons_int(1, es->grouping_stack); |
||||
else |
||||
es->grouping_stack = lcons_int(0, es->grouping_stack); |
||||
es->indent += depth; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Pop one level of grouping state, allowing for a re-push later. |
||||
* |
||||
* This is typically used after ExplainOpenSetAsideGroup; pass the |
||||
* same "depth" used for that. |
||||
* |
||||
* This should not emit any output. If state needs to be saved, |
||||
* save it at *state_save. Currently, an integer save area is sufficient |
||||
* for all formats, but we might need to revisit that someday. |
||||
*/ |
||||
void |
||||
ExplainSaveGroup(ExplainState *es, int depth, int *state_save) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
es->indent -= depth; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
es->indent -= depth; |
||||
*state_save = linitial_int(es->grouping_stack); |
||||
es->grouping_stack = list_delete_first(es->grouping_stack); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
es->indent -= depth; |
||||
*state_save = linitial_int(es->grouping_stack); |
||||
es->grouping_stack = list_delete_first(es->grouping_stack); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Re-push one level of grouping state, undoing the effects of ExplainSaveGroup. |
||||
*/ |
||||
void |
||||
ExplainRestoreGroup(ExplainState *es, int depth, int *state_save) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
es->indent += depth; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
es->grouping_stack = lcons_int(*state_save, es->grouping_stack); |
||||
es->indent += depth; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
es->grouping_stack = lcons_int(*state_save, es->grouping_stack); |
||||
es->indent += depth; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Emit a "dummy" group that never has any members. |
||||
* |
||||
* objtype is the type of the group object, labelname is its label within |
||||
* a containing object (if any). |
||||
*/ |
||||
void |
||||
ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
ExplainJSONLineEnding(es); |
||||
appendStringInfoSpaces(es->str, 2 * es->indent); |
||||
if (labelname) |
||||
{ |
||||
escape_json(es->str, labelname); |
||||
appendStringInfoString(es->str, ": "); |
||||
} |
||||
escape_json(es->str, objtype); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
ExplainYAMLLineStarting(es); |
||||
if (labelname) |
||||
{ |
||||
escape_yaml(es->str, labelname); |
||||
appendStringInfoString(es->str, ": "); |
||||
} |
||||
else |
||||
{ |
||||
appendStringInfoString(es->str, "- "); |
||||
} |
||||
escape_yaml(es->str, objtype); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Emit the start-of-output boilerplate. |
||||
* |
||||
* This is just enough different from processing a subgroup that we need |
||||
* a separate pair of subroutines. |
||||
*/ |
||||
void |
||||
ExplainBeginOutput(ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
appendStringInfoString(es->str, |
||||
"<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n"); |
||||
es->indent++; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
/* top-level structure is an array of plans */ |
||||
appendStringInfoChar(es->str, '['); |
||||
es->grouping_stack = lcons_int(0, es->grouping_stack); |
||||
es->indent++; |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
es->grouping_stack = lcons_int(0, es->grouping_stack); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Emit the end-of-output boilerplate. |
||||
*/ |
||||
void |
||||
ExplainEndOutput(ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* nothing to do */ |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
es->indent--; |
||||
appendStringInfoString(es->str, "</explain>"); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_JSON: |
||||
es->indent--; |
||||
appendStringInfoString(es->str, "\n]"); |
||||
es->grouping_stack = list_delete_first(es->grouping_stack); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_YAML: |
||||
es->grouping_stack = list_delete_first(es->grouping_stack); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Put an appropriate separator between multiple plans |
||||
*/ |
||||
void |
||||
ExplainSeparatePlans(ExplainState *es) |
||||
{ |
||||
switch (es->format) |
||||
{ |
||||
case EXPLAIN_FORMAT_TEXT: |
||||
/* add a blank line */ |
||||
appendStringInfoChar(es->str, '\n'); |
||||
break; |
||||
|
||||
case EXPLAIN_FORMAT_XML: |
||||
case EXPLAIN_FORMAT_JSON: |
||||
case EXPLAIN_FORMAT_YAML: |
||||
/* nothing to do */ |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Emit opening or closing XML tag. |
||||
* |
||||
* "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE. |
||||
* Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally |
||||
* add. |
||||
* |
||||
* XML restricts tag names more than our other output formats, eg they can't |
||||
* contain white space or slashes. Replace invalid characters with dashes, |
||||
* so that for example "I/O Read Time" becomes "I-O-Read-Time". |
||||
*/ |
||||
static void |
||||
ExplainXMLTag(const char *tagname, int flags, ExplainState *es) |
||||
{ |
||||
const char *s; |
||||
const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; |
||||
|
||||
if ((flags & X_NOWHITESPACE) == 0) |
||||
appendStringInfoSpaces(es->str, 2 * es->indent); |
||||
appendStringInfoCharMacro(es->str, '<'); |
||||
if ((flags & X_CLOSING) != 0) |
||||
appendStringInfoCharMacro(es->str, '/'); |
||||
for (s = tagname; *s; s++) |
||||
appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-'); |
||||
if ((flags & X_CLOSE_IMMEDIATE) != 0) |
||||
appendStringInfoString(es->str, " /"); |
||||
appendStringInfoCharMacro(es->str, '>'); |
||||
if ((flags & X_NOWHITESPACE) == 0) |
||||
appendStringInfoCharMacro(es->str, '\n'); |
||||
} |
||||
|
||||
/*
|
||||
* Indent a text-format line. |
||||
* |
||||
* We indent by two spaces per indentation level. However, when emitting |
||||
* data for a parallel worker there might already be data on the current line |
||||
* (cf. ExplainOpenWorker); in that case, don't indent any more. |
||||
*/ |
||||
void |
||||
ExplainIndentText(ExplainState *es) |
||||
{ |
||||
Assert(es->format == EXPLAIN_FORMAT_TEXT); |
||||
if (es->str->len == 0 || es->str->data[es->str->len - 1] == '\n') |
||||
appendStringInfoSpaces(es->str, es->indent * 2); |
||||
} |
||||
|
||||
/*
|
||||
* Emit a JSON line ending. |
||||
* |
||||
* JSON requires a comma after each property but the last. To facilitate this, |
||||
* in JSON format, the text emitted for each property begins just prior to the |
||||
* preceding line-break (and comma, if applicable). |
||||
*/ |
||||
static void |
||||
ExplainJSONLineEnding(ExplainState *es) |
||||
{ |
||||
Assert(es->format == EXPLAIN_FORMAT_JSON); |
||||
if (linitial_int(es->grouping_stack) != 0) |
||||
appendStringInfoChar(es->str, ','); |
||||
else |
||||
linitial_int(es->grouping_stack) = 1; |
||||
appendStringInfoChar(es->str, '\n'); |
||||
} |
||||
|
||||
/*
|
||||
* Indent a YAML line. |
||||
* |
||||
* YAML lines are ordinarily indented by two spaces per indentation level. |
||||
* The text emitted for each property begins just prior to the preceding |
||||
* line-break, except for the first property in an unlabeled group, for which |
||||
* it begins immediately after the "- " that introduces the group. The first |
||||
* property of the group appears on the same line as the opening "- ". |
||||
*/ |
||||
static void |
||||
ExplainYAMLLineStarting(ExplainState *es) |
||||
{ |
||||
Assert(es->format == EXPLAIN_FORMAT_YAML); |
||||
if (linitial_int(es->grouping_stack) == 0) |
||||
{ |
||||
linitial_int(es->grouping_stack) = 1; |
||||
} |
||||
else |
||||
{ |
||||
appendStringInfoChar(es->str, '\n'); |
||||
appendStringInfoSpaces(es->str, es->indent * 2); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* YAML is a superset of JSON; unfortunately, the YAML quoting rules are |
||||
* ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of |
||||
* http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
|
||||
* Empty strings, strings with leading or trailing whitespace, and strings |
||||
* containing a variety of special characters must certainly be quoted or the |
||||
* output is invalid; and other seemingly harmless strings like "0xa" or |
||||
* "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean |
||||
* constant rather than a string. |
||||
*/ |
||||
static void |
||||
escape_yaml(StringInfo buf, const char *str) |
||||
{ |
||||
escape_json(buf, str); |
||||
} |
||||
@ -0,0 +1,52 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* explain_format.h |
||||
* prototypes for explain_format.c |
||||
* |
||||
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994-5, Regents of the University of California |
||||
* |
||||
* src/include/commands/explain_format.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef EXPLAIN_FORMAT_H |
||||
#define EXPLAIN_FORMAT_H |
||||
|
||||
#include "commands/explain.h" |
||||
|
||||
extern void ExplainPropertyList(const char *qlabel, List *data, |
||||
ExplainState *es); |
||||
extern void ExplainPropertyListNested(const char *qlabel, List *data, |
||||
ExplainState *es); |
||||
extern void ExplainPropertyText(const char *qlabel, const char *value, |
||||
ExplainState *es); |
||||
extern void ExplainPropertyInteger(const char *qlabel, const char *unit, |
||||
int64 value, ExplainState *es); |
||||
extern void ExplainPropertyUInteger(const char *qlabel, const char *unit, |
||||
uint64 value, ExplainState *es); |
||||
extern void ExplainPropertyFloat(const char *qlabel, const char *unit, |
||||
double value, int ndigits, ExplainState *es); |
||||
extern void ExplainPropertyBool(const char *qlabel, bool value, |
||||
ExplainState *es); |
||||
|
||||
extern void ExplainOpenGroup(const char *objtype, const char *labelname, |
||||
bool labeled, ExplainState *es); |
||||
extern void ExplainCloseGroup(const char *objtype, const char *labelname, |
||||
bool labeled, ExplainState *es); |
||||
|
||||
extern void ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, |
||||
bool labeled, int depth, ExplainState *es); |
||||
extern void ExplainSaveGroup(ExplainState *es, int depth, int *state_save); |
||||
extern void ExplainRestoreGroup(ExplainState *es, int depth, int *state_save); |
||||
|
||||
extern void ExplainDummyGroup(const char *objtype, const char *labelname, |
||||
ExplainState *es); |
||||
|
||||
extern void ExplainBeginOutput(ExplainState *es); |
||||
extern void ExplainEndOutput(ExplainState *es); |
||||
extern void ExplainSeparatePlans(ExplainState *es); |
||||
|
||||
extern void ExplainIndentText(ExplainState *es); |
||||
|
||||
#endif |
||||
Loading…
Reference in new issue