Make it possible for loadable modules to add EXPLAIN options.
authorRobert Haas <rhaas@postgresql.org>
Tue, 18 Mar 2025 12:41:12 +0000 (08:41 -0400)
committerRobert Haas <rhaas@postgresql.org>
Tue, 18 Mar 2025 12:41:12 +0000 (08:41 -0400)
Modules can use RegisterExtensionExplainOption to register new
EXPLAIN options, and GetExplainExtensionId, GetExplainExtensionState,
and SetExplainExtensionState to store related state inside the
ExplainState object.

Since this substantially increases the amount of code that needs
to handle ExplainState-related tasks, move a few bits of existing
code to a new file explain_state.c and add the rest of this
infrastructure there.

See the comments at the top of explain_state.c for further
explanation of how this mechanism works.

This does not yet provide a way for such such options to do anything
useful. The intention is that we'll add hooks for that purpose in a
separate commit.

Discussion: http://postgr.es/m/CA+TgmoYSzg58hPuBmei46o8D3SKX+SZoO4K_aGQGwiRzvRApLg@mail.gmail.com
Reviewed-by: Srinath Reddy <srinath2133@gmail.com>
Reviewed-by: Andrei Lepikhov <lepihov@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
18 files changed:
contrib/auto_explain/auto_explain.c
contrib/file_fdw/file_fdw.c
contrib/postgres_fdw/postgres_fdw.c
src/backend/commands/Makefile
src/backend/commands/createas.c
src/backend/commands/explain.c
src/backend/commands/explain_dr.c
src/backend/commands/explain_format.c
src/backend/commands/explain_state.c [new file with mode: 0644]
src/backend/commands/meson.build
src/backend/commands/prepare.c
src/backend/executor/execAmi.c
src/backend/tcop/pquery.c
src/include/commands/explain.h
src/include/commands/explain_state.h [new file with mode: 0644]
src/include/commands/prepare.h
src/include/nodes/extensible.h
src/tools/pgindent/typedefs.list

index 7007a226c08ca37251cfdd44164185c13d4cfb37..3b73bd191079454b576c74bb719392a0e8d8b871 100644 (file)
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "commands/explain.h"
 #include "commands/explain_format.h"
+#include "commands/explain_state.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
 #include "utils/guc.h"
index bf707c812ede2e5fc85343620a495d87de98ac87..56ececac70b88ee9809e5d38060963d451bb7b23 100644 (file)
 #include "commands/copy.h"
 #include "commands/copyfrom_internal.h"
 #include "commands/defrem.h"
-#include "commands/explain.h"
 #include "commands/explain_format.h"
+#include "commands/explain_state.h"
 #include "commands/vacuum.h"
+#include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
index 1131a8bf77ec255d5f48c0b10ebfb6d6fa964aa6..263c879026e5d44a8a778d0c844fa82b6ff7cd46 100644 (file)
@@ -19,8 +19,8 @@
 #include "access/table.h"
 #include "catalog/pg_opfamily.h"
 #include "commands/defrem.h"
-#include "commands/explain.h"
 #include "commands/explain_format.h"
+#include "commands/explain_state.h"
 #include "executor/execAsync.h"
 #include "foreign/fdwapi.h"
 #include "funcapi.h"
index 85cfea6fd713298a3e495798a77f6e94769a99b7..cb2fbdc7c6018baf19cb99fb7913d51851f31777 100644 (file)
@@ -36,6 +36,7 @@ OBJS = \
    explain.o \
    explain_dr.o \
    explain_format.o \
+   explain_state.o \
    extension.o \
    foreigncmds.o \
    functioncmds.o \
index 44b4665ccd34d04d4d439879e066aafad0ccadc7..0a4155773eb5f87cd8fd6656e219e93bdfb25998 100644 (file)
@@ -35,6 +35,8 @@
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
 #include "commands/view.h"
+#include "executor/execdesc.h"
+#include "executor/executor.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/queryjumble.h"
index 19ffcc2cacb429466288e8f09033c7d499ce8633..ab3898ff1eb242ab6c291946d14067ecd2e1327e 100644 (file)
 #include "catalog/pg_type.h"
 #include "commands/createas.h"
 #include "commands/defrem.h"
+#include "commands/explain.h"
 #include "commands/explain_dr.h"
 #include "commands/explain_format.h"
+#include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
@@ -176,130 +178,11 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
    JumbleState *jstate = NULL;
    Query      *query;
    List       *rewritten;
-   ListCell   *lc;
-   bool        timing_set = false;
-   bool        buffers_set = false;
-   bool        summary_set = false;
-
-   /* Parse options list. */
-   foreach(lc, stmt->options)
-   {
-       DefElem    *opt = (DefElem *) lfirst(lc);
 
-       if (strcmp(opt->defname, "analyze") == 0)
-           es->analyze = defGetBoolean(opt);
-       else if (strcmp(opt->defname, "verbose") == 0)
-           es->verbose = defGetBoolean(opt);
-       else if (strcmp(opt->defname, "costs") == 0)
-           es->costs = defGetBoolean(opt);
-       else if (strcmp(opt->defname, "buffers") == 0)
-       {
-           buffers_set = true;
-           es->buffers = defGetBoolean(opt);
-       }
-       else if (strcmp(opt->defname, "wal") == 0)
-           es->wal = defGetBoolean(opt);
-       else if (strcmp(opt->defname, "settings") == 0)
-           es->settings = defGetBoolean(opt);
-       else if (strcmp(opt->defname, "generic_plan") == 0)
-           es->generic = defGetBoolean(opt);
-       else if (strcmp(opt->defname, "timing") == 0)
-       {
-           timing_set = true;
-           es->timing = defGetBoolean(opt);
-       }
-       else if (strcmp(opt->defname, "summary") == 0)
-       {
-           summary_set = true;
-           es->summary = defGetBoolean(opt);
-       }
-       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);
-
-           if (strcmp(p, "text") == 0)
-               es->format = EXPLAIN_FORMAT_TEXT;
-           else if (strcmp(p, "xml") == 0)
-               es->format = EXPLAIN_FORMAT_XML;
-           else if (strcmp(p, "json") == 0)
-               es->format = EXPLAIN_FORMAT_JSON;
-           else if (strcmp(p, "yaml") == 0)
-               es->format = EXPLAIN_FORMAT_YAML;
-           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
-           ereport(ERROR,
-                   (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("unrecognized EXPLAIN option \"%s\"",
-                           opt->defname),
-                    parser_errposition(pstate, opt->location)));
-   }
-
-   /* check that WAL is used with EXPLAIN ANALYZE */
-   if (es->wal && !es->analyze)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("EXPLAIN option %s requires ANALYZE", "WAL")));
-
-   /* if the timing was not set explicitly, set default value */
-   es->timing = (timing_set) ? es->timing : es->analyze;
-
-   /* if the buffers was not set explicitly, set default value */
-   es->buffers = (buffers_set) ? es->buffers : es->analyze;
-
-   /* check that timing is used with EXPLAIN ANALYZE */
-   if (es->timing && !es->analyze)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
-
-   /* 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 %s requires ANALYZE", "SERIALIZE")));
-
-   /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
-   if (es->generic && es->analyze)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together")));
-
-   /* if the summary was not set explicitly, set default value */
-   es->summary = (summary_set) ? es->summary : es->analyze;
+   /* Configure the ExplainState based on the provided options */
+   ParseExplainOptionList(es, stmt->options, pstate);
 
+   /* Extract the query and, if enabled, jumble it */
    query = castNode(Query, stmt->query);
    if (IsQueryIdEnabled())
        jstate = JumbleQuery(query);
@@ -360,22 +243,6 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
    pfree(es->str->data);
 }
 
-/*
- * Create a new ExplainState struct initialized with default options.
- */
-ExplainState *
-NewExplainState(void)
-{
-   ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
-
-   /* Set default options (most fields can be left as zeroes). */
-   es->costs = true;
-   /* Prepare output buffer. */
-   es->str = makeStringInfo();
-
-   return es;
-}
-
 /*
  * ExplainResultDesc -
  *   construct the result tupledesc for an EXPLAIN
index fb42bee6e723698f135fb824d1dd53c4510152a0..5715546cf437be1490d884ed79cbb52ff50d2c3b 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "commands/explain.h"
 #include "commands/explain_dr.h"
+#include "commands/explain_state.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "utils/lsyscache.h"
index bccdd76a87483235640e6a65ebc790ab07482a77..752691d56dbc748ca3ee1b94d772ba9b65ca1e8a 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "commands/explain.h"
 #include "commands/explain_format.h"
+#include "commands/explain_state.h"
 #include "utils/json.h"
 #include "utils/xml.h"
 
diff --git a/src/backend/commands/explain_state.c b/src/backend/commands/explain_state.c
new file mode 100644 (file)
index 0000000..1d4be3c
--- /dev/null
@@ -0,0 +1,371 @@
+/*-------------------------------------------------------------------------
+ *
+ * explain_state.c
+ *   Code for initializing and accessing ExplainState objects
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * In-core options have hard-coded fields inside ExplainState; e.g. if
+ * the user writes EXPLAIN (BUFFERS) then ExplainState's "buffers" member
+ * will be set to true. Extensions can also register options using
+ * RegisterExtensionExplainOption; so that e.g. EXPLAIN (BICYCLE 'red')
+ * will invoke a designated handler that knows what the legal values are
+ * for the BICYCLE option. However, it's not enough for an extension to be
+ * able to parse new options: it also needs a place to store the results
+ * of that parsing, and an ExplainState has no 'bicycle' field.
+ *
+ * To solve this problem, an ExplainState can contain an array of opaque
+ * pointers, one per extension. An extension can use GetExplainExtensionId
+ * to acquire an integer ID to acquire an offset into this array that is
+ * reserved for its exclusive use, and then use GetExplainExtensionState
+ * and SetExplainExtensionState to read and write its own private state
+ * within an ExplainState.
+ *
+ * Note that there is no requirement that the name of the option match
+ * the name of the extension; e.g. a pg_explain_conveyance extension could
+ * implement options for BICYCLE, MONORAIL, etc.
+ *
+ * IDENTIFICATION
+ *   src/backend/commands/explain_state.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "commands/explain_state.h"
+
+typedef struct
+{
+   const char *option_name;
+   ExplainOptionHandler option_handler;
+} ExplainExtensionOption;
+
+static const char **ExplainExtensionNameArray = NULL;
+static int ExplainExtensionNamesAssigned = 0;
+static int ExplainExtensionNamesAllocated = 0;
+
+static ExplainExtensionOption *ExplainExtensionOptionArray = NULL;
+static int ExplainExtensionOptionsAssigned = 0;
+static int ExplainExtensionOptionsAllocated = 0;
+
+/*
+ * Create a new ExplainState struct initialized with default options.
+ */
+ExplainState *
+NewExplainState(void)
+{
+   ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
+
+   /* Set default options (most fields can be left as zeroes). */
+   es->costs = true;
+   /* Prepare output buffer. */
+   es->str = makeStringInfo();
+
+   return es;
+}
+
+/*
+ * Parse a list of EXPLAIN options and update an ExplainState accordingly.
+ */
+void
+ParseExplainOptionList(ExplainState *es, List *options, ParseState *pstate)
+{
+   ListCell   *lc;
+   bool        timing_set = false;
+   bool        buffers_set = false;
+   bool        summary_set = false;
+
+   /* Parse options list. */
+   foreach(lc, options)
+   {
+       DefElem    *opt = (DefElem *) lfirst(lc);
+
+       if (strcmp(opt->defname, "analyze") == 0)
+           es->analyze = defGetBoolean(opt);
+       else if (strcmp(opt->defname, "verbose") == 0)
+           es->verbose = defGetBoolean(opt);
+       else if (strcmp(opt->defname, "costs") == 0)
+           es->costs = defGetBoolean(opt);
+       else if (strcmp(opt->defname, "buffers") == 0)
+       {
+           buffers_set = true;
+           es->buffers = defGetBoolean(opt);
+       }
+       else if (strcmp(opt->defname, "wal") == 0)
+           es->wal = defGetBoolean(opt);
+       else if (strcmp(opt->defname, "settings") == 0)
+           es->settings = defGetBoolean(opt);
+       else if (strcmp(opt->defname, "generic_plan") == 0)
+           es->generic = defGetBoolean(opt);
+       else if (strcmp(opt->defname, "timing") == 0)
+       {
+           timing_set = true;
+           es->timing = defGetBoolean(opt);
+       }
+       else if (strcmp(opt->defname, "summary") == 0)
+       {
+           summary_set = true;
+           es->summary = defGetBoolean(opt);
+       }
+       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);
+
+           if (strcmp(p, "text") == 0)
+               es->format = EXPLAIN_FORMAT_TEXT;
+           else if (strcmp(p, "xml") == 0)
+               es->format = EXPLAIN_FORMAT_XML;
+           else if (strcmp(p, "json") == 0)
+               es->format = EXPLAIN_FORMAT_JSON;
+           else if (strcmp(p, "yaml") == 0)
+               es->format = EXPLAIN_FORMAT_YAML;
+           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 if (!ApplyExtensionExplainOption(es, opt, pstate))
+           ereport(ERROR,
+                   (errcode(ERRCODE_SYNTAX_ERROR),
+                    errmsg("unrecognized EXPLAIN option \"%s\"",
+                           opt->defname),
+                    parser_errposition(pstate, opt->location)));
+   }
+
+   /* check that WAL is used with EXPLAIN ANALYZE */
+   if (es->wal && !es->analyze)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("EXPLAIN option %s requires ANALYZE", "WAL")));
+
+   /* if the timing was not set explicitly, set default value */
+   es->timing = (timing_set) ? es->timing : es->analyze;
+
+   /* if the buffers was not set explicitly, set default value */
+   es->buffers = (buffers_set) ? es->buffers : es->analyze;
+
+   /* check that timing is used with EXPLAIN ANALYZE */
+   if (es->timing && !es->analyze)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("EXPLAIN option %s requires ANALYZE", "TIMING")));
+
+   /* 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 %s requires ANALYZE", "SERIALIZE")));
+
+   /* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
+   if (es->generic && es->analyze)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("EXPLAIN options ANALYZE and GENERIC_PLAN cannot be used together")));
+
+   /* if the summary was not set explicitly, set default value */
+   es->summary = (summary_set) ? es->summary : es->analyze;
+}
+
+/*
+ * Map the name of an EXPLAIN extension to an integer ID.
+ *
+ * Within the lifetime of a particular backend, the same name will be mapped
+ * to the same ID every time. IDs are not stable across backends. Use the ID
+ * that you get from this function to call GetExplainExtensionState and
+ * SetExplainExtensionState.
+ *
+ * extension_name is assumed to be a constant string or allocated in storage
+ * that will never be freed.
+ */
+int
+GetExplainExtensionId(const char *extension_name)
+{
+   /* Search for an existing extension by this name; if found, return ID. */
+   for (int i = 0; i < ExplainExtensionNamesAssigned; ++i)
+       if (strcmp(ExplainExtensionNameArray[i], extension_name) == 0)
+           return i;
+
+   /* If there is no array yet, create one. */
+   if (ExplainExtensionNameArray == NULL)
+   {
+       ExplainExtensionNamesAllocated = 16;
+       ExplainExtensionNameArray = (const char **)
+           MemoryContextAlloc(TopMemoryContext,
+                              ExplainExtensionNamesAllocated
+                              * sizeof(char *));
+   }
+
+   /* If there's an array but it's currently full, expand it. */
+   if (ExplainExtensionNamesAssigned >= ExplainExtensionNamesAllocated)
+   {
+       int         i = pg_nextpower2_32(ExplainExtensionNamesAssigned + 1);
+
+       ExplainExtensionNameArray = (const char **)
+           repalloc(ExplainExtensionNameArray, i * sizeof(char *));
+       ExplainExtensionNamesAllocated = i;
+   }
+
+   /* Assign and return new ID. */
+   ExplainExtensionNameArray[ExplainExtensionNamesAssigned] = extension_name;
+   return ExplainExtensionNamesAssigned++;
+}
+
+/*
+ * Get extension-specific state from an ExplainState.
+ *
+ * See comments for SetExplainExtensionState, below.
+ */
+void *
+GetExplainExtensionState(ExplainState *es, int extension_id)
+{
+   Assert(extension_id >= 0);
+
+   if (extension_id >= es->extension_state_allocated)
+       return NULL;
+
+   return es->extension_state[extension_id];
+}
+
+/*
+ * Store extension-specific state into an ExplainState.
+ *
+ * To use this function, first obtain an integer extension_id using
+ * GetExplainExtensionId. Then use this function to store an opaque pointer
+ * in the ExplainState. Later, you can retrieve the opaque pointer using
+ * GetExplainExtensionState.
+ */
+void
+SetExplainExtensionState(ExplainState *es, int extension_id, void *opaque)
+{
+   Assert(extension_id >= 0);
+
+   /* If there is no array yet, create one. */
+   if (es->extension_state == NULL)
+   {
+       es->extension_state_allocated = 16;
+       es->extension_state =
+           palloc0(es->extension_state_allocated * sizeof(void *));
+   }
+
+   /* If there's an array but it's currently full, expand it. */
+   if (extension_id >= es->extension_state_allocated)
+   {
+       int         i;
+
+       i = pg_nextpower2_32(es->extension_state_allocated + 1);
+       es->extension_state = (void **)
+           repalloc0(es->extension_state,
+                     es->extension_state_allocated * sizeof(void *),
+                     i * sizeof(void *));
+       es->extension_state_allocated = i;
+   }
+
+   es->extension_state[extension_id] = opaque;
+}
+
+/*
+ * Register a new EXPLAIN option.
+ *
+ * When option_name is used as an EXPLAIN option, handler will be called and
+ * should update the ExplainState passed to it. See comments at top of file
+ * for a more detailed explanation.
+ *
+ * option_name is assumed to be a constant string or allocated in storage
+ * that will never be freed.
+ */
+void
+RegisterExtensionExplainOption(const char *option_name,
+                              ExplainOptionHandler handler)
+{
+   ExplainExtensionOption *exopt;
+
+   /* Search for an existing option by this name; if found, update handler. */
+   for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
+   {
+       if (strcmp(ExplainExtensionOptionArray[i].option_name,
+                  option_name) == 0)
+       {
+           ExplainExtensionOptionArray[i].option_handler = handler;
+           return;
+       }
+   }
+
+   /* If there is no array yet, create one. */
+   if (ExplainExtensionOptionArray == NULL)
+   {
+       ExplainExtensionOptionsAllocated = 16;
+       ExplainExtensionOptionArray = (ExplainExtensionOption *)
+           MemoryContextAlloc(TopMemoryContext,
+                              ExplainExtensionOptionsAllocated
+                              * sizeof(char *));
+   }
+
+   /* If there's an array but it's currently full, expand it. */
+   if (ExplainExtensionOptionsAssigned >= ExplainExtensionOptionsAllocated)
+   {
+       int         i = pg_nextpower2_32(ExplainExtensionOptionsAssigned + 1);
+
+       ExplainExtensionOptionArray = (ExplainExtensionOption *)
+           repalloc(ExplainExtensionOptionArray, i * sizeof(char *));
+       ExplainExtensionOptionsAllocated = i;
+   }
+
+   /* Assign and return new ID. */
+   exopt = &ExplainExtensionOptionArray[ExplainExtensionOptionsAssigned++];
+   exopt->option_name = option_name;
+   exopt->option_handler = handler;
+}
+
+/*
+ * Apply an EXPLAIN option registered by an extension.
+ *
+ * If no extension has registered the named option, returns false. Otherwise,
+ * calls the appropriate handler function and then returns true.
+ */
+bool
+ApplyExtensionExplainOption(ExplainState *es, DefElem *opt, ParseState *pstate)
+{
+   for (int i = 0; i < ExplainExtensionOptionsAssigned; ++i)
+   {
+       if (strcmp(ExplainExtensionOptionArray[i].option_name,
+                  opt->defname) == 0)
+       {
+           ExplainExtensionOptionArray[i].option_handler(es, opt, pstate);
+           return true;
+       }
+   }
+
+   return false;
+}
index ce8d1ab8bace059bc204682098de7c2afa2cb121..dd4cde41d32ccfbdcd6de1b89c721e601a17f392 100644 (file)
@@ -24,6 +24,7 @@ backend_sources += files(
   'explain.c',
   'explain_dr.c',
   'explain_format.c',
+  'explain_state.c',
   'extension.c',
   'foreigncmds.c',
   'functioncmds.c',
index 4d68d4d25c76e819743c190c27584ca88d75548a..bf7d2b2309fc2122e1290fce0b4a3c3b2d00bdc5 100644 (file)
@@ -21,7 +21,9 @@
 #include "access/xact.h"
 #include "catalog/pg_type.h"
 #include "commands/createas.h"
+#include "commands/explain.h"
 #include "commands/explain_format.h"
+#include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
index db52519455420eba7020b9c95d415493a05604a5..1d0e8ad57b4a0954d8a3754ededb2bce12794c46 100644 (file)
@@ -15,6 +15,7 @@
 #include "access/amapi.h"
 #include "access/htup_details.h"
 #include "catalog/pg_class.h"
+#include "executor/executor.h"
 #include "executor/nodeAgg.h"
 #include "executor/nodeAppend.h"
 #include "executor/nodeBitmapAnd.h"
index dea24453a6c2ae93ea1786ae975bec64eaaa64b1..8164d0fbb4f6974370a5a31c8adb4817e0fe0ce7 100644 (file)
@@ -20,6 +20,7 @@
 #include "access/xact.h"
 #include "commands/prepare.h"
 #include "executor/execdesc.h"
+#include "executor/executor.h"
 #include "executor/tstoreReceiver.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
index 64547bd9b9caaec4fc46a44182830933a069b17a..783f67b468a1b0089c8b9e2f6ab334998ad71536 100644 (file)
 #define EXPLAIN_H
 
 #include "executor/executor.h"
-#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,
-   EXPLAIN_FORMAT_XML,
-   EXPLAIN_FORMAT_JSON,
-   EXPLAIN_FORMAT_YAML,
-} ExplainFormat;
-
-typedef struct ExplainWorkersState
-{
-   int         num_workers;    /* # of worker processes the plan used */
-   bool       *worker_inited;  /* per-worker state-initialized flags */
-   StringInfoData *worker_str; /* per-worker transient output buffers */
-   int        *worker_state_save;  /* per-worker grouping state save areas */
-   StringInfo  prev_str;       /* saved output buffer while redirecting */
-} ExplainWorkersState;
-
-typedef struct ExplainState
-{
-   StringInfo  str;            /* output buffer */
-   /* options */
-   bool        verbose;        /* be verbose */
-   bool        analyze;        /* print actual times */
-   bool        costs;          /* print estimated costs */
-   bool        buffers;        /* print buffer usage */
-   bool        wal;            /* print WAL usage */
-   bool        timing;         /* print detailed node timing */
-   bool        summary;        /* print total planning and execution timing */
-   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 */
-   List       *grouping_stack; /* format-specific grouping state */
-   /* state related to the current plan tree (filled by ExplainPrintPlan) */
-   PlannedStmt *pstmt;         /* top of plan */
-   List       *rtable;         /* range table */
-   List       *rtable_names;   /* alias names for RTEs */
-   List       *deparse_cxt;    /* context list for deparsing expressions */
-   Bitmapset  *printed_subplans;   /* ids of SubPlans we've printed */
-   bool        hide_workers;   /* set if we find an invisible Gather */
-   int         rtable_size;    /* length of rtable excluding the RTE_GROUP
-                                * entry */
-   /* state related to the current plan node */
-   ExplainWorkersState *workers_state; /* needed if parallel plan */
-} ExplainState;
+struct ExplainState;   /* defined in explain_state.h */
 
 /* Hook for plugins to get control in ExplainOneQuery() */
 typedef void (*ExplainOneQuery_hook_type) (Query *query,
                                           int cursorOptions,
                                           IntoClause *into,
-                                          ExplainState *es,
+                                          struct ExplainState *es,
                                           const char *queryString,
                                           ParamListInfo params,
                                           QueryEnvironment *queryEnv);
@@ -91,33 +36,34 @@ extern PGDLLIMPORT explain_get_index_name_hook_type explain_get_index_name_hook;
 extern void ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
                         ParamListInfo params, DestReceiver *dest);
 extern void standard_ExplainOneQuery(Query *query, int cursorOptions,
-                                    IntoClause *into, ExplainState *es,
+                                    IntoClause *into, struct ExplainState *es,
                                     const char *queryString, ParamListInfo params,
                                     QueryEnvironment *queryEnv);
 
-extern ExplainState *NewExplainState(void);
-
 extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
 
 extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
-                             ExplainState *es, ParseState *pstate,
+                             struct ExplainState *es, ParseState *pstate,
                              ParamListInfo params);
 
 extern void ExplainOnePlan(PlannedStmt *plannedstmt, CachedPlan *cplan,
                           CachedPlanSource *plansource, int plan_index,
-                          IntoClause *into, ExplainState *es,
+                          IntoClause *into, struct ExplainState *es,
                           const char *queryString,
                           ParamListInfo params, QueryEnvironment *queryEnv,
                           const instr_time *planduration,
                           const BufferUsage *bufusage,
                           const MemoryContextCounters *mem_counters);
 
-extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
-extern void ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc);
+extern void ExplainPrintPlan(struct ExplainState *es, QueryDesc *queryDesc);
+extern void ExplainPrintTriggers(struct ExplainState *es,
+                                QueryDesc *queryDesc);
 
-extern void ExplainPrintJITSummary(ExplainState *es, QueryDesc *queryDesc);
+extern void ExplainPrintJITSummary(struct ExplainState *es,
+                                  QueryDesc *queryDesc);
 
-extern void ExplainQueryText(ExplainState *es, QueryDesc *queryDesc);
-extern void ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen);
+extern void ExplainQueryText(struct ExplainState *es, QueryDesc *queryDesc);
+extern void ExplainQueryParameters(struct ExplainState *es,
+                                  ParamListInfo params, int maxlen);
 
 #endif                         /* EXPLAIN_H */
diff --git a/src/include/commands/explain_state.h b/src/include/commands/explain_state.h
new file mode 100644 (file)
index 0000000..9250974
--- /dev/null
@@ -0,0 +1,95 @@
+/*-------------------------------------------------------------------------
+ *
+ * explain_state.h
+ *   prototypes for explain_state.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_state.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXPLAIN_STATE_H
+#define EXPLAIN_STATE_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/plannodes.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,
+   EXPLAIN_FORMAT_XML,
+   EXPLAIN_FORMAT_JSON,
+   EXPLAIN_FORMAT_YAML,
+} ExplainFormat;
+
+typedef struct ExplainWorkersState
+{
+   int         num_workers;    /* # of worker processes the plan used */
+   bool       *worker_inited;  /* per-worker state-initialized flags */
+   StringInfoData *worker_str; /* per-worker transient output buffers */
+   int        *worker_state_save;  /* per-worker grouping state save areas */
+   StringInfo  prev_str;       /* saved output buffer while redirecting */
+} ExplainWorkersState;
+
+typedef struct ExplainState
+{
+   StringInfo  str;            /* output buffer */
+   /* options */
+   bool        verbose;        /* be verbose */
+   bool        analyze;        /* print actual times */
+   bool        costs;          /* print estimated costs */
+   bool        buffers;        /* print buffer usage */
+   bool        wal;            /* print WAL usage */
+   bool        timing;         /* print detailed node timing */
+   bool        summary;        /* print total planning and execution timing */
+   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 */
+   List       *grouping_stack; /* format-specific grouping state */
+   /* state related to the current plan tree (filled by ExplainPrintPlan) */
+   PlannedStmt *pstmt;         /* top of plan */
+   List       *rtable;         /* range table */
+   List       *rtable_names;   /* alias names for RTEs */
+   List       *deparse_cxt;    /* context list for deparsing expressions */
+   Bitmapset  *printed_subplans;   /* ids of SubPlans we've printed */
+   bool        hide_workers;   /* set if we find an invisible Gather */
+   int         rtable_size;    /* length of rtable excluding the RTE_GROUP
+                                * entry */
+   /* state related to the current plan node */
+   ExplainWorkersState *workers_state; /* needed if parallel plan */
+   /* extensions */
+   void      **extension_state;
+   int         extension_state_allocated;
+} ExplainState;
+
+typedef void (*ExplainOptionHandler) (ExplainState *, DefElem *, ParseState *);
+
+extern ExplainState *NewExplainState(void);
+extern void ParseExplainOptionList(ExplainState *es, List *options,
+                                  ParseState *pstate);
+
+extern int GetExplainExtensionId(const char *extension_name);
+extern void *GetExplainExtensionState(ExplainState *es, int extension_id);
+extern void SetExplainExtensionState(ExplainState *es, int extension_id,
+                                    void *opaque);
+
+extern void RegisterExtensionExplainOption(const char *option_name,
+                                          ExplainOptionHandler handler);
+extern bool ApplyExtensionExplainOption(ExplainState *es, DefElem *opt,
+                                       ParseState *pstate);
+
+#endif                         /* EXPLAIN_STATE_H */
index b9533f1af841284972e54381304c4cd264daff18..08daac8c92665c793263cbee1295f934552e448a 100644 (file)
@@ -13,8 +13,9 @@
 #ifndef PREPARE_H
 #define PREPARE_H
 
-#include "commands/explain.h"
+#include "commands/explain_state.h"
 #include "datatype/timestamp.h"
+#include "tcop/dest.h"
 #include "utils/plancache.h"
 
 /*
index 552ce19bdd53a96e9a0cde2df90344fcf405da91..1129c4ba4b1b4296f8556993eebf2e579f9270d2 100644 (file)
@@ -15,7 +15,7 @@
 #define EXTENSIBLE_H
 
 #include "access/parallel.h"
-#include "commands/explain.h"
+#include "commands/explain_state.h"
 #include "nodes/execnodes.h"
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
index f4261145353c52b37f31806c94313fe13b42b745..c04a47cf222b8dfb865ae2b4800842e254dc40c9 100644 (file)
@@ -4271,3 +4271,5 @@ yyscan_t
 z_stream
 z_streamp
 zic_t
+ExplainExtensionOption
+ExplainOptionHandler