diff options
-rw-r--r-- | doc/src/sgml/perform.sgml | 24 | ||||
-rw-r--r-- | doc/src/sgml/ref/explain.sgml | 29 | ||||
-rw-r--r-- | src/backend/access/common/printtup.c | 5 | ||||
-rw-r--r-- | src/backend/commands/explain.c | 411 | ||||
-rw-r--r-- | src/backend/tcop/dest.c | 7 | ||||
-rw-r--r-- | src/include/commands/explain.h | 10 | ||||
-rw-r--r-- | src/include/tcop/dest.h | 1 | ||||
-rw-r--r-- | src/test/regress/expected/explain.out | 54 | ||||
-rw-r--r-- | src/test/regress/sql/explain.sql | 9 | ||||
-rw-r--r-- | src/tools/pgindent/typedefs.list | 3 |
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 |