aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/perform.sgml24
-rw-r--r--doc/src/sgml/ref/explain.sgml29
-rw-r--r--src/backend/access/common/printtup.c5
-rw-r--r--src/backend/commands/explain.c411
-rw-r--r--src/backend/tcop/dest.c7
-rw-r--r--src/include/commands/explain.h10
-rw-r--r--src/include/tcop/dest.h1
-rw-r--r--src/test/regress/expected/explain.out54
-rw-r--r--src/test/regress/sql/explain.sql9
-rw-r--r--src/tools/pgindent/typedefs.list3
10 files changed, 542 insertions, 11 deletions
diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index 4b831a62066..2d0097f121a 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -144,11 +144,11 @@ EXPLAIN SELECT * FROM tenk1;
It's important to understand that the cost of an upper-level node includes
the cost of all its child nodes. It's also important to realize that
the cost only reflects things that the planner cares about.
- In particular, the cost does not consider the time spent transmitting
- result rows to the client, which could be an important
- factor in the real elapsed time; but the planner ignores it because
- it cannot change it by altering the plan. (Every correct plan will
- output the same row set, we trust.)
+ In particular, the cost does not consider the time spent to convert
+ output values to text form or to transmit them to the client, which
+ could be important factors in the real elapsed time; but the planner
+ ignores those costs because it cannot change them by altering the
+ plan. (Every correct plan will output the same row set, we trust.)
</para>
<para>
@@ -956,6 +956,17 @@ EXPLAIN UPDATE parent SET f2 = f2 + 1 WHERE f1 = 101;
<command>EXPLAIN ANALYZE</command>.
</para>
+ <para>
+ The time shown for the top-level node does not include any time needed
+ to convert the query's output data into displayable form or to send it
+ to the client. While <command>EXPLAIN ANALYZE</command> will never
+ send the data to the client, it can be told to convert the query's
+ output data to displayable form and measure the time needed for that,
+ by specifying the <literal>SERIALIZE</literal> option. That time will
+ be shown separately, and it's also included in the
+ total <literal>Execution time</literal>.
+ </para>
+
</sect2>
<sect2 id="using-explain-caveats">
@@ -965,7 +976,8 @@ EXPLAIN UPDATE parent SET f2 = f2 + 1 WHERE f1 = 101;
There are two significant ways in which run times measured by
<command>EXPLAIN ANALYZE</command> can deviate from normal execution of
the same query. First, since no output rows are delivered to the client,
- network transmission costs and I/O conversion costs are not included.
+ network transmission costs are not included. I/O conversion costs are
+ not included either unless <literal>SERIALIZE</literal> is specified.
Second, the measurement overhead added by <command>EXPLAIN
ANALYZE</command> can be significant, especially on machines with slow
<function>gettimeofday()</function> operating-system calls. You can use the
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index a4b6564bdb3..db9d3a8549a 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -41,6 +41,7 @@ EXPLAIN [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] <rep
SETTINGS [ <replaceable class="parameter">boolean</replaceable> ]
GENERIC_PLAN [ <replaceable class="parameter">boolean</replaceable> ]
BUFFERS [ <replaceable class="parameter">boolean</replaceable> ]
+ SERIALIZE [ { NONE | TEXT | BINARY } ]
WAL [ <replaceable class="parameter">boolean</replaceable> ]
TIMING [ <replaceable class="parameter">boolean</replaceable> ]
SUMMARY [ <replaceable class="parameter">boolean</replaceable> ]
@@ -207,6 +208,34 @@ ROLLBACK;
</varlistentry>
<varlistentry>
+ <term><literal>SERIALIZE</literal></term>
+ <listitem>
+ <para>
+ Include information on the cost
+ of <firstterm>serializing</firstterm> the query's output data, that
+ is converting it to text or binary format to send to the client.
+ This can be a significant part of the time required for regular
+ execution of the query, if the datatype output functions are
+ expensive or if <acronym>TOAST</acronym>ed values must be fetched
+ from out-of-line storage. <command>EXPLAIN</command>'s default
+ behavior, <literal>SERIALIZE NONE</literal>, does not perform these
+ conversions. If <literal>SERIALIZE TEXT</literal>
+ or <literal>SERIALIZE BINARY</literal> is specified, the appropriate
+ conversions are performed, and the time spent doing so is measured
+ (unless <literal>TIMING OFF</literal> is specified). If
+ the <literal>BUFFERS</literal> option is also specified, then any
+ buffer accesses involved in the conversions are counted too.
+ In no case, however, will <command>EXPLAIN</command> actually send
+ the resulting data to the client; hence network transmission costs
+ cannot be investigated this way.
+ Serialization may only be enabled when <literal>ANALYZE</literal> is
+ also enabled. If <literal>SERIALIZE</literal> is written without an
+ argument, <literal>TEXT</literal> is assumed.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>WAL</literal></term>
<listitem>
<para>
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 37c5aa2b955..f2d5ca14fee 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -294,6 +294,9 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
/* ----------------
* printtup --- send a tuple to the client
+ *
+ * Note: if you change this function, see also serializeAnalyzeReceive
+ * in explain.c, which is meant to replicate the computations done here.
* ----------------
*/
static bool
@@ -317,7 +320,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
/*
- * Prepare a DataRow message (note buffer is in per-row context)
+ * Prepare a DataRow message (note buffer is in per-query context)
*/
pq_beginmessage_reuse(buf, 'D');
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 926d70afaf8..895d18ebd59 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -20,6 +20,7 @@
#include "commands/prepare.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
+#include "libpq/pqformat.h"
#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -47,6 +48,14 @@ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
+/* Instrumentation data for SERIALIZE option */
+typedef struct SerializeMetrics
+{
+ uint64 bytesSent; /* # of bytes serialized */
+ instr_time timeSpent; /* time spent serializing */
+ BufferUsage bufferUsage; /* buffers accessed during serialization */
+} SerializeMetrics;
+
/* OR-able flags for ExplainXMLTag() */
#define X_OPENING 0
#define X_CLOSING 1
@@ -59,6 +68,8 @@ static void ExplainOneQuery(Query *query, int cursorOptions,
QueryEnvironment *queryEnv);
static void ExplainPrintJIT(ExplainState *es, int jit_flags,
JitInstrumentation *ji);
+static void ExplainPrintSerialize(ExplainState *es,
+ SerializeMetrics *metrics);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
static double elapsed_time(instr_time *starttime);
@@ -153,6 +164,7 @@ 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);
@@ -204,6 +216,31 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
}
else if (strcmp(opt->defname, "memory") == 0)
es->memory = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "serialize") == 0)
+ {
+ if (opt->arg)
+ {
+ char *p = defGetString(opt);
+
+ if (strcmp(p, "off") == 0 || strcmp(p, "none") == 0)
+ es->serialize = EXPLAIN_SERIALIZE_NONE;
+ else if (strcmp(p, "text") == 0)
+ es->serialize = EXPLAIN_SERIALIZE_TEXT;
+ else if (strcmp(p, "binary") == 0)
+ es->serialize = EXPLAIN_SERIALIZE_BINARY;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
+ opt->defname, p),
+ parser_errposition(pstate, opt->location)));
+ }
+ else
+ {
+ /* SERIALIZE without an argument is taken as 'text' */
+ es->serialize = EXPLAIN_SERIALIZE_TEXT;
+ }
+ }
else if (strcmp(opt->defname, "format") == 0)
{
char *p = defGetString(opt);
@@ -246,6 +283,12 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("EXPLAIN option TIMING requires ANALYZE")));
+ /* check that serialize is used with EXPLAIN ANALYZE */
+ if (es->serialize != EXPLAIN_SERIALIZE_NONE && !es->analyze)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("EXPLAIN option SERIALIZE requires ANALYZE")));
+
/* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
if (es->generic && es->analyze)
ereport(ERROR,
@@ -576,6 +619,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
double totaltime = 0;
int eflags;
int instrument_option = 0;
+ SerializeMetrics serializeMetrics = {0};
Assert(plannedstmt->commandType != CMD_UTILITY);
@@ -604,11 +648,17 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
UpdateActiveSnapshotCommandId();
/*
- * Normally we discard the query's output, but if explaining CREATE TABLE
- * AS, we'd better use the appropriate tuple receiver.
+ * We discard the output if we have no use for it. If we're explaining
+ * CREATE TABLE AS, we'd better use the appropriate tuple receiver, while
+ * the SERIALIZE option requires its own tuple receiver. (If you specify
+ * SERIALIZE while explaining CREATE TABLE AS, you'll see zeroes for the
+ * results, which is appropriate since no data would have gone to the
+ * client.)
*/
if (into)
dest = CreateIntoRelDestReceiver(into);
+ else if (es->serialize != EXPLAIN_SERIALIZE_NONE)
+ dest = CreateExplainSerializeDestReceiver(es);
else
dest = None_Receiver;
@@ -651,6 +701,13 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
totaltime += elapsed_time(&starttime);
}
+ /* grab serialization metrics before we destroy the DestReceiver */
+ if (es->serialize != EXPLAIN_SERIALIZE_NONE)
+ serializeMetrics = GetSerializationMetrics(dest);
+
+ /* call the DestReceiver's destroy method even during explain */
+ dest->rDestroy(dest);
+
ExplainOpenGroup("Query", NULL, true, es);
/* Create textual dump of plan tree */
@@ -700,6 +757,10 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
if (es->costs)
ExplainPrintJITSummary(es, queryDesc);
+ /* Print info about serialization of output */
+ if (es->serialize != EXPLAIN_SERIALIZE_NONE)
+ ExplainPrintSerialize(es, &serializeMetrics);
+
/*
* Close down the query and free resources. Include time for this in the
* total execution time (although it should be pretty minimal).
@@ -1034,6 +1095,62 @@ ExplainPrintJIT(ExplainState *es, int jit_flags, JitInstrumentation *ji)
}
/*
+ * ExplainPrintSerialize -
+ * Append information about query output volume to es->str.
+ */
+static void
+ExplainPrintSerialize(ExplainState *es, SerializeMetrics *metrics)
+{
+ const char *format;
+
+ /* We shouldn't get called for EXPLAIN_SERIALIZE_NONE */
+ if (es->serialize == EXPLAIN_SERIALIZE_TEXT)
+ format = "text";
+ else
+ {
+ Assert(es->serialize == EXPLAIN_SERIALIZE_BINARY);
+ format = "binary";
+ }
+
+ ExplainOpenGroup("Serialization", "Serialization", true, es);
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ ExplainIndentText(es);
+ if (es->timing)
+ appendStringInfo(es->str, "Serialization: time=%.3f ms output=" INT64_FORMAT "kB format=%s\n",
+ 1000.0 * INSTR_TIME_GET_DOUBLE(metrics->timeSpent),
+ (metrics->bytesSent + 512) / 1024,
+ format);
+ else
+ appendStringInfo(es->str, "Serialization: output=" INT64_FORMAT "kB format=%s\n",
+ (metrics->bytesSent + 512) / 1024,
+ format);
+
+ if (es->buffers && peek_buffer_usage(es, &metrics->bufferUsage))
+ {
+ es->indent++;
+ show_buffer_usage(es, &metrics->bufferUsage);
+ es->indent--;
+ }
+ }
+ else
+ {
+ if (es->timing)
+ ExplainPropertyFloat("Time", "ms",
+ 1000.0 * INSTR_TIME_GET_DOUBLE(metrics->timeSpent),
+ 3, es);
+ ExplainPropertyUInteger("Output Volume", "kB",
+ (metrics->bytesSent + 512) / 1024, es);
+ ExplainPropertyText("Format", format, es);
+ if (es->buffers)
+ show_buffer_usage(es, &metrics->bufferUsage);
+ }
+
+ ExplainCloseGroup("Serialization", "Serialization", true, es);
+}
+
+/*
* ExplainQueryText -
* add a "Query Text" node that contains the actual text of the query
*
@@ -5161,3 +5278,293 @@ 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
+ * messages while measuring the resources expended and total serialized size,
+ * while never sending the data to the client. This allows measuring the
+ * overhead of deTOASTing and datatype out/sendfuncs, which are not otherwise
+ * exercisable without actually hitting the network.
+ */
+typedef struct SerializeDestReceiver
+{
+ DestReceiver pub;
+ ExplainState *es; /* this EXPLAIN statement's ExplainState */
+ int8 format; /* text or binary, like pq wire protocol */
+ TupleDesc attrinfo; /* the output tuple desc */
+ int nattrs; /* current number of columns */
+ FmgrInfo *finfos; /* precomputed call info for output fns */
+ MemoryContext tmpcontext; /* per-row temporary memory context */
+ StringInfoData buf; /* buffer to hold the constructed message */
+ SerializeMetrics metrics; /* collected metrics */
+} SerializeDestReceiver;
+
+/*
+ * Get the function lookup info that we'll need for output.
+ *
+ * This is a subset of what printtup_prepare_info() does. We don't need to
+ * cope with format choices varying across columns, so it's slightly simpler.
+ */
+static void
+serialize_prepare_info(SerializeDestReceiver *receiver,
+ TupleDesc typeinfo, int nattrs)
+{
+ /* get rid of any old data */
+ if (receiver->finfos)
+ pfree(receiver->finfos);
+ receiver->finfos = NULL;
+
+ receiver->attrinfo = typeinfo;
+ receiver->nattrs = nattrs;
+ if (nattrs <= 0)
+ return;
+
+ receiver->finfos = (FmgrInfo *) palloc0(nattrs * sizeof(FmgrInfo));
+
+ for (int i = 0; i < nattrs; i++)
+ {
+ FmgrInfo *finfo = receiver->finfos + i;
+ Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+ Oid typoutput;
+ Oid typsend;
+ bool typisvarlena;
+
+ if (receiver->format == 0)
+ {
+ /* wire protocol format text */
+ getTypeOutputInfo(attr->atttypid,
+ &typoutput,
+ &typisvarlena);
+ fmgr_info(typoutput, finfo);
+ }
+ else if (receiver->format == 1)
+ {
+ /* wire protocol format binary */
+ getTypeBinaryOutputInfo(attr->atttypid,
+ &typsend,
+ &typisvarlena);
+ fmgr_info(typsend, finfo);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unsupported format code: %d", receiver->format)));
+ }
+}
+
+/*
+ * serializeAnalyzeReceive - collect tuples for EXPLAIN (SERIALIZE)
+ *
+ * This should match printtup() in printtup.c as closely as possible,
+ * except for the addition of measurement code.
+ */
+static bool
+serializeAnalyzeReceive(TupleTableSlot *slot, DestReceiver *self)
+{
+ TupleDesc typeinfo = slot->tts_tupleDescriptor;
+ SerializeDestReceiver *myState = (SerializeDestReceiver *) self;
+ MemoryContext oldcontext;
+ StringInfo buf = &myState->buf;
+ int natts = typeinfo->natts;
+ instr_time start,
+ end;
+ BufferUsage instr_start;
+
+ /* only measure time, buffers if requested */
+ if (myState->es->timing)
+ INSTR_TIME_SET_CURRENT(start);
+ if (myState->es->buffers)
+ instr_start = pgBufferUsage;
+
+ /* Set or update my derived attribute info, if needed */
+ if (myState->attrinfo != typeinfo || myState->nattrs != natts)
+ serialize_prepare_info(myState, typeinfo, natts);
+
+ /* Make sure the tuple is fully deconstructed */
+ slot_getallattrs(slot);
+
+ /* Switch into per-row context so we can recover memory below */
+ oldcontext = MemoryContextSwitchTo(myState->tmpcontext);
+
+ /*
+ * Prepare a DataRow message (note buffer is in per-query context)
+ *
+ * Note that we fill a StringInfo buffer the same as printtup() does, so
+ * as to capture the costs of manipulating the strings accurately.
+ */
+ pq_beginmessage_reuse(buf, 'D');
+
+ pq_sendint16(buf, natts);
+
+ /*
+ * send the attributes of this tuple
+ */
+ for (int i = 0; i < natts; i++)
+ {
+ FmgrInfo *finfo = myState->finfos + i;
+ Datum attr = slot->tts_values[i];
+
+ if (slot->tts_isnull[i])
+ {
+ pq_sendint32(buf, -1);
+ continue;
+ }
+
+ if (myState->format == 0)
+ {
+ /* Text output */
+ char *outputstr;
+
+ outputstr = OutputFunctionCall(finfo, attr);
+ pq_sendcountedtext(buf, outputstr, strlen(outputstr));
+ }
+ else
+ {
+ /* Binary output */
+ bytea *outputbytes;
+
+ outputbytes = SendFunctionCall(finfo, attr);
+ pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
+ pq_sendbytes(buf, VARDATA(outputbytes),
+ VARSIZE(outputbytes) - VARHDRSZ);
+ }
+ }
+
+ /*
+ * We mustn't call pq_endmessage_reuse(), since that would actually send
+ * the data to the client. Just count the data, instead. We can leave
+ * the buffer alone; it'll be reset on the next iteration (as would also
+ * happen in printtup()).
+ */
+ myState->metrics.bytesSent += buf->len;
+
+ /* Return to caller's context, and flush row's temporary memory */
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextReset(myState->tmpcontext);
+
+ /* Update timing data */
+ if (myState->es->timing)
+ {
+ INSTR_TIME_SET_CURRENT(end);
+ INSTR_TIME_ACCUM_DIFF(myState->metrics.timeSpent, end, start);
+ }
+
+ /* Update buffer metrics */
+ if (myState->es->buffers)
+ BufferUsageAccumDiff(&myState->metrics.bufferUsage,
+ &pgBufferUsage,
+ &instr_start);
+
+ return true;
+}
+
+/*
+ * serializeAnalyzeStartup - start up the serializeAnalyze receiver
+ */
+static void
+serializeAnalyzeStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
+
+ Assert(receiver->es != NULL);
+
+ switch (receiver->es->serialize)
+ {
+ case EXPLAIN_SERIALIZE_NONE:
+ Assert(false);
+ break;
+ case EXPLAIN_SERIALIZE_TEXT:
+ receiver->format = 0; /* wire protocol format text */
+ break;
+ case EXPLAIN_SERIALIZE_BINARY:
+ receiver->format = 1; /* wire protocol format binary */
+ break;
+ }
+
+ /* Create per-row temporary memory context */
+ receiver->tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+ "SerializeTupleReceive",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* The output buffer is re-used across rows, as in printtup.c */
+ initStringInfo(&receiver->buf);
+
+ /* Initialize results counters */
+ memset(&receiver->metrics, 0, sizeof(SerializeMetrics));
+ INSTR_TIME_SET_ZERO(receiver->metrics.timeSpent);
+}
+
+/*
+ * serializeAnalyzeShutdown - shut down the serializeAnalyze receiver
+ */
+static void
+serializeAnalyzeShutdown(DestReceiver *self)
+{
+ SerializeDestReceiver *receiver = (SerializeDestReceiver *) self;
+
+ if (receiver->finfos)
+ pfree(receiver->finfos);
+ receiver->finfos = NULL;
+
+ if (receiver->buf.data)
+ pfree(receiver->buf.data);
+ receiver->buf.data = NULL;
+
+ if (receiver->tmpcontext)
+ MemoryContextDelete(receiver->tmpcontext);
+ receiver->tmpcontext = NULL;
+}
+
+/*
+ * serializeAnalyzeDestroy - destroy the serializeAnalyze receiver
+ */
+static void
+serializeAnalyzeDestroy(DestReceiver *self)
+{
+ pfree(self);
+}
+
+/*
+ * Build a DestReceiver for EXPLAIN (SERIALIZE) instrumentation.
+ */
+DestReceiver *
+CreateExplainSerializeDestReceiver(ExplainState *es)
+{
+ SerializeDestReceiver *self;
+
+ self = (SerializeDestReceiver *) palloc0(sizeof(SerializeDestReceiver));
+
+ self->pub.receiveSlot = serializeAnalyzeReceive;
+ self->pub.rStartup = serializeAnalyzeStartup;
+ self->pub.rShutdown = serializeAnalyzeShutdown;
+ self->pub.rDestroy = serializeAnalyzeDestroy;
+ self->pub.mydest = DestExplainSerialize;
+
+ self->es = es;
+
+ return (DestReceiver *) self;
+}
+
+/*
+ * GetSerializationMetrics - collect metrics
+ *
+ * We have to be careful here since the receiver could be an IntoRel
+ * receiver if the subject statement is CREATE TABLE AS. In that
+ * case, return all-zeroes stats.
+ */
+static SerializeMetrics
+GetSerializationMetrics(DestReceiver *dest)
+{
+ SerializeMetrics empty;
+
+ if (dest->mydest == DestExplainSerialize)
+ return ((SerializeDestReceiver *) dest)->metrics;
+
+ memset(&empty, 0, sizeof(SerializeMetrics));
+ INSTR_TIME_SET_ZERO(empty.timeSpent);
+
+ return empty;
+}
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 6d727ae24fc..96f80b30463 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -33,6 +33,7 @@
#include "access/xact.h"
#include "commands/copy.h"
#include "commands/createas.h"
+#include "commands/explain.h"
#include "commands/matview.h"
#include "executor/functions.h"
#include "executor/tqueue.h"
@@ -151,6 +152,9 @@ CreateDestReceiver(CommandDest dest)
case DestTupleQueue:
return CreateTupleQueueDestReceiver(NULL);
+
+ case DestExplainSerialize:
+ return CreateExplainSerializeDestReceiver(NULL);
}
/* should never get here */
@@ -186,6 +190,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestExplainSerialize:
break;
}
}
@@ -231,6 +236,7 @@ NullCommand(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestExplainSerialize:
break;
}
}
@@ -274,6 +280,7 @@ ReadyForQuery(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestExplainSerialize:
break;
}
}
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index cf195f13597..9b8b351d9a2 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -17,6 +17,13 @@
#include "lib/stringinfo.h"
#include "parser/parse_node.h"
+typedef enum ExplainSerializeOption
+{
+ EXPLAIN_SERIALIZE_NONE,
+ EXPLAIN_SERIALIZE_TEXT,
+ EXPLAIN_SERIALIZE_BINARY,
+} ExplainSerializeOption;
+
typedef enum ExplainFormat
{
EXPLAIN_FORMAT_TEXT,
@@ -48,6 +55,7 @@ typedef struct ExplainState
bool memory; /* print planner's memory usage information */
bool settings; /* print modified settings */
bool generic; /* generate a generic plan */
+ ExplainSerializeOption serialize; /* serialize the query's output? */
ExplainFormat format; /* output format */
/* state for output formatting --- not reset for each new plan tree */
int indent; /* current indentation level */
@@ -132,4 +140,6 @@ extern void ExplainOpenGroup(const char *objtype, const char *labelname,
extern void ExplainCloseGroup(const char *objtype, const char *labelname,
bool labeled, ExplainState *es);
+extern DestReceiver *CreateExplainSerializeDestReceiver(ExplainState *es);
+
#endif /* EXPLAIN_H */
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 7e613bd7fcc..a3d521b6f97 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -96,6 +96,7 @@ typedef enum
DestSQLFunction, /* results sent to SQL-language func mgr */
DestTransientRel, /* results sent to transient relation */
DestTupleQueue, /* results sent to tuple queue */
+ DestExplainSerialize, /* results are serialized and discarded */
} CommandDest;
/* ----------------
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 1299ee79ad5..703800d856d 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -135,7 +135,7 @@ select explain_filter('explain (analyze, buffers, format xml) select * from int8
</explain>
(1 row)
-select explain_filter('explain (analyze, buffers, format yaml) select * from int8_tbl i8');
+select explain_filter('explain (analyze, serialize, buffers, format yaml) select * from int8_tbl i8');
explain_filter
-------------------------------
- Plan: +
@@ -175,6 +175,20 @@ select explain_filter('explain (analyze, buffers, format yaml) select * from int
Temp Written Blocks: N +
Planning Time: N.N +
Triggers: +
+ Serialization: +
+ Time: N.N +
+ Output Volume: N +
+ Format: "text" +
+ Shared Hit Blocks: N +
+ Shared Read Blocks: N +
+ Shared Dirtied Blocks: N +
+ Shared Written Blocks: N +
+ Local Hit Blocks: N +
+ Local Read Blocks: N +
+ Local Dirtied Blocks: N +
+ Local Written Blocks: N +
+ Temp Read Blocks: N +
+ Temp Written Blocks: N +
Execution Time: N.N
(1 row)
@@ -639,3 +653,41 @@ select explain_filter('explain (verbose) select * from int8_tbl i8');
Query Identifier: N
(3 rows)
+-- Test SERIALIZE option
+select explain_filter('explain (analyze,serialize) select * from int8_tbl i8');
+ explain_filter
+-----------------------------------------------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Serialization: time=N.N ms output=NkB format=text
+ Execution Time: N.N ms
+(4 rows)
+
+select explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8');
+ explain_filter
+---------------------------------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual rows=N loops=N)
+ Planning Time: N.N ms
+ Serialization: output=NkB format=text
+ Execution Time: N.N ms
+(4 rows)
+
+select explain_filter('explain (analyze,serialize binary,buffers,timing) select * from int8_tbl i8');
+ explain_filter
+-----------------------------------------------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Serialization: time=N.N ms output=NkB format=binary
+ Execution Time: N.N ms
+(4 rows)
+
+-- this tests an edge case where we have no data to return
+select explain_filter('explain (analyze,serialize) create temp table explain_temp as select * from int8_tbl i8');
+ explain_filter
+-----------------------------------------------------------------------------------------------
+ Seq Scan on int8_tbl i8 (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Serialization: time=N.N ms output=NkB format=text
+ Execution Time: N.N ms
+(4 rows)
+
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index 2274dc1b5a6..c7055f850c5 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -66,7 +66,7 @@ select explain_filter('explain (analyze) select * from int8_tbl i8');
select explain_filter('explain (analyze, verbose) select * from int8_tbl i8');
select explain_filter('explain (analyze, buffers, format text) select * from int8_tbl i8');
select explain_filter('explain (analyze, buffers, format xml) select * from int8_tbl i8');
-select explain_filter('explain (analyze, buffers, format yaml) select * from int8_tbl i8');
+select explain_filter('explain (analyze, serialize, buffers, format yaml) select * from int8_tbl i8');
select explain_filter('explain (buffers, format text) select * from int8_tbl i8');
select explain_filter('explain (buffers, format json) select * from int8_tbl i8');
@@ -162,3 +162,10 @@ select explain_filter('explain (verbose) select * from t1 where pg_temp.mysin(f1
-- Test compute_query_id
set compute_query_id = on;
select explain_filter('explain (verbose) select * from int8_tbl i8');
+
+-- Test SERIALIZE option
+select explain_filter('explain (analyze,serialize) select * from int8_tbl i8');
+select explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8');
+select explain_filter('explain (analyze,serialize binary,buffers,timing) select * from int8_tbl i8');
+-- this tests an edge case where we have no data to return
+select explain_filter('explain (analyze,serialize) create temp table explain_temp as select * from int8_tbl i8');
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fa1ede5fe7a..8d08386d65c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -713,6 +713,7 @@ ExplainForeignModify_function
ExplainForeignScan_function
ExplainFormat
ExplainOneQuery_hook_type
+ExplainSerializeOption
ExplainState
ExplainStmt
ExplainWorkersState
@@ -2536,6 +2537,8 @@ SerCommitSeqNo
SerialControl
SerialIOData
SerializableXactHandle
+SerializeDestReceiver
+SerializeMetrics
SerializedActiveRelMaps
SerializedClientConnectionInfo
SerializedRanges