diff options
author | Robert Haas <rhaas@postgresql.org> | 2025-02-27 12:37:10 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2025-02-27 12:37:10 -0500 |
commit | 9173e8b604636633a8e3aca54bb56a437bffa718 (patch) | |
tree | 3d2ab9fea6b2bf5586da2b7e9dc5c10af0e82041 /src/backend/commands/explain.c | |
parent | 95dbd827f2edc4d10bebd7e840a0bd6782cf69b7 (diff) | |
download | postgresql-9173e8b604636633a8e3aca54bb56a437bffa718.tar.gz postgresql-9173e8b604636633a8e3aca54bb56a437bffa718.zip |
Create explain_format.c and move relevant code there.
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.com
Diffstat (limited to 'src/backend/commands/explain.c')
-rw-r--r-- | src/backend/commands/explain.c | 704 |
1 files changed, 1 insertions, 703 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 4271dd48e4e..38430a2e314 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -17,6 +17,7 @@ #include "catalog/pg_type.h" #include "commands/createas.h" #include "commands/defrem.h" +#include "commands/explain_format.h" #include "commands/prepare.h" #include "foreign/fdwapi.h" #include "jit/jit.h" @@ -57,12 +58,6 @@ typedef struct SerializeMetrics BufferUsage bufferUsage; /* buffers accessed during serialization */ } SerializeMetrics; -/* OR-able flags for ExplainXMLTag() */ -#define X_OPENING 0 -#define X_CLOSING 1 -#define X_CLOSE_IMMEDIATE 2 -#define X_NOWHITESPACE 4 - /* * Various places within need to convert bytes to kilobytes. Round these up * to the next whole kilobyte. @@ -166,19 +161,6 @@ static ExplainWorkersState *ExplainCreateWorkersState(int num_workers); static void ExplainOpenWorker(int n, ExplainState *es); static void ExplainCloseWorker(int n, ExplainState *es); static void ExplainFlushWorkersState(ExplainState *es); -static void ExplainProperty(const char *qlabel, const char *unit, - const char *value, bool numeric, ExplainState *es); -static void ExplainOpenSetAsideGroup(const char *objtype, const char *labelname, - bool labeled, int depth, ExplainState *es); -static void ExplainSaveGroup(ExplainState *es, int depth, int *state_save); -static void ExplainRestoreGroup(ExplainState *es, int depth, int *state_save); -static void ExplainDummyGroup(const char *objtype, const char *labelname, - ExplainState *es); -static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es); -static void ExplainIndentText(ExplainState *es); -static void ExplainJSONLineEnding(ExplainState *es); -static void ExplainYAMLLineStarting(ExplainState *es); -static void escape_yaml(StringInfo buf, const char *str); static SerializeMetrics GetSerializationMetrics(DestReceiver *dest); @@ -4959,690 +4941,6 @@ ExplainFlushWorkersState(ExplainState *es) } /* - * 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. - */ -static 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. - */ -static 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. - */ -static 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). - */ -static 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. - */ -static 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); -} - - -/* * DestReceiver functions for SERIALIZE option * * A DestReceiver for query tuples, that serializes passed rows into RowData |