diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index cd6625020a73..e720ddf39f8f 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -15,6 +15,7 @@ #include #include "access/parallel.h" +#include "commands/dynamic_explain.h" #include "commands/explain.h" #include "commands/explain_format.h" #include "commands/explain_state.h" @@ -412,26 +413,9 @@ explain_ExecutorEnd(QueryDesc *queryDesc) es->format = auto_explain_log_format; es->settings = auto_explain_log_settings; - ExplainBeginOutput(es); - ExplainQueryText(es, queryDesc); - ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length); - ExplainPrintPlan(es, queryDesc); - if (es->analyze && auto_explain_log_triggers) - ExplainPrintTriggers(es, queryDesc); - if (es->costs) - ExplainPrintJITSummary(es, queryDesc); - ExplainEndOutput(es); - - /* Remove last line break */ - if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n') - es->str->data[--es->str->len] = '\0'; - - /* Fix JSON to output an object */ - if (auto_explain_log_format == EXPLAIN_FORMAT_JSON) - { - es->str->data[0] = '{'; - es->str->data[es->str->len - 1] = '}'; - } + ExplainStringAssemble(es, queryDesc, auto_explain_log_format, + auto_explain_log_triggers, + auto_explain_log_parameter_max_length); /* * Note: we rely on the existing logging of context or diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index af3d056b9923..1e33988eb8e4 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28821,6 +28821,29 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} + + + + pg_log_query_plan + + pg_log_query_plan ( pid integer ) + boolean + + + Requests to log the plan of the query currently running on the + backend with specified process ID. + It will be logged at LOG message level and + will appear in the server log based on the log + configuration set (See + for more information), but will not be sent to the client + regardless of . + + + This function is restricted to superusers by default, but other + users can be granted EXECUTE to run the function. + + + @@ -28971,6 +28994,38 @@ stats_timestamp | 2025-03-24 13:55:47.796698+01 will be less resource intensive when only the local backend is of interest. + + + + pg_log_query_plan can be used + to log the plan of a backend process. For example: + +postgres=# SELECT pg_log_query_plan(201116); + pg_log_query_plan +--------------------------- + t +(1 row) + +The format of the query plan is the same as when VERBOSE, +COSTS, SETTINGS and +FORMAT TEXT are used in the EXPLAIN +command. For example: + +LOG: query plan running on backend with PID 201116 is: + Query Text: SELECT * FROM pgbench_accounts; + Seq Scan on public.pgbench_accounts (cost=0.00..52787.00 rows=2000000 width=97) + Output: aid, bid, abalance, filler + Settings: work_mem = '1MB' + Query Identifier: 8621255546560739680 + + Note that when the target is executing nested statements(statements executed + inside a function), only the innermost query plan is logged. + Note that logging plan may take some time, as it occurs when + the plan node is executed. For example, when a query is + running pg_sleep, the plan will not be + logged until the function execution completes. + Similarly, when a query is running under the extended query + protocol, the plan is logged only during the execute step. diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index b885513f7654..b49edd236617 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -36,6 +36,7 @@ #include "catalog/pg_enum.h" #include "catalog/storage.h" #include "commands/async.h" +#include "commands/dynamic_explain.h" #include "commands/tablecmds.h" #include "commands/trigger.h" #include "common/pg_prng.h" @@ -2904,6 +2905,9 @@ AbortTransaction(void) /* Reset snapshot export state. */ SnapBuildResetExportedSnapshotState(); + /* Reset pg_log_query_plan() related state. */ + ResetLogQueryPlanState(); + /* * If this xact has started any unfinished parallel operation, clean up * its workers and exit parallel mode. Don't warn about leaked resources. @@ -5296,6 +5300,9 @@ AbortSubTransaction(void) /* Reset logical streaming state. */ ResetLogicalStreamingState(); + /* Reset pg_log_query_plan() related state. */ + ResetLogQueryPlanState(); + /* * No need for SnapBuildResetExportedSnapshotState() here, snapshot * exports are not supported in subtransactions. diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 566f308e4439..ec2e1c1fd9a2 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -775,6 +775,8 @@ REVOKE EXECUTE ON FUNCTION pg_ls_logicalmapdir() FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pg_ls_replslotdir(text) FROM PUBLIC; +REVOKE EXECUTE ON FUNCTION pg_log_query_plan(integer) FROM PUBLIC; + -- -- We also set up some things as accessible to standard roles. -- diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index cb2fbdc7c601..5e83907eb171 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -32,6 +32,7 @@ OBJS = \ define.o \ discard.o \ dropcmds.o \ + dynamic_explain.o \ event_trigger.o \ explain.o \ explain_dr.o \ diff --git a/src/backend/commands/dynamic_explain.c b/src/backend/commands/dynamic_explain.c new file mode 100644 index 000000000000..d161aa8fa76f --- /dev/null +++ b/src/backend/commands/dynamic_explain.c @@ -0,0 +1,368 @@ +/*------------------------------------------------------------------------- + * + * dynamic_explain.c + * Explain query plans during execution + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994-5, Regents of the University of California + * + * IDENTIFICATION + * src/backend/commands/dynamic_explain.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "commands/dynamic_explain.h" +#include "commands/explain.h" +#include "commands/explain_format.h" +#include "commands/explain_state.h" +#include "miscadmin.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "utils/backend_status.h" + +/* Whether this backend is performing logging plan */ +static bool ProcessLogQueryPlanInterruptActive = false; + +/* Currently executing query's QueryDesc */ +static QueryDesc *ActiveQueryDesc = NULL; + +static TupleTableSlot *ExecProcNodeWithExplain(PlanState *ps); +static void WrapExecProcNodeWithExplain(PlanState *ps); +static void UnwrapExecProcNodeWithExplain(PlanState *ps); + +/* + * Handle receipt of an interrupt indicating logging the plan of the currently + * running query. + * + * All the actual work is deferred to ProcessLogQueryPlanInterrupt(), + * because we cannot safely emit a log message inside the signal handler. + */ +void +HandleLogQueryPlanInterrupt(void) +{ + InterruptPending = true; + LogQueryPlanPending = true; + /* latch will be set by procsignal_sigusr1_handler */ +} + +/* + * Clear pg_log_query_plan() related state during (sub)transaction abort + */ +void +ResetLogQueryPlanState(void) +{ + /* + * After abort, some elements of ActiveQueryDesc is freed. To avoid + * accessing them, reset ActiveQueryDesc here. + */ + ActiveQueryDesc = NULL; + ProcessLogQueryPlanInterruptActive = false; +} + +/* + * Wrap array of PlanState ExecProcNodes with ExecProcNodeWithExplain + */ +static void +WrapPlanStatesWithExplain(PlanState **planstates, int nplans) +{ + int i; + + for (i = 0; i < nplans; i++) + WrapExecProcNodeWithExplain(planstates[i]); +} + +/* + * Wrap CustomScanState children's ExecProcNodes with ExecProcNodeWithExplain + */ +static void +WrapCustomPlanChildWithExplain(CustomScanState *css) +{ + ListCell *cell; + + foreach(cell, css->custom_ps) + WrapExecProcNodeWithExplain((PlanState *) lfirst(cell)); +} + +/* + * Wrap ExecProcNode with ExecProcNodeWithExplain recursively + */ +static void +WrapExecProcNodeWithExplain(PlanState *ps) +{ + /* wrapping can be done only once */ + if (ps->ExecProcNodeOriginal != NULL) + return; + + check_stack_depth(); + + ps->ExecProcNodeOriginal = ps->ExecProcNode; + ps->ExecProcNode = ExecProcNodeWithExplain; + + if (ps->lefttree != NULL) + WrapExecProcNodeWithExplain(ps->lefttree); + if (ps->righttree != NULL) + WrapExecProcNodeWithExplain(ps->righttree); + if (ps->subPlan != NULL) + { + ListCell *l; + + foreach(l, ps->subPlan) + { + SubPlanState *sstate = (SubPlanState *) lfirst(l); + + WrapExecProcNodeWithExplain(sstate->planstate); + } + } + + /* special child plans */ + switch (nodeTag(ps->plan)) + { + case T_Append: + WrapPlanStatesWithExplain(((AppendState *) ps)->appendplans, + ((AppendState *) ps)->as_nplans); + break; + case T_MergeAppend: + WrapPlanStatesWithExplain(((MergeAppendState *) ps)->mergeplans, + ((MergeAppendState *) ps)->ms_nplans); + break; + case T_BitmapAnd: + WrapPlanStatesWithExplain(((BitmapAndState *) ps)->bitmapplans, + ((BitmapAndState *) ps)->nplans); + break; + case T_BitmapOr: + WrapPlanStatesWithExplain(((BitmapOrState *) ps)->bitmapplans, + ((BitmapOrState *) ps)->nplans); + break; + case T_SubqueryScan: + WrapExecProcNodeWithExplain(((SubqueryScanState *) ps)->subplan); + break; + case T_CustomScan: + WrapCustomPlanChildWithExplain((CustomScanState *) ps); + break; + default: + break; + } +} + +/* + * Unwrap array of PlanStates ExecProcNodes with ExecProcNodeWithExplain + */ +static void +UnwrapPlanStatesWithExplain(PlanState **planstates, int nplans) +{ + int i; + + for (i = 0; i < nplans; i++) + UnwrapExecProcNodeWithExplain(planstates[i]); +} + +/* + * Unwrap CustomScanState children's ExecProcNodes with ExecProcNodeWithExplain + */ +static void +UnwrapCustomPlanChildWithExplain(CustomScanState *css) +{ + ListCell *cell; + + foreach(cell, css->custom_ps) + UnwrapExecProcNodeWithExplain((PlanState *) lfirst(cell)); +} + +/* + * Unwrap ExecProcNode with ExecProcNodeWithExplain recursively + */ +static void +UnwrapExecProcNodeWithExplain(PlanState *ps) +{ + Assert(ps->ExecProcNodeOriginal != NULL); + + check_stack_depth(); + + ps->ExecProcNode = ps->ExecProcNodeOriginal; + ps->ExecProcNodeOriginal = NULL; + + if (ps->lefttree != NULL) + UnwrapExecProcNodeWithExplain(ps->lefttree); + if (ps->righttree != NULL) + UnwrapExecProcNodeWithExplain(ps->righttree); + if (ps->subPlan != NULL) + { + ListCell *l; + + foreach(l, ps->subPlan) + { + SubPlanState *sstate = (SubPlanState *) lfirst(l); + + UnwrapExecProcNodeWithExplain(sstate->planstate); + } + } + + /* special child plans */ + switch (nodeTag(ps->plan)) + { + case T_Append: + UnwrapPlanStatesWithExplain(((AppendState *) ps)->appendplans, + ((AppendState *) ps)->as_nplans); + break; + case T_MergeAppend: + UnwrapPlanStatesWithExplain(((MergeAppendState *) ps)->mergeplans, + ((MergeAppendState *) ps)->ms_nplans); + break; + case T_BitmapAnd: + UnwrapPlanStatesWithExplain(((BitmapAndState *) ps)->bitmapplans, + ((BitmapAndState *) ps)->nplans); + break; + case T_BitmapOr: + UnwrapPlanStatesWithExplain(((BitmapOrState *) ps)->bitmapplans, + ((BitmapOrState *) ps)->nplans); + break; + case T_SubqueryScan: + UnwrapExecProcNodeWithExplain(((SubqueryScanState *) ps)->subplan); + break; + case T_CustomScan: + UnwrapCustomPlanChildWithExplain((CustomScanState *) ps); + break; + default: + break; + } +} + +/* + * Wrap ExecProcNode with codes which logs currently running plan + */ +static TupleTableSlot * +ExecProcNodeWithExplain(PlanState *ps) +{ + ExplainState *es; + MemoryContext cxt; + MemoryContext old_cxt; + + check_stack_depth(); + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "log_query_plan temporary context", + ALLOCSET_DEFAULT_SIZES); + + old_cxt = MemoryContextSwitchTo(cxt); + + es = NewExplainState(); + + es->format = EXPLAIN_FORMAT_TEXT; + es->settings = true; + es->verbose = true; + es->signaled = true; + + ExplainStringAssemble(es, ActiveQueryDesc, es->format, 0, -1); + + ereport(LOG_SERVER_ONLY, + errmsg("query plan running on backend with PID %d is:\n%s", + MyProcPid, es->str->data), + errhidestmt(true), + errhidecontext(true)); + + MemoryContextSwitchTo(old_cxt); + MemoryContextDelete(cxt); + + UnwrapExecProcNodeWithExplain(ActiveQueryDesc->planstate); + + /* + * Since unwrapping has already done, call ExecProcNode() not + * ExecProcNodeOriginal(). + */ + return ps->ExecProcNode(ps); +} + +/* + * Add wrapper which logs explain of the plan to ExecProcNode + * + * Since running EXPLAIN codes at any arbitrary CHECK_FOR_INTERRUPTS() is + * unsafe, this function just wraps every ExecProcNode. + * In this way, EXPLAIN code is only executed at the timing of ExecProcNode, + * which seems safe. + */ +void +ProcessLogQueryPlanInterrupt(void) +{ + LogQueryPlanPending = false; + + /* Cannot re-enter */ + if (ProcessLogQueryPlanInterruptActive) + return; + + ProcessLogQueryPlanInterruptActive = true; + + if (ActiveQueryDesc == NULL) + { + ereport(LOG_SERVER_ONLY, + errmsg("backend with PID %d is not running a query or a subtransaction is aborted", + MyProcPid), + errhidestmt(true), + errhidecontext(true)); + + ProcessLogQueryPlanInterruptActive = false; + return; + } + + WrapExecProcNodeWithExplain(ActiveQueryDesc->planstate); + ProcessLogQueryPlanInterruptActive = false; +} + +QueryDesc * +GetActiveQueryDesc(void) +{ + return ActiveQueryDesc; +} + +void +SetActiveQueryDesc(QueryDesc *queryDesc) +{ + ActiveQueryDesc = queryDesc; +} + +/* + * Signal a backend process to log the query plan of the running query. + * + * By default, only superusers are allowed to signal to log the plan because + * allowing any users to issue this request at an unbounded rate would + * cause lots of log messages and which can lead to denial of service. + * Additional roles can be permitted with GRANT. + */ +Datum +pg_log_query_plan(PG_FUNCTION_ARGS) +{ + int pid = PG_GETARG_INT32(0); + PGPROC *proc; + PgBackendStatus *be_status; + + proc = BackendPidGetProc(pid); + + if (proc == NULL) + { + /* + * This is just a warning so a loop-through-resultset will not abort + * if one backend terminated on its own during the run. + */ + ereport(WARNING, + (errmsg("PID %d is not a PostgreSQL backend process", pid))); + PG_RETURN_BOOL(false); + } + + be_status = pgstat_get_beentry_by_proc_number(proc->vxid.procNumber); + if (be_status->st_backendType != B_BACKEND) + { + ereport(WARNING, + (errmsg("PID %d is not a PostgreSQL client backend process", pid))); + PG_RETURN_BOOL(false); + } + + if (SendProcSignal(pid, PROCSIG_LOG_QUERY_PLAN, proc->vxid.procNumber) < 0) + { + /* Again, just a warning to allow loops */ + ereport(WARNING, + (errmsg("could not send signal to process %d: %m", pid))); + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 786ee865f147..fd377725046b 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -1100,6 +1100,37 @@ ExplainQueryParameters(ExplainState *es, ParamListInfo params, int maxlen) ExplainPropertyText("Query Parameters", str, es); } +/* + * ExplainStringAssemble - + * Assemble es->str for logging according to specified options and format + */ + +void +ExplainStringAssemble(ExplainState *es, QueryDesc *queryDesc, int logFormat, + bool logTriggers, int logParameterMaxLength) +{ + ExplainBeginOutput(es); + ExplainQueryText(es, queryDesc); + ExplainQueryParameters(es, queryDesc->params, logParameterMaxLength); + ExplainPrintPlan(es, queryDesc); + if (es->analyze && logTriggers) + ExplainPrintTriggers(es, queryDesc); + if (es->costs) + ExplainPrintJITSummary(es, queryDesc); + ExplainEndOutput(es); + + /* Remove last line break */ + if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n') + es->str->data[--es->str->len] = '\0'; + + /* Fix JSON to output an object */ + if (logFormat == EXPLAIN_FORMAT_JSON) + { + es->str->data[0] = '{'; + es->str->data[es->str->len - 1] = '}'; + } +} + /* * report_triggers - * report execution stats for a single relation's triggers @@ -1827,7 +1858,10 @@ ExplainNode(PlanState *planstate, List *ancestors, /* * We have to forcibly clean up the instrumentation state because we - * haven't done ExecutorEnd yet. This is pretty grotty ... + * haven't done ExecutorEnd yet. This is pretty grotty ... This cleanup + * should not be done when the query has already been executed and explain + * has been called by signal, as the target query may use instrumentation + * and clean itself up. * * Note: contrib/auto_explain could cause instrumentation to be set up * even though we didn't ask for it here. Be careful not to print any @@ -1835,7 +1869,7 @@ ExplainNode(PlanState *planstate, List *ancestors, * InstrEndLoop call anyway, if possible, to reduce the number of cases * auto_explain has to contend with. */ - if (planstate->instrument) + if (planstate->instrument && !es->signaled) InstrEndLoop(planstate->instrument); if (es->analyze && diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build index dd4cde41d32c..4a64b8e9d097 100644 --- a/src/backend/commands/meson.build +++ b/src/backend/commands/meson.build @@ -20,6 +20,7 @@ backend_sources += files( 'define.c', 'discard.c', 'dropcmds.c', + 'dynamic_explain.c', 'event_trigger.c', 'explain.c', 'explain_dr.c', diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 7230f968101a..b1f3e83b626b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -43,6 +43,7 @@ #include "access/xact.h" #include "catalog/namespace.h" #include "catalog/partition.h" +#include "commands/dynamic_explain.h" #include "commands/matview.h" #include "commands/trigger.h" #include "executor/executor.h" @@ -380,6 +381,7 @@ standard_ExecutorRun(QueryDesc *queryDesc, DestReceiver *dest; bool sendTuples; MemoryContext oldcontext; + QueryDesc *oldActiveQueryDesc; /* sanity checks */ Assert(queryDesc != NULL); @@ -393,6 +395,13 @@ standard_ExecutorRun(QueryDesc *queryDesc, /* caller must ensure the query's snapshot is active */ Assert(GetActiveSnapshot() == estate->es_snapshot); + /* + * Save ActiveQueryDesc here to enable retrieval of the currently running + * queryDesc for nested queries. + */ + oldActiveQueryDesc = GetActiveQueryDesc(); + SetActiveQueryDesc(queryDesc); + /* * Switch into per-query memory context */ @@ -455,6 +464,7 @@ standard_ExecutorRun(QueryDesc *queryDesc, InstrStopNode(queryDesc->totaltime, estate->es_processed); MemoryContextSwitchTo(oldcontext); + SetActiveQueryDesc(oldActiveQueryDesc); } /* ---------------------------------------------------------------- diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index a3c2cd122773..d242bedcbb37 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -19,6 +19,7 @@ #include "access/parallel.h" #include "commands/async.h" +#include "commands/dynamic_explain.h" #include "miscadmin.h" #include "pgstat.h" #include "port/pg_bitutils.h" @@ -693,6 +694,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS) if (CheckProcSignal(PROCSIG_GET_MEMORY_CONTEXT)) HandleGetMemoryContextInterrupt(); + if (CheckProcSignal(PROCSIG_LOG_QUERY_PLAN)) + HandleLogQueryPlanInterrupt(); + if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE)) HandleParallelApplyMessageInterrupt(); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index dc4c600922df..9be814b16ef2 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -37,6 +37,7 @@ #include "catalog/pg_type.h" #include "commands/async.h" #include "commands/event_trigger.h" +#include "commands/dynamic_explain.h" #include "commands/prepare.h" #include "common/pg_prng.h" #include "jit/jit.h" @@ -3538,6 +3539,9 @@ ProcessInterrupts(void) if (PublishMemoryContextPending) ProcessGetMemoryContextInterrupt(); + if (LogQueryPlanPending) + ProcessLogQueryPlanInterrupt(); + if (ParallelApplyMessagePending) ProcessParallelApplyMessages(); } diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 1847e7c85d3a..ae5bd851f33d 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -41,6 +41,8 @@ volatile sig_atomic_t ProcSignalBarrierPending = false; volatile sig_atomic_t LogMemoryContextPending = false; volatile sig_atomic_t PublishMemoryContextPending = false; volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false; +volatile sig_atomic_t LogQueryPlanPending = false; + volatile uint32 InterruptHoldoffCount = 0; volatile uint32 QueryCancelHoldoffCount = 0; volatile uint32 CritSectionCount = 0; diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 62beb71da288..ae793299e253 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8581,6 +8581,12 @@ proargnames => '{pid, summary, timeout, name, ident, type, path, level, total_bytes, total_nblocks, free_bytes, free_chunks, used_bytes, num_agg_contexts, stats_timestamp}', prosrc => 'pg_get_process_memory_contexts' }, +# logging plan of the running query on the specified backend +{ oid => '8000', descr => 'log plan of the running query on the specified backend', + proname => 'pg_log_query_plan', + provolatile => 'v', prorettype => 'bool', + proargtypes => 'int4', prosrc => 'pg_log_query_plan' }, + # non-persistent series generator { oid => '1066', descr => 'non-persistent series generator', proname => 'generate_series', prorows => '1000', diff --git a/src/include/commands/dynamic_explain.h b/src/include/commands/dynamic_explain.h new file mode 100644 index 000000000000..68b16ec9bee6 --- /dev/null +++ b/src/include/commands/dynamic_explain.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * dynamic_explain.h + * prototypes for dynamic_explain.c + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994-5, Regents of the University of California + * + * src/include/commands/dynamic_explain.h + * + *------------------------------------------------------------------------- + */ +#ifndef DYNAMIC_EXPLAIN_H +#define DYNAMIC_EXPLAIN_H + +#include "executor/executor.h" +#include "commands/explain_state.h" + +extern void HandleLogQueryPlanInterrupt(void); +extern void ResetLogQueryPlanState(void); +extern void ProcessLogQueryPlanInterrupt(void); +extern QueryDesc *GetActiveQueryDesc(void); +extern void SetActiveQueryDesc(QueryDesc *queryDesc); +extern Datum pg_log_query_plan(PG_FUNCTION_ARGS); + +#endif /* DYNAMIC_EXPLAIN_H */ diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 03c5b3d73e5c..083567caf06b 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -72,6 +72,8 @@ extern void ExplainOnePlan(PlannedStmt *plannedstmt, CachedPlan *cplan, const BufferUsage *bufusage, const MemoryContextCounters *mem_counters); +extern void ExplainPrintPlan(struct ExplainState *es, QueryDesc *queryDesc); +extern void ExplainPrintTriggers(struct ExplainState *es, QueryDesc *queryDesc); extern void ExplainPrintPlan(struct ExplainState *es, QueryDesc *queryDesc); extern void ExplainPrintTriggers(struct ExplainState *es, QueryDesc *queryDesc); @@ -82,5 +84,8 @@ extern void ExplainPrintJITSummary(struct ExplainState *es, extern void ExplainQueryText(struct ExplainState *es, QueryDesc *queryDesc); extern void ExplainQueryParameters(struct ExplainState *es, ParamListInfo params, int maxlen); +extern void ExplainStringAssemble(struct ExplainState *es, QueryDesc *queryDesc, + int logFormat, bool logTriggers, + int logParameterMaxLength); #endif /* EXPLAIN_H */ diff --git a/src/include/commands/explain_state.h b/src/include/commands/explain_state.h index 32728f5d1a17..fcef95639607 100644 --- a/src/include/commands/explain_state.h +++ b/src/include/commands/explain_state.h @@ -71,6 +71,7 @@ typedef struct ExplainState * entry */ /* state related to the current plan node */ ExplainWorkersState *workers_state; /* needed if parallel plan */ + bool signaled; /* whether explain is called by signal */ /* extensions */ void **extension_state; int extension_state_allocated; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 72f5655fb344..49412a9cb400 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -97,6 +97,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending; extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending; extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending; extern PGDLLIMPORT volatile sig_atomic_t PublishMemoryContextPending; +extern PGDLLIMPORT volatile sig_atomic_t LogQueryPlanPending; extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending; extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 5b6cadb5a6c1..feb29a381893 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1166,6 +1166,9 @@ typedef struct PlanState ExecProcNodeMtd ExecProcNodeReal; /* actual function, if above is a * wrapper */ + ExecProcNodeMtd ExecProcNodeOriginal; /* temporary place when adding + * process for ExecProcNode */ + Instrumentation *instrument; /* Optional runtime stats for this node */ WorkerInstrumentation *worker_instrument; /* per-worker instrumentation */ diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h index cfe146314452..82cfcaccaa5c 100644 --- a/src/include/storage/procsignal.h +++ b/src/include/storage/procsignal.h @@ -36,6 +36,8 @@ typedef enum PROCSIG_BARRIER, /* global barrier interrupt */ PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */ PROCSIG_GET_MEMORY_CONTEXT, /* ask backend to send the memory contexts */ + PROCSIG_LOG_QUERY_PLAN, /* ask backend to log plan of the current + * query */ PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */ /* Recovery conflict reasons */ diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index fa3cc5f2dfca..7949abf76bed 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -22,7 +22,6 @@ struct PlannedStmt; /* avoid including plannodes.h here */ extern PGDLLIMPORT Portal ActivePortal; - extern PortalStrategy ChoosePortalStrategy(List *stmts); extern List *FetchPortalTargetList(Portal portal); diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out index d3f5d16a6725..7dcb9951e791 100644 --- a/src/test/regress/expected/misc_functions.out +++ b/src/test/regress/expected/misc_functions.out @@ -316,14 +316,15 @@ SELECT test_canonicalize_path('./abc/./def/.././ghi/../../../jkl/mno'); ../jkl/mno (1 row) +-- Test logging functions -- --- pg_log_backend_memory_contexts() --- --- Memory contexts are logged and they are not returned to the function. +-- The outputs of these functions are logged and they are not returned to +-- the function. -- Furthermore, their contents can vary depending on the timing. However, -- we can at least verify that the code doesn't fail, and that the -- permissions are set properly. -- +-- pg_log_backend_memory_contexts() SELECT pg_log_backend_memory_contexts(pg_backend_pid()); pg_log_backend_memory_contexts -------------------------------- @@ -337,8 +338,8 @@ SELECT pg_log_backend_memory_contexts(pid) FROM pg_stat_activity t (1 row) -CREATE ROLE regress_log_memory; -SELECT has_function_privilege('regress_log_memory', +CREATE ROLE regress_log_function; +SELECT has_function_privilege('regress_log_function', 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- no has_function_privilege ------------------------ @@ -346,15 +347,15 @@ SELECT has_function_privilege('regress_log_memory', (1 row) GRANT EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) - TO regress_log_memory; -SELECT has_function_privilege('regress_log_memory', + TO regress_log_function; +SELECT has_function_privilege('regress_log_function', 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- yes has_function_privilege ------------------------ t (1 row) -SET ROLE regress_log_memory; +SET ROLE regress_log_function; SELECT pg_log_backend_memory_contexts(pg_backend_pid()); pg_log_backend_memory_contexts -------------------------------- @@ -363,8 +364,41 @@ SELECT pg_log_backend_memory_contexts(pg_backend_pid()); RESET ROLE; REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) - FROM regress_log_memory; -DROP ROLE regress_log_memory; + FROM regress_log_function; +-- pg_log_query_plan() +SELECT pg_log_query_plan(pg_backend_pid()); + pg_log_query_plan +------------------- + t +(1 row) + +SELECT has_function_privilege('regress_log_function', + 'pg_log_query_plan(integer)', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +GRANT EXECUTE ON FUNCTION pg_log_query_plan(integer) + TO regress_log_function; +SELECT has_function_privilege('regress_log_function', + 'pg_log_query_plan(integer)', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SET ROLE regress_log_function; +SELECT pg_log_query_plan(pg_backend_pid()); + pg_log_query_plan +------------------- + t +(1 row) + +RESET ROLE; +REVOKE EXECUTE ON FUNCTION pg_log_query_plan(integer) + FROM regress_log_function; +DROP ROLE regress_log_function; -- -- Test some built-in SRFs -- diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql index aaebb298330b..92330355f649 100644 --- a/src/test/regress/sql/misc_functions.sql +++ b/src/test/regress/sql/misc_functions.sql @@ -109,39 +109,60 @@ SELECT test_canonicalize_path('./abc/./def/.'); SELECT test_canonicalize_path('./abc/././def/.'); SELECT test_canonicalize_path('./abc/./def/.././ghi/../../../jkl/mno'); +-- Test logging functions -- --- pg_log_backend_memory_contexts() --- --- Memory contexts are logged and they are not returned to the function. +-- The outputs of these functions are logged and they are not returned to +-- the function. -- Furthermore, their contents can vary depending on the timing. However, -- we can at least verify that the code doesn't fail, and that the -- permissions are set properly. -- +-- pg_log_backend_memory_contexts() + SELECT pg_log_backend_memory_contexts(pg_backend_pid()); SELECT pg_log_backend_memory_contexts(pid) FROM pg_stat_activity WHERE backend_type = 'checkpointer'; -CREATE ROLE regress_log_memory; +CREATE ROLE regress_log_function; -SELECT has_function_privilege('regress_log_memory', +SELECT has_function_privilege('regress_log_function', 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- no GRANT EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) - TO regress_log_memory; + TO regress_log_function; -SELECT has_function_privilege('regress_log_memory', +SELECT has_function_privilege('regress_log_function', 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- yes -SET ROLE regress_log_memory; +SET ROLE regress_log_function; SELECT pg_log_backend_memory_contexts(pg_backend_pid()); RESET ROLE; REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) - FROM regress_log_memory; + FROM regress_log_function; + +-- pg_log_query_plan() +SELECT pg_log_query_plan(pg_backend_pid()); + +SELECT has_function_privilege('regress_log_function', + 'pg_log_query_plan(integer)', 'EXECUTE'); -- no + +GRANT EXECUTE ON FUNCTION pg_log_query_plan(integer) + TO regress_log_function; + +SELECT has_function_privilege('regress_log_function', + 'pg_log_query_plan(integer)', 'EXECUTE'); -- yes + +SET ROLE regress_log_function; +SELECT pg_log_query_plan(pg_backend_pid()); +RESET ROLE; + +REVOKE EXECUTE ON FUNCTION pg_log_query_plan(integer) + FROM regress_log_function; -DROP ROLE regress_log_memory; +DROP ROLE regress_log_function; -- -- Test some built-in SRFs