From 90447a90fca3c5a32c16509a296a8a9fefaaf5f8 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent Date: Mon, 26 Feb 2024 20:17:40 +0100 Subject: [PATCH v10] Explain: Add SERIALIZE option This option integrates with TIMING, and is gated behind ANALYZE. EXPLAIN (SERIALIZE) allows analysis of the cost of actually serializing the resultset, which usually can't be tested without actually consuming the resultset on the client. As sending a resultset of gigabytes across e.g. a VPN connection can be slow and expensive, this option increases coverage of EXPLAIN and allows for further diagnostics in case of e.g. attributes that are slow to deTOAST. Future iterations may want to further instrument the deTOAST and ANALYZE infrastructure to measure counts of deTOAST operations, but that is not part of this patch. Original patch by Stepan Rutz , heavily modified by myself (Matthias van de Meent ) Reviewed-by: Tom Lane --- doc/src/sgml/ref/explain.sgml | 27 ++ src/backend/commands/explain.c | 444 +++++++++++++++++++++++++- src/backend/tcop/dest.c | 7 + src/include/commands/explain.h | 11 + src/include/tcop/dest.h | 2 + src/test/regress/expected/explain.out | 61 +++- src/test/regress/sql/explain.sql | 28 +- src/tools/pgindent/typedefs.list | 1 + 8 files changed, 576 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml index a4b6564bdb..237fbe00d1 100644 --- a/doc/src/sgml/ref/explain.sgml +++ b/doc/src/sgml/ref/explain.sgml @@ -41,6 +41,7 @@ EXPLAIN [ ( option [, ...] ) ] boolean ] GENERIC_PLAN [ boolean ] BUFFERS [ boolean ] + SERIALIZE [ { NONE | TEXT | BINARY } ] WAL [ boolean ] TIMING [ boolean ] SUMMARY [ boolean ] @@ -206,6 +207,32 @@ ROLLBACK; + + SERIALIZE + + + Specifies whether the query's results should be serialized as if the + data was sent to the client using textual or binary representations. + If the value value is NONE we don't process output + tuples, so data from TOASTed values is not accessed + and EXPLAIN timings may be unexpected. If the value is + TEXT or BINARY, + PostgreSQL will measure the size of the + would-be transmitted data after serializing the rows to + DataRow packets, using each column type's + output (for TEXT) or + send (for BINARY) functions for + serializing the column's values. When the TIMING + option is enabled, the output also includes how much time was spent to + serialize the data. + This parameter may only be used when ANALYZE is also + enabled. The default value for this parameter is NONE, + but when SERIALIZE is provided without parameters, + TEXT is used instead. + + + + WAL diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 926d70afaf..19e9805245 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" @@ -32,6 +33,7 @@ #include "utils/guc_tables.h" #include "utils/json.h" #include "utils/lsyscache.h" +#include "utils/memdebug.h" #include "utils/rel.h" #include "utils/ruleutils.h" #include "utils/snapmgr.h" @@ -46,6 +48,14 @@ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL; /* Hook for plugins to get control in explain_get_index_name() */ explain_get_index_name_hook_type explain_get_index_name_hook = NULL; +/* Instrumentation structures for EXPLAIN's SERIALIZE option */ +typedef struct ExplSerInstrumentation +{ + uint64 bytesSent; /* # of bytes serialized */ + instr_time timeSpent; /* time spent serializing */ + BufferUsage bufferUsage; /* buffers accessed for serialization */ + ExplainSerializeFormat serialize; /* serialization format */ +} ExplSerInstrumentation; /* OR-able flags for ExplainXMLTag() */ #define X_OPENING 0 @@ -59,6 +69,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, + ExplSerInstrumentation * instr); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es); static double elapsed_time(instr_time *starttime); @@ -154,7 +166,7 @@ static void ExplainJSONLineEnding(ExplainState *es); static void ExplainYAMLLineStarting(ExplainState *es); static void escape_yaml(StringInfo buf, const char *str); - +static ExplSerInstrumentation GetSerializationMetrics(DestReceiver *dest); /* * ExplainQuery - @@ -192,6 +204,35 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, es->settings = defGetBoolean(opt); else if (strcmp(opt->defname, "generic_plan") == 0) es->generic = defGetBoolean(opt); + else if (strcmp(opt->defname, "serialize") == 0) + { + /* check the optional argument, if defined */ + 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 + { + /* + * The default serialization mode when the option is specified + * is 'text'. + */ + es->serialize = EXPLAIN_SERIALIZE_TEXT; + } + } else if (strcmp(opt->defname, "timing") == 0) { timing_set = true; @@ -246,6 +287,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 +623,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, double totaltime = 0; int eflags; int instrument_option = 0; + ExplSerInstrumentation serializeMetrics = {0}; Assert(plannedstmt->commandType != CMD_UTILITY); @@ -604,11 +652,15 @@ 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, and + * when we EXPLAIN (ANALYZE, SERIALIZE) we better set up a serializing + * (but discarding) DestReceiver. */ if (into) dest = CreateIntoRelDestReceiver(into); + else if (es->analyze && es->serialize != EXPLAIN_SERIALIZE_NONE) + dest = CreateExplainSerializeDestReceiver(es); else dest = None_Receiver; @@ -647,6 +699,13 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, /* run cleanup too */ ExecutorFinish(queryDesc); + /* grab the 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); + /* We can't run ExecutorEnd 'till we're done printing the stats... */ totaltime += elapsed_time(&starttime); } @@ -700,6 +759,10 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, if (es->costs) ExplainPrintJITSummary(es, queryDesc); + /* print the info about serialization of data */ + if (es->analyze && 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). @@ -5161,3 +5224,378 @@ escape_yaml(StringInfo buf, const char *str) { escape_json(buf, str); } + + +/* + * Serializing DestReceiver functions + * + * EXPLAIN (ANALYZE) can fail to provide accurate results for some queries, + * which can usually be attributed to a lack of deTOASTing when the resultset + * isn't fully serialized, or other features usually only accessed in the + * DestReceiver functions. To measure the overhead of transferring the + * resulting dataset of a query, the SERIALIZE option is added, which can show + * and measure the relevant metrics available to a PostgreSQL server. This + * allows the measuring of server time spent on deTOASTing, serialization and + * copying of data. + * + * However, this critically does not measure the network performance: All + * measured timings are about processes inside the database. + */ + +/* an attribute info cached for each column */ +typedef struct SerializeAttrInfo +{ /* Per-attribute information */ + Oid typoutput; /* Oid for the type's text output fn */ + Oid typsend; /* Oid for the type's binary output fn */ + bool typisvarlena; /* is it varlena (ie possibly toastable)? */ + int8 format; /* text of binary, like pq wire protocol */ + FmgrInfo finfo; /* Precomputed call info for output fn */ +} SerializeAttrInfo; + +/* + * A DestReceiver for query tuples, that serializes passed rows to RowData + * messages while measuring time taken in serialization and total serialized + * size, while never sending the data to the client. + */ +typedef struct SerializeDestReceiver +{ + DestReceiver pub; + MemoryContext memoryContext; + ExplainState *es; /* this EXPLAIN-statement's ExplainState */ + int8 format; /* text or binary, like pq wire protocol */ + TupleDesc tupdesc; /* the output tuple desc */ + SerializeAttrInfo *attrInfo; /* Cached info about each attr */ + int nattrs; + StringInfoData buf; /* serialization buffer to hold the output + * data */ + ExplSerInstrumentation metrics; /* metrics */ +} SerializeDestReceiver; + +/* + * Get the lookup info that the row-callback of the receiver needs. this code + * is similar to the code from printup.c except that it doesn't do any actual + * output. + */ +static void +serialize_prepare_info(SerializeDestReceiver *receiver, TupleDesc typeinfo, + int nattrs) +{ + /* get rid of any old data */ + if (receiver->attrInfo) + pfree(receiver->attrInfo); + receiver->attrInfo = NULL; + + receiver->tupdesc = typeinfo; + receiver->nattrs = nattrs; + if (nattrs <= 0) + return; + + receiver->attrInfo = (SerializeAttrInfo *) + palloc0(nattrs * sizeof(SerializeAttrInfo)); + + for (int i = 0; i < nattrs; i++) + { + SerializeAttrInfo *info = &receiver->attrInfo[i]; + Form_pg_attribute attr = TupleDescAttr(typeinfo, i); + + info->format = receiver->format; + + if (info->format == 0) + { + /* wire protocol format text */ + getTypeOutputInfo(attr->atttypid, + &info->typoutput, + &info->typisvarlena); + fmgr_info(info->typoutput, &info->finfo); + } + else if (info->format == 1) + { + /* wire protocol format binary */ + getTypeBinaryOutputInfo(attr->atttypid, + &info->typsend, + &info->typisvarlena); + fmgr_info(info->typsend, &info->finfo); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unsupported format code: %d", info->format))); + } + } +} + + + +/* + * serializeAnalyzeReceive - process tuples for EXPLAIN (SERIALIZE) + * + * This method receives the tuples/records during EXPLAIN (ANALYZE, SERIALIZE) + * and serializes them while measuring various things about that + * serialization, in a way that should be as close as possible to printtup.c + * without actually sending the data; thus capturing the overhead of + * deTOASTing and type's out/sendfuncs, which are not otherwise exercisable + * without actually hitting the network, thus increasing the number of paths + * you can exercise with EXPLAIN. + * + * See also: printtup() in printtup.c, the older twin of this code. + */ +static bool +serializeAnalyzeReceive(TupleTableSlot *slot, DestReceiver *self) +{ + TupleDesc tupdesc; + MemoryContext oldcontext; + SerializeDestReceiver *receiver = (SerializeDestReceiver *) self; + StringInfo buf = &receiver->buf; + instr_time start, + end; + BufferUsage instr_start; + + tupdesc = slot->tts_tupleDescriptor; + + /* only measure time, buffers if requested */ + if (receiver->es->timing) + INSTR_TIME_SET_CURRENT(start); + if (receiver->es->buffers) + instr_start = pgBufferUsage; + + /* Cache attribute infos and function oid if outdated */ + if (receiver->tupdesc != tupdesc || receiver->nattrs != tupdesc->natts) + serialize_prepare_info(receiver, tupdesc, tupdesc->natts); + + /* + * Fill all the slot's attributes, we can now use slot->tts_values and its + * tts_isnull array which should be long enough even if added a + * null-column to the table + */ + slot_getallattrs(slot); + + oldcontext = MemoryContextSwitchTo(receiver->memoryContext); + + /* + * Note that we us an actual StringInfo buffer. This is to include the + * cost of memory accesses and copy operations, reducing the number of + * operations unique to the true printtup path vs the EXPLAIN (SERIALIZE) + * path. + */ + pq_beginmessage_reuse(buf, 'D'); + pq_sendint16(buf, receiver->nattrs); + + /* + * Iterate over all attributes of the tuple and invoke the output func (or + * send function in case of a binary format). We'll completely ignore the + * result. The MemoryContext is reset at the end of this per-tuple + * callback anyhow. + */ + for (int i = 0; i < tupdesc->natts; i++) + { + SerializeAttrInfo *thisState = receiver->attrInfo + i; + Datum attr = slot->tts_values[i]; + + if (slot->tts_isnull[i]) + { + pq_sendint32(buf, -1); + continue; + } + + if (thisState->format == 0) + { + /* Text output */ + char *outputstr; + + outputstr = OutputFunctionCall(&thisState->finfo, attr); + pq_sendcountedtext(buf, outputstr, strlen(outputstr)); + } + else + { + /* Binary output */ + bytea *outputbytes; + + Assert(thisState->format == 1); + + outputbytes = SendFunctionCall(&thisState->finfo, attr); + pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ); + pq_sendbytes(buf, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + } + } + + /* finalize the timers */ + if (receiver->es->timing) + { + INSTR_TIME_SET_CURRENT(end); + INSTR_TIME_ACCUM_DIFF(receiver->metrics.timeSpent, end, start); + } + + /* finalize buffer metrics */ + if (receiver->es->buffers) + BufferUsageAccumDiff(&receiver->metrics.bufferUsage, + &pgBufferUsage, + &instr_start); + + /* + * Register the size of the packet we would've sent to the client. The + * buffer will be dropped on the next iteration. + */ + receiver->metrics.bytesSent += buf->len; + + /* cleanup and reset */ + MemoryContextSwitchTo(oldcontext); + MemoryContextReset(receiver->memoryContext); + + return true; +} + +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); + elog(ERROR, "Invalid explain serialization format code %d", receiver->es->serialize); + 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; + } + + memset(&receiver->metrics, 0, sizeof(ExplSerInstrumentation)); + receiver->metrics.serialize = receiver->es->serialize; + + /* memory context for our work */ + receiver->memoryContext = AllocSetContextCreate(CurrentMemoryContext, + "SerializeTupleReceive", ALLOCSET_DEFAULT_SIZES); + + /* initialize various fields */ + INSTR_TIME_SET_ZERO(receiver->metrics.timeSpent); + initStringInfo(&receiver->buf); +} + +/* + * serializeAnalyzeShutdown - shut down the serializeAnalyze receiver + */ +static void +serializeAnalyzeShutdown(DestReceiver *self) +{ + SerializeDestReceiver *receiver = (SerializeDestReceiver *) self; + + if (receiver->attrInfo) + pfree(receiver->attrInfo); + receiver->attrInfo = NULL; + + if (receiver->buf.data) + pfree(receiver->buf.data); + receiver->buf.data = NULL; + + if (receiver->memoryContext) + MemoryContextDelete(receiver->memoryContext); + receiver->memoryContext = NULL; +} + +/* + * serializeAnalyzeShutdown - shut down the serializeAnalyze receiver + */ +static void +serializeAnalyzeDestroy(DestReceiver *self) +{ + pfree(self); +} + +/* Build a DestReceiver with 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; +} + +static ExplSerInstrumentation +GetSerializationMetrics(DestReceiver *dest) +{ + ExplSerInstrumentation empty; + + if (dest->mydest == DestExplainSerialize) + return ((SerializeDestReceiver *) dest)->metrics; + + memset(&empty, 0, sizeof(ExplSerInstrumentation)); + return empty; +} + +/* Print data for the SERIALIZE option */ +static void +ExplainPrintSerialize(ExplainState *es, ExplSerInstrumentation * instr) +{ + char *format; + + Assert(es->serialize == instr->serialize); + /* We shouldn't get called for EXPLAIN_SERIALIZE_NONE */ + if (instr->serialize == EXPLAIN_SERIALIZE_TEXT) + format = "text"; + else + { + Assert(instr->serialize == EXPLAIN_SERIALIZE_BINARY); + format = "binary"; + } + + ExplainOpenGroup("Serialization", "Serialization", true, es); + + if (es->format == EXPLAIN_FORMAT_TEXT) + { + ExplainIndentText(es); + + /* timing is optional */ + if (es->timing) + appendStringInfo(es->str, "Serialization: time=%.3f produced=" INT64_FORMAT "kB format=%s", + 1000.0 * INSTR_TIME_GET_DOUBLE(instr->timeSpent), + instr->bytesSent / 1024, + format); + else + appendStringInfo(es->str, "Serialization: produced=" INT64_FORMAT "kB format=%s", + instr->bytesSent / 1024, + format); + + appendStringInfoChar(es->str, '\n'); + + if (es->buffers && peek_buffer_usage(es, &instr->bufferUsage)) + { + es->indent++; + show_buffer_usage(es, &instr->bufferUsage); + es->indent--; + } + } + else + { + if (es->timing) + { + ExplainPropertyFloat("Time", "ms", + 1000.0 * INSTR_TIME_GET_DOUBLE(instr->timeSpent), + 3, es); + } + + ExplainPropertyUInteger("Written Bytes", "bytes", + instr->bytesSent, es); + ExplainPropertyText("Format", format, es); + show_buffer_usage(es, &instr->bufferUsage); + } + + ExplainCloseGroup("Serialization", "Serialization", true, es); +} diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index 6d727ae24f..96f80b3046 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 cf195f1359..9f2da28f93 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -25,6 +25,13 @@ typedef enum ExplainFormat EXPLAIN_FORMAT_YAML, } ExplainFormat; +typedef enum ExplainSerializeFormat +{ + EXPLAIN_SERIALIZE_NONE, + EXPLAIN_SERIALIZE_TEXT, + EXPLAIN_SERIALIZE_BINARY, +} ExplainSerializeFormat; + typedef struct ExplainWorkersState { int num_workers; /* # of worker processes the plan used */ @@ -48,6 +55,8 @@ typedef struct ExplainState bool memory; /* print planner's memory usage information */ bool settings; /* print modified settings */ bool generic; /* generate a generic plan */ + ExplainSerializeFormat serialize; /* do serialization (in ANALZYE) */ + ExplainFormat format; /* output format */ /* state for output formatting --- not reset for each new plan tree */ int indent; /* current indentation level */ @@ -132,4 +141,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 7e613bd7fc..851272a719 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -96,6 +96,8 @@ 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 only serialized, not + * transferred */ } CommandDest; /* ---------------- diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out index 1299ee79ad..bc121b0b52 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 (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 + + Written Bytes: 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,48 @@ select explain_filter('explain (verbose) select * from int8_tbl i8'); Query Identifier: N (3 rows) +-- Test that SERIALIZE is accepted as a parameter to explain +-- timings are filtered out by explain_filter +create table test_serialize(id bigserial, val text); +select explain_filter('explain (analyze,serialize) select * from test_serialize'); + explain_filter +-------------------------------------------------------------------------------------------------- + Seq Scan on test_serialize (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 produced=NkB format=text + Execution Time: N.N ms +(4 rows) + +drop table test_serialize; +-- Test that SERIALIZE BINARY is accepted as a parameter to explain +create table test_serialize(id bigserial, val text); +select explain_filter('explain (analyze,serialize binary,buffers) select * from test_serialize'); + explain_filter +-------------------------------------------------------------------------------------------------- + Seq Scan on test_serialize (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 produced=NkB format=binary + Execution Time: N.N ms +(4 rows) + +drop table test_serialize; +-- Test that _SERIALIZE invalidparameter_ is not accepted as a parameter to explain +create table test_serialize(id bigserial, val text); +select explain_filter('explain (analyze,serialize invalidparameter) select * from test_serialize'); +ERROR: unrecognized value for EXPLAIN option "serialize": "invalidparameter" +LINE 1: select explain_filter('explain (analyze,serialize invalidpar... + ^ +CONTEXT: PL/pgSQL function explain_filter(text) line 5 at FOR over EXECUTE statement +drop table test_serialize; +-- Test SERIALIZE is _not_ accepted as a parameter to explain unless ANALYZE is specified +create table test_serialize(id bigserial, val text); +select explain_filter('explain (serialize) select * from test_serialize'); +ERROR: EXPLAIN option SERIALIZE requires ANALYZE +CONTEXT: PL/pgSQL function explain_filter(text) line 5 at FOR over EXECUTE statement +drop table test_serialize; +-- Test SERIALIZEBINARY is _not_ accepted as a parameter to explain unless ANALYZE is specified +create table test_serialize(id bigserial, val text); +select explain_filter('explain (serialize binary) select * from test_serialize'); +ERROR: EXPLAIN option SERIALIZE requires ANALYZE +CONTEXT: PL/pgSQL function explain_filter(text) line 5 at FOR over EXECUTE statement +drop table test_serialize; diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql index 2274dc1b5a..76e441f258 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,29 @@ 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 that SERIALIZE is accepted as a parameter to explain +-- timings are filtered out by explain_filter +create table test_serialize(id bigserial, val text); +select explain_filter('explain (analyze,serialize) select * from test_serialize'); +drop table test_serialize; + +-- Test that SERIALIZE BINARY is accepted as a parameter to explain +create table test_serialize(id bigserial, val text); +select explain_filter('explain (analyze,serialize binary,buffers) select * from test_serialize'); +drop table test_serialize; + +-- Test that _SERIALIZE invalidparameter_ is not accepted as a parameter to explain +create table test_serialize(id bigserial, val text); +select explain_filter('explain (analyze,serialize invalidparameter) select * from test_serialize'); +drop table test_serialize; + +-- Test SERIALIZE is _not_ accepted as a parameter to explain unless ANALYZE is specified +create table test_serialize(id bigserial, val text); +select explain_filter('explain (serialize) select * from test_serialize'); +drop table test_serialize; + +-- Test SERIALIZEBINARY is _not_ accepted as a parameter to explain unless ANALYZE is specified +create table test_serialize(id bigserial, val text); +select explain_filter('explain (serialize binary) select * from test_serialize'); +drop table test_serialize; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 79745ba913..873eaaa58a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2532,6 +2532,7 @@ SerCommitSeqNo SerialControl SerialIOData SerializableXactHandle +SerializeDestReceiver SerializedActiveRelMaps SerializedClientConnectionInfo SerializedRanges -- 2.40.1