mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +03:00
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
This commit is contained in:
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "commands/explain.h"
|
#include "commands/explain.h"
|
||||||
|
#include "commands/explain_format.h"
|
||||||
#include "common/pg_prng.h"
|
#include "common/pg_prng.h"
|
||||||
#include "executor/instrument.h"
|
#include "executor/instrument.h"
|
||||||
#include "utils/guc.h"
|
#include "utils/guc.h"
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "commands/copyfrom_internal.h"
|
#include "commands/copyfrom_internal.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "commands/explain.h"
|
#include "commands/explain.h"
|
||||||
|
#include "commands/explain_format.h"
|
||||||
#include "commands/vacuum.h"
|
#include "commands/vacuum.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "foreign/foreign.h"
|
#include "foreign/foreign.h"
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "catalog/pg_opfamily.h"
|
#include "catalog/pg_opfamily.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
#include "commands/explain.h"
|
#include "commands/explain.h"
|
||||||
|
#include "commands/explain_format.h"
|
||||||
#include "executor/execAsync.h"
|
#include "executor/execAsync.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
|
@ -34,6 +34,7 @@ OBJS = \
|
|||||||
dropcmds.o \
|
dropcmds.o \
|
||||||
event_trigger.o \
|
event_trigger.o \
|
||||||
explain.o \
|
explain.o \
|
||||||
|
explain_format.o \
|
||||||
extension.o \
|
extension.o \
|
||||||
foreigncmds.o \
|
foreigncmds.o \
|
||||||
functioncmds.o \
|
functioncmds.o \
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/createas.h"
|
#include "commands/createas.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
|
#include "commands/explain_format.h"
|
||||||
#include "commands/prepare.h"
|
#include "commands/prepare.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "jit/jit.h"
|
#include "jit/jit.h"
|
||||||
@ -57,12 +58,6 @@ typedef struct SerializeMetrics
|
|||||||
BufferUsage bufferUsage; /* buffers accessed during serialization */
|
BufferUsage bufferUsage; /* buffers accessed during serialization */
|
||||||
} SerializeMetrics;
|
} 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
|
* Various places within need to convert bytes to kilobytes. Round these up
|
||||||
* to the next whole kilobyte.
|
* to the next whole kilobyte.
|
||||||
@ -166,19 +161,6 @@ static ExplainWorkersState *ExplainCreateWorkersState(int num_workers);
|
|||||||
static void ExplainOpenWorker(int n, ExplainState *es);
|
static void ExplainOpenWorker(int n, ExplainState *es);
|
||||||
static void ExplainCloseWorker(int n, ExplainState *es);
|
static void ExplainCloseWorker(int n, ExplainState *es);
|
||||||
static void ExplainFlushWorkersState(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);
|
static SerializeMetrics GetSerializationMetrics(DestReceiver *dest);
|
||||||
|
|
||||||
|
|
||||||
@ -4958,690 +4940,6 @@ ExplainFlushWorkersState(ExplainState *es)
|
|||||||
pfree(wstate);
|
pfree(wstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
* DestReceiver functions for SERIALIZE option
|
||||||
*
|
*
|
||||||
|
713
src/backend/commands/explain_format.c
Normal file
713
src/backend/commands/explain_format.c
Normal file
@ -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);
|
||||||
|
}
|
@ -22,6 +22,7 @@ backend_sources += files(
|
|||||||
'dropcmds.c',
|
'dropcmds.c',
|
||||||
'event_trigger.c',
|
'event_trigger.c',
|
||||||
'explain.c',
|
'explain.c',
|
||||||
|
'explain_format.c',
|
||||||
'extension.c',
|
'extension.c',
|
||||||
'foreigncmds.c',
|
'foreigncmds.c',
|
||||||
'functioncmds.c',
|
'functioncmds.c',
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/createas.h"
|
#include "commands/createas.h"
|
||||||
|
#include "commands/explain_format.h"
|
||||||
#include "commands/prepare.h"
|
#include "commands/prepare.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
|
@ -120,30 +120,6 @@ extern void ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc);
|
|||||||
extern void ExplainQueryText(ExplainState *es, QueryDesc *queryDesc);
|
extern void ExplainQueryText(ExplainState *es, QueryDesc *queryDesc);
|
||||||
extern void ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen);
|
extern void ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen);
|
||||||
|
|
||||||
extern void ExplainBeginOutput(ExplainState *es);
|
|
||||||
extern void ExplainEndOutput(ExplainState *es);
|
|
||||||
extern void ExplainSeparatePlans(ExplainState *es);
|
|
||||||
|
|
||||||
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 DestReceiver *CreateExplainSerializeDestReceiver(ExplainState *es);
|
extern DestReceiver *CreateExplainSerializeDestReceiver(ExplainState *es);
|
||||||
|
|
||||||
#endif /* EXPLAIN_H */
|
#endif /* EXPLAIN_H */
|
||||||
|
52
src/include/commands/explain_format.h
Normal file
52
src/include/commands/explain_format.h
Normal file
@ -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
|
Reference in New Issue
Block a user