v4-0002-Optionally-record-a-plan_id-in-PlannedStmt-to-ide.patch
application/octet-stream
Filename: v4-0002-Optionally-record-a-plan_id-in-PlannedStmt-to-ide.patch
Type: application/octet-stream
Part: 0
From 59227e046cf2848b34ac77741d040b6e489be1bd Mon Sep 17 00:00:00 2001
From: Lukas Fittl <lukas@fittl.com>
Date: Sun, 2 Feb 2025 12:18:49 -0800
Subject: [PATCH v4 2/3] Optionally record a plan_id in PlannedStmt to identify
plan shape
When enabled via the new compute_plan_id GUC (default off), this utilizes
the existing treewalk in setrefs.c after planning to calculate a hash
(the "plan_id", or plan identifier) that can be used to identify
which plan was chosen.
The plan_id generally intends to be the same if a given EXPLAIN (without
ANALYZE) output is the same. The plan_id includes both the top-level plan
as well as all subplans. Execution statistics are excluded.
If enabled, the plan_id is shown for currently running queries in
pg_stat_activity, as well as recorded in EXPLAIN and auto_explain output.
Other in core users or extensions can use this facility to show or
accumulate statistics about the plans used by queries, to help identify
plan regressions, or drive plan management decisions.
Note that this commit intentionally does not include a facility to map
a given plan_id to the EXPLAIN text output - it is a assumed that users
can utilize the auto_explain extension to establish this mapping as
needed, or extensions can record this via the existing planner hook.
---
doc/src/sgml/config.sgml | 34 +++
doc/src/sgml/monitoring.sgml | 16 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/commands/explain.c | 16 ++
src/backend/executor/execMain.c | 10 +-
src/backend/executor/execParallel.c | 1 +
src/backend/nodes/gen_node_support.pl | 36 ++-
src/backend/nodes/queryjumblefuncs.c | 65 +++++
src/backend/optimizer/plan/planner.c | 19 ++
src/backend/optimizer/plan/setrefs.c | 9 +
src/backend/postmaster/launch_backend.c | 3 +
src/backend/tcop/postgres.c | 1 +
src/backend/utils/activity/backend_status.c | 70 ++++-
src/backend/utils/adt/pgstatfuncs.c | 7 +-
src/backend/utils/misc/guc_tables.c | 28 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_proc.dat | 6 +-
src/include/nodes/pathnodes.h | 3 +
src/include/nodes/plannodes.h | 243 ++++++++++++------
src/include/nodes/primnodes.h | 7 +-
src/include/nodes/queryjumble.h | 30 ++-
src/include/utils/backend_status.h | 5 +
src/test/regress/expected/explain.out | 11 +
src/test/regress/expected/rules.out | 9 +-
src/test/regress/sql/explain.sql | 4 +
25 files changed, 529 insertions(+), 106 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a782f109982..85a1e0d8d4b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8406,6 +8406,40 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
+ <varlistentry id="guc-compute-plan-id" xreflabel="compute_plan_id">
+ <term><varname>compute_plan_id</varname> (<type>enum</type>)
+ <indexterm>
+ <primary><varname>compute_plan_id</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables in-core computation of a plan identifier.
+ Plan identifiers can be displayed in the <link
+ linkend="monitoring-pg-stat-activity-view"><structname>pg_stat_activity</structname></link>
+ view or using <command>EXPLAIN</command>.
+ Note that an external module can alternatively be used if the
+ in-core plan identifier computation method is not acceptable.
+ In this case, in-core computation must be always disabled.
+ Valid values are <literal>off</literal> (always disabled),
+ <literal>on</literal> (always enabled), <literal>auto</literal>,
+ which lets modules that utilize plan identifiers enable
+ it automatically, and <literal>regress</literal> which
+ has the same effect as <literal>on</literal>, except that the
+ query identifier is not shown in the <literal>EXPLAIN</literal> output
+ in order to facilitate automated regression testing.
+ The default is <literal>auto</literal>.
+ </para>
+ <note>
+ <para>
+ To ensure that only one plan identifier is calculated and
+ displayed, extensions that calculate plan identifiers should
+ throw an error if a plan identifier has already been computed.
+ </para>
+ </note>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-log-statement-stats">
<term><varname>log_statement_stats</varname> (<type>boolean</type>)
<indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index edc2470bcf9..64e62a09059 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -972,6 +972,22 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>plan_id</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Identifier of this backend's most recent query plan. If
+ <structfield>state</structfield> is <literal>active</literal> this
+ field shows the identifier of the currently executing query plan. In
+ all other states, it shows the identifier of last query plan that
+ was executed. Plan identifiers are not computed by default so this
+ field will be null unless <xref linkend="guc-compute-plan-id"/>
+ parameter is enabled or a third-party module that computes plan
+ identifiers is configured.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>query</structfield> <type>text</type>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index cddc3ea9b53..d3d5578d2c8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -889,6 +889,7 @@ CREATE VIEW pg_stat_activity AS
S.backend_xid,
s.backend_xmin,
S.query_id,
+ S.plan_id,
S.query,
S.backend_type
FROM pg_stat_get_activity(NULL) AS S
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c24e66f82e1..31a1761f539 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -966,6 +966,22 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
ExplainPropertyInteger("Query Identifier", NULL, (int64)
queryDesc->plannedstmt->queryId, es);
}
+
+ /*
+ * COMPUTE_PLAN_ID_REGRESS means COMPUTE_PLAN_ID_YES, but we don't show
+ * the queryid in any of the EXPLAIN plans to keep stable the results
+ * generated by regression test suites.
+ */
+ if (es->verbose && queryDesc->plannedstmt->planId != UINT64CONST(0) &&
+ compute_plan_id != COMPUTE_PLAN_ID_REGRESS)
+ {
+ /*
+ * Output the queryid as an int64 rather than a uint64 so we match
+ * what would be seen in the BIGINT pg_stat_activity.plan_id column.
+ */
+ ExplainPropertyInteger("Plan Identifier", NULL, (int64)
+ queryDesc->plannedstmt->planId, es);
+ }
}
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 604cb0625b8..6890082d745 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -121,13 +121,15 @@ ExecutorStart(QueryDesc *queryDesc, int eflags)
{
/*
* In some cases (e.g. an EXECUTE statement or an execute message with the
- * extended query protocol) the query_id won't be reported, so do it now.
+ * extended query protocol) the query_id and plan_id won't be reported, so
+ * do it now.
*
- * Note that it's harmless to report the query_id multiple times, as the
- * call will be ignored if the top level query_id has already been
- * reported.
+ * Note that it's harmless to report the identifiers multiple times, as
+ * the call will be ignored if the top level query_id / plan_id has
+ * already been reported.
*/
pgstat_report_query_id(queryDesc->plannedstmt->queryId, false);
+ pgstat_report_plan_id(queryDesc->plannedstmt->planId, queryDesc->plannedstmt->queryId, false);
if (ExecutorStart_hook)
(*ExecutorStart_hook) (queryDesc, eflags);
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 9c313d81315..da2279579a8 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -174,6 +174,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
pstmt = makeNode(PlannedStmt);
pstmt->commandType = CMD_SELECT;
pstmt->queryId = pgstat_get_my_query_id();
+ pstmt->planId = pgstat_get_my_plan_id();
pstmt->hasReturning = false;
pstmt->hasModifyingCTE = false;
pstmt->canSetTag = true;
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 1a657f7e0ae..6c20bfc5986 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -1281,13 +1281,18 @@ _jumble${n}(JumbleState *jstate, Node *node)
{
my $t = $node_type_info{$n}->{field_types}{$f};
my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+ my $array_size_field;
my $query_jumble_ignore = $struct_no_query_jumble;
my $query_jumble_location = 0;
# extract per-field attributes
foreach my $a (@a)
{
- if ($a eq 'query_jumble_ignore')
+ if ($a =~ /^array_size\(([\w.]+)\)$/)
+ {
+ $array_size_field = $1;
+ }
+ elsif ($a eq 'query_jumble_ignore')
{
$query_jumble_ignore = 1;
}
@@ -1297,8 +1302,15 @@ _jumble${n}(JumbleState *jstate, Node *node)
}
}
+ next if $query_jumble_ignore;
+
+ if ($t eq 'Bitmapset*')
+ {
+ print $jff "\tJUMBLE_BITMAPSET($f);\n"
+ unless $query_jumble_ignore;
+ }
# node type
- if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+ elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
and elem $1, @node_types)
{
print $jff "\tJUMBLE_NODE($f);\n"
@@ -1318,6 +1330,26 @@ _jumble${n}(JumbleState *jstate, Node *node)
print $jff "\tJUMBLE_STRING($f);\n"
unless $query_jumble_ignore;
}
+ elsif ($t =~ /^(\w+)(\*|\[\w+\])$/ and elem $1, @scalar_types)
+ {
+ if (!defined $array_size_field)
+ {
+ die "no array size defined for $n.$f of type $t\n";
+ }
+ if ($node_type_info{$n}->{field_types}{$array_size_field} eq
+ 'List*')
+ {
+ print $jff
+ "\tJUMBLE_ARRAY($f, list_length(expr->$array_size_field));\n"
+ unless $query_jumble_ignore;
+ }
+ else
+ {
+ print $jff
+ "\tJUMBLE_ARRAY($f, expr->$array_size_field);\n"
+ unless $query_jumble_ignore;
+ }
+ }
else
{
print $jff "\tJUMBLE_FIELD($f);\n"
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 545d8edcae2..64bec54a211 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -35,12 +35,14 @@
#include "common/hashfn.h"
#include "miscadmin.h"
#include "nodes/queryjumble.h"
+#include "parser/parsetree.h"
#include "parser/scansup.h"
#define JUMBLE_SIZE 1024 /* query serialization buffer size */
/* GUC parameters */
int compute_query_id = COMPUTE_QUERY_ID_AUTO;
+int compute_plan_id = COMPUTE_PLAN_ID_AUTO;
/*
* True when compute_query_id is ON or AUTO, and a module requests them.
@@ -51,6 +53,15 @@ int compute_query_id = COMPUTE_QUERY_ID_AUTO;
*/
bool query_id_enabled = false;
+/*
+ * True when compute_plan_id is ON or AUTO, and a module requests them.
+ *
+ * Note that IsPlanIdEnabled() should be used instead of checking
+ * plan_id_enabled or plan_query_id directly when we want to know
+ * whether plan identifiers are computed in the core or not.
+ */
+bool plan_id_enabled = false;
+
static void RecordConstLocation(JumbleState *jstate, int location);
static void _jumbleA_Const(JumbleState *jstate, Node *node);
static void _jumbleList(JumbleState *jstate, Node *node);
@@ -171,6 +182,19 @@ EnableQueryId(void)
query_id_enabled = true;
}
+/*
+ * Enables plan identifier computation.
+ *
+ * Third-party plugins can use this function to inform core that they require
+ * a query identifier to be computed.
+ */
+void
+EnablePlanId(void)
+{
+ if (compute_plan_id != COMPUTE_PLAN_ID_OFF)
+ plan_id_enabled = true;
+}
+
/*
* AppendJumble: Append a value that is substantive in a given query to
* the current jumble.
@@ -240,6 +264,13 @@ RecordConstLocation(JumbleState *jstate, int location)
RecordConstLocation(jstate, expr->location)
#define JUMBLE_FIELD(item) \
AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_BITMAPSET(item) \
+do { \
+ if (expr->item) \
+ AppendJumble(jstate, (const unsigned char *) expr->item->words, sizeof(bitmapword) * expr->item->nwords); \
+} while(0)
+#define JUMBLE_ARRAY(item, len) \
+ AppendJumble(jstate, (const unsigned char *) expr->item, sizeof(*(expr->item)) * len)
#define JUMBLE_FIELD_SINGLE(item) \
AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
#define JUMBLE_STRING(str) \
@@ -388,3 +419,37 @@ _jumbleVariableSetStmt(JumbleState *jstate, Node *node)
JUMBLE_FIELD(is_local);
JUMBLE_LOCATION(location);
}
+
+/*
+ * Jumble the entries in the rangle table to map RT indexes to relations
+ *
+ * This ensures jumbled RT indexes (e.g. in a Scan or Modify node), are
+ * distinguished by the target of the RT entry, even if the index is the same.
+ */
+void
+JumbleRangeTable(JumbleState *jstate, List *rtable)
+{
+ ListCell *lc;
+
+ foreach(lc, rtable)
+ {
+ RangeTblEntry *expr = lfirst_node(RangeTblEntry, lc);
+
+ switch (expr->rtekind)
+ {
+ case RTE_RELATION:
+ JUMBLE_FIELD(relid);
+ break;
+ case RTE_CTE:
+ JUMBLE_STRING(ctename);
+ break;
+ default:
+
+ /*
+ * Ignore other targets, the jumble includes something identifying
+ * about them already
+ */
+ break;
+ }
+ }
+}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index ffd7517ea97..9d43a174b9e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -37,6 +37,7 @@
#ifdef OPTIMIZER_DEBUG
#include "nodes/print.h"
#endif
+#include "nodes/queryjumble.h"
#include "nodes/supportnodes.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
@@ -532,6 +533,16 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
Assert(glob->finalrowmarks == NIL);
Assert(glob->resultRelations == NIL);
Assert(glob->appendRelations == NIL);
+
+ /*
+ * Initialize plan identifier jumble if needed
+ *
+ * Note the actual jumbling is done in the tree walk in
+ * set_plan_references
+ */
+ if (IsPlanIdEnabled())
+ glob->plan_jumble_state = InitializeJumbleState(false);
+
top_plan = set_plan_references(root, top_plan);
/* ... and the subplans (both regular subplans and initplans) */
Assert(list_length(glob->subplans) == list_length(glob->subroots));
@@ -596,6 +607,14 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->jitFlags |= PGJIT_DEFORM;
}
+ if (IsPlanIdEnabled())
+ {
+ JumbleRangeTable(glob->plan_jumble_state, glob->finalrtable);
+ result->planId = HashJumbleState(glob->plan_jumble_state);
+ pfree(glob->plan_jumble_state->jumble);
+ pfree(glob->plan_jumble_state);
+ }
+
if (glob->partition_directory != NULL)
DestroyPartitionDirectory(glob->partition_directory);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 0868249be94..5e890f576b0 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -19,6 +19,7 @@
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/queryjumble.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
@@ -1306,6 +1307,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
plan->lefttree = set_plan_refs(root, plan->lefttree, rtoffset);
plan->righttree = set_plan_refs(root, plan->righttree, rtoffset);
+ /*
+ * If enabled, append significant information to the plan identifier
+ * jumble (we do this here since we're already walking the tree in a
+ * near-final state)
+ */
+ if (IsPlanIdEnabled())
+ JumbleNode(root->glob->plan_jumble_state, (Node *) plan);
+
return plan;
}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index a97a1eda6da..654acf5bf04 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -115,6 +115,7 @@ typedef struct
bool redirection_done;
bool IsBinaryUpgrade;
bool query_id_enabled;
+ bool plan_id_enabled;
int max_safe_fds;
int MaxBackends;
int num_pmchild_slots;
@@ -744,6 +745,7 @@ save_backend_variables(BackendParameters *param,
param->redirection_done = redirection_done;
param->IsBinaryUpgrade = IsBinaryUpgrade;
param->query_id_enabled = query_id_enabled;
+ param->plan_id_enabled = plan_id_enabled;
param->max_safe_fds = max_safe_fds;
param->MaxBackends = MaxBackends;
@@ -1004,6 +1006,7 @@ restore_backend_variables(BackendParameters *param)
redirection_done = param->redirection_done;
IsBinaryUpgrade = param->IsBinaryUpgrade;
query_id_enabled = param->query_id_enabled;
+ plan_id_enabled = param->plan_id_enabled;
max_safe_fds = param->max_safe_fds;
MaxBackends = param->MaxBackends;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5655348a2e2..6d8947bae9b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1106,6 +1106,7 @@ exec_simple_query(const char *query_string)
size_t cmdtaglen;
pgstat_report_query_id(0, true);
+ pgstat_report_plan_id(0, 0, true);
/*
* Get the command name for use in status display (it also becomes the
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index 731342799a6..1dfb7a58f87 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -379,6 +379,7 @@ pgstat_bestart(void)
lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID;
lbeentry.st_progress_command_target = InvalidOid;
lbeentry.st_query_id = UINT64CONST(0);
+ lbeentry.st_plan_id = UINT64CONST(0);
/*
* we don't zero st_progress_param here to save cycles; nobody should
@@ -533,6 +534,7 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
/* st_xact_start_timestamp and wait_event_info are also disabled */
beentry->st_xact_start_timestamp = 0;
beentry->st_query_id = UINT64CONST(0);
+ beentry->st_plan_id = UINT64CONST(0);
proc->wait_event_info = 0;
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
@@ -588,12 +590,15 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
beentry->st_state_start_timestamp = current_timestamp;
/*
- * If a new query is started, we reset the query identifier as it'll only
- * be known after parse analysis, to avoid reporting last query's
- * identifier.
+ * If a new query is started, we reset the query and plan identifier as
+ * it'll only be known after parse analysis / planning, to avoid reporting
+ * last query's identifiers.
*/
if (state == STATE_RUNNING)
+ {
beentry->st_query_id = UINT64CONST(0);
+ beentry->st_plan_id = UINT64CONST(0);
+ }
if (cmd_str != NULL)
{
@@ -644,6 +649,45 @@ pgstat_report_query_id(uint64 query_id, bool force)
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
+/* --------
+ * pgstat_report_plan_id() -
+ *
+ * Called to update top-level plan identifier.
+ * --------
+ */
+void
+pgstat_report_plan_id(uint64 plan_id, uint64 query_id, bool force)
+{
+ volatile PgBackendStatus *beentry = MyBEEntry;
+
+ /*
+ * if track_activities is disabled, st_plan_id should already have been
+ * reset
+ */
+ if (!beentry || !pgstat_track_activities)
+ return;
+
+ /*
+ * We only report the top-level plan identifiers. The stored plan_id is
+ * reset when a backend calls pgstat_report_activity(STATE_RUNNING), or
+ * with an explicit call to this function using the force flag. If the
+ * saved plan identifier is not zero or the query identifier is 0, it
+ * means that it's not a top-level command, so ignore the one provided
+ * unless it's an explicit call to reset the identifier.
+ */
+ if ((beentry->st_plan_id != 0 || query_id == 0) && !force)
+ return;
+
+ /*
+ * Update my status entry, following the protocol of bumping
+ * st_changecount before and after. We use a volatile pointer here to
+ * ensure the compiler doesn't try to get cute.
+ */
+ PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
+ beentry->st_plan_id = plan_id;
+ PGSTAT_END_WRITE_ACTIVITY(beentry);
+}
+
/* ----------
* pgstat_report_appname() -
@@ -1040,6 +1084,26 @@ pgstat_get_my_query_id(void)
return MyBEEntry->st_query_id;
}
+/* ----------
+ * pgstat_get_my_plan_id() -
+ *
+ * Return current backend's plan identifier.
+ */
+uint64
+pgstat_get_my_plan_id(void)
+{
+ if (!MyBEEntry)
+ return 0;
+
+ /*
+ * There's no need for a lock around pgstat_begin_read_activity /
+ * pgstat_end_read_activity here as it's only called from
+ * pg_stat_get_activity which is already protected, or from the same
+ * backend which means that there won't be concurrent writes.
+ */
+ return MyBEEntry->st_plan_id;
+}
+
/* ----------
* pgstat_get_backend_type_by_proc_number() -
*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index e9096a88492..a3d5592beb6 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -330,7 +330,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
Datum
pg_stat_get_activity(PG_FUNCTION_ARGS)
{
-#define PG_STAT_GET_ACTIVITY_COLS 31
+#define PG_STAT_GET_ACTIVITY_COLS 32
int num_backends = pgstat_fetch_stat_numbackends();
int curr_backend;
int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -641,6 +641,10 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
nulls[30] = true;
else
values[30] = UInt64GetDatum(beentry->st_query_id);
+ if (beentry->st_plan_id == 0)
+ nulls[31] = true;
+ else
+ values[31] = UInt64GetDatum(beentry->st_plan_id);
}
else
{
@@ -670,6 +674,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
nulls[28] = true;
nulls[29] = true;
nulls[30] = true;
+ nulls[31] = true;
}
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 71448bb4fdd..22fdfb60ccc 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -309,6 +309,24 @@ static const struct config_enum_entry compute_query_id_options[] = {
{NULL, 0, false}
};
+/*
+ * Although only "on" and "off" are documented, we accept
+ * all the likely variants of "on" and "off".
+ */
+static const struct config_enum_entry compute_plan_id_options[] = {
+ {"auto", COMPUTE_PLAN_ID_AUTO, false},
+ {"regress", COMPUTE_PLAN_ID_REGRESS, false},
+ {"on", COMPUTE_PLAN_ID_ON, false},
+ {"off", COMPUTE_PLAN_ID_OFF, false},
+ {"true", COMPUTE_PLAN_ID_ON, true},
+ {"false", COMPUTE_PLAN_ID_OFF, true},
+ {"yes", COMPUTE_PLAN_ID_ON, true},
+ {"no", COMPUTE_PLAN_ID_OFF, true},
+ {"1", COMPUTE_PLAN_ID_ON, true},
+ {"0", COMPUTE_PLAN_ID_OFF, true},
+ {NULL, 0, false}
+};
+
/*
* Although only "on", "off", and "partition" are documented, we
* accept all the likely variants of "on" and "off".
@@ -4873,6 +4891,16 @@ struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"compute_plan_id", PGC_SUSET, STATS_MONITORING,
+ gettext_noop("Enables in-core computation of plan identifiers."),
+ NULL
+ },
+ &compute_plan_id,
+ COMPUTE_PLAN_ID_AUTO, compute_plan_id_options,
+ NULL, NULL, NULL
+ },
+
{
{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
gettext_noop("Enables the planner to use constraints to optimize queries."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 079efa1baa7..0634ae90dd3 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -641,6 +641,7 @@
# - Monitoring -
#compute_query_id = auto
+#compute_plan_id = auto
#log_statement_stats = off
#log_parser_stats = off
#log_planner_stats = off
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b8c2ad2a54..5547db7d2f1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5568,9 +5568,9 @@
proname => 'pg_stat_get_activity', prorows => '100', proisstrict => 'f',
proretset => 't', provolatile => 's', proparallel => 'r',
prorettype => 'record', proargtypes => 'int4',
- proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,text,numeric,text,bool,text,bool,bool,int4,int8}',
- proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
- proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
+ proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,text,numeric,text,bool,text,bool,bool,int4,int8,int8}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id,plan_id}',
prosrc => 'pg_stat_get_activity' },
{ oid => '6318', descr => 'describe wait events',
proname => 'pg_get_wait_events', procost => '10', prorows => '250',
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 52d44f43021..4baf829f644 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -166,6 +166,9 @@ typedef struct PlannerGlobal
/* partition descriptors */
PartitionDirectory partition_directory pg_node_attr(read_write_ignore);
+
+ /* optional jumble state for plan identifier calculation */
+ struct JumbleState *plan_jumble_state pg_node_attr(read_write_ignore);
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 06d9559ebb9..2ccf772d87e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -53,6 +53,10 @@ typedef struct PlannedStmt
uint64 queryId; /* query identifier (copied from Query) */
+ uint64 planId; /* plan identifier (calculated if
+ * compute_plan_id is enabled, can also be set
+ * by plugins) */
+
bool hasReturning; /* is it insert|update|delete|merge RETURNING? */
bool hasModifyingCTE; /* has insert|update|delete|merge in WITH? */
@@ -121,44 +125,55 @@ typedef struct PlannedStmt
*/
typedef struct Plan
{
- pg_node_attr(abstract, no_equal, no_query_jumble)
+ pg_node_attr(abstract, no_equal)
NodeTag type;
/*
* estimated execution costs for plan (see costsize.c for more info)
*/
- int disabled_nodes; /* count of disabled nodes */
- Cost startup_cost; /* cost expended before fetching any tuples */
- Cost total_cost; /* total cost (assuming all tuples fetched) */
+ int disabled_nodes pg_node_attr(query_jumble_ignore); /* count of disabled
+ * nodes */
+ Cost startup_cost pg_node_attr(query_jumble_ignore); /* cost expended before
+ * fetching any tuples */
+ Cost total_cost pg_node_attr(query_jumble_ignore); /* total cost (assuming
+ * all tuples fetched) */
/*
* planner's estimate of result size of this plan step
*/
- Cardinality plan_rows; /* number of rows plan is expected to emit */
- int plan_width; /* average row width in bytes */
+ Cardinality plan_rows pg_node_attr(query_jumble_ignore); /* number of rows plan
+ * is expected to emit */
+ int plan_width pg_node_attr(query_jumble_ignore); /* average row width in
+ * bytes */
/*
* information needed for parallel query
*/
- bool parallel_aware; /* engage parallel-aware logic? */
- bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool parallel_aware; /* engage parallel-aware
+ * logic? */
+ bool parallel_safe; /* OK to use as part of
+ * parallel plan? */
/*
* information needed for asynchronous execution
*/
- bool async_capable; /* engage asynchronous-capable logic? */
+ bool async_capable; /* engage
+ * asynchronous-capable
+ * logic? */
/*
* Common structural data for all Plan types.
*/
- int plan_node_id; /* unique across entire final plan tree */
+ int plan_node_id; /* unique across entire
+ * final plan tree */
List *targetlist; /* target list to be computed at this node */
List *qual; /* implicitly-ANDed qual conditions */
- struct Plan *lefttree; /* input plan tree(s) */
- struct Plan *righttree;
- List *initPlan; /* Init Plan nodes (un-correlated expr
- * subselects) */
+ struct Plan *lefttree pg_node_attr(query_jumble_ignore); /* input plan tree(s) */
+ struct Plan *righttree pg_node_attr(query_jumble_ignore);
+ List *initPlan pg_node_attr(query_jumble_ignore); /* Init Plan nodes
+ * (un-correlated expr
+ * subselects) */
/*
* Information for management of parameter-change-driven rescanning
@@ -234,31 +249,47 @@ typedef struct ModifyTable
{
Plan plan;
CmdType operation; /* INSERT, UPDATE, DELETE, or MERGE */
- bool canSetTag; /* do we set the command tag/es_processed? */
- Index nominalRelation; /* Parent RT index for use of EXPLAIN */
- Index rootRelation; /* Root RT index, if partitioned/inherited */
+ bool canSetTag; /* do we set the command
+ * tag/es_processed? */
+ Index nominalRelation; /* Parent RT index for
+ * use of EXPLAIN */
+ Index rootRelation; /* Root RT index, if
+ * partitioned/inherited */
bool partColsUpdated; /* some part key in hierarchy updated? */
- List *resultRelations; /* integer list of RT indexes */
- List *updateColnosLists; /* per-target-table update_colnos lists */
- List *withCheckOptionLists; /* per-target-table WCO lists */
- char *returningOldAlias; /* alias for OLD in RETURNING lists */
- char *returningNewAlias; /* alias for NEW in RETURNING lists */
- List *returningLists; /* per-target-table RETURNING tlists */
- List *fdwPrivLists; /* per-target-table FDW private data lists */
- Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
- List *rowMarks; /* PlanRowMarks (non-locking only) */
- int epqParam; /* ID of Param for EvalPlanQual re-eval */
+ List *resultRelations; /* integer list of RT
+ * indexes */
+ List *updateColnosLists; /* per-target-table
+ * update_colnos lists */
+ List *withCheckOptionLists; /* per-target-table WCO
+ * lists */
+ char *returningOldAlias; /* alias for OLD in
+ * RETURNING lists */
+ char *returningNewAlias; /* alias for NEW in
+ * RETURNING lists */
+ List *returningLists; /* per-target-table
+ * RETURNING tlists */
+ List *fdwPrivLists pg_node_attr(query_jumble_ignore); /* per-target-table FDW
+ * private data lists */
+ Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM
+ * plans */
+ List *rowMarks; /* PlanRowMarks
+ * (non-locking only) */
+ int epqParam; /* ID of Param for
+ * EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
List *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs */
List *onConflictSet; /* INSERT ON CONFLICT DO UPDATE targetlist */
List *onConflictCols; /* target column numbers for onConflictSet */
Node *onConflictWhere; /* WHERE for ON CONFLICT UPDATE */
- Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */
- List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
- List *mergeActionLists; /* per-target-table lists of actions for
- * MERGE */
- List *mergeJoinConditions; /* per-target-table join conditions
- * for MERGE */
+ Index exclRelRTI; /* RTI of the EXCLUDED
+ * pseudo relation */
+ List *exclRelTlist; /* tlist of the EXCLUDED
+ * pseudo relation */
+ List *mergeActionLists; /* per-target-table
+ * lists of actions for
+ * MERGE */
+ List *mergeJoinConditions; /* per-target-table join
+ * conditions for MERGE */
} ModifyTable;
struct PartitionPruneInfo; /* forward reference to struct below */
@@ -272,7 +303,7 @@ typedef struct Append
{
Plan plan;
Bitmapset *apprelids; /* RTIs of appendrel(s) formed by this node */
- List *appendplans;
+ List *appendplans pg_node_attr(query_jumble_ignore);
int nasyncplans; /* # of asynchronous plans */
/*
@@ -301,7 +332,7 @@ typedef struct MergeAppend
/* RTIs of appendrel(s) formed by this node */
Bitmapset *apprelids;
- List *mergeplans;
+ List *mergeplans pg_node_attr(query_jumble_ignore);
/* these fields are just like the sort-key info in struct Sort: */
@@ -356,7 +387,7 @@ typedef struct RecursiveUnion
Oid *dupCollations pg_node_attr(array_size(numCols));
/* estimated number of groups in input */
- long numGroups;
+ long numGroups pg_node_attr(query_jumble_ignore);
} RecursiveUnion;
/* ----------------
@@ -370,7 +401,7 @@ typedef struct RecursiveUnion
typedef struct BitmapAnd
{
Plan plan;
- List *bitmapplans;
+ List *bitmapplans pg_node_attr(query_jumble_ignore);
} BitmapAnd;
/* ----------------
@@ -385,7 +416,7 @@ typedef struct BitmapOr
{
Plan plan;
bool isshared;
- List *bitmapplans;
+ List *bitmapplans pg_node_attr(query_jumble_ignore);
} BitmapOr;
/*
@@ -400,7 +431,8 @@ typedef struct Scan
pg_node_attr(abstract)
Plan plan;
- Index scanrelid; /* relid is index into the range table */
+ Index scanrelid; /* relid is index into
+ * the range table */
} Scan;
/* ----------------
@@ -465,9 +497,11 @@ typedef struct IndexScan
Scan scan;
Oid indexid; /* OID of index to scan */
List *indexqual; /* list of index quals (usually OpExprs) */
- List *indexqualorig; /* the same in original form */
+ List *indexqualorig; /* the same in original
+ * form */
List *indexorderby; /* list of index ORDER BY exprs */
- List *indexorderbyorig; /* the same in original form */
+ List *indexorderbyorig; /* the same in original
+ * form */
List *indexorderbyops; /* OIDs of sort ops for ORDER BY exprs */
ScanDirection indexorderdir; /* forward or backward or don't care */
} IndexScan;
@@ -508,9 +542,12 @@ typedef struct IndexOnlyScan
Scan scan;
Oid indexid; /* OID of index to scan */
List *indexqual; /* list of index quals (usually OpExprs) */
- List *recheckqual; /* index quals in recheckable form */
+ List *recheckqual; /* index quals in
+ * recheckable form */
List *indexorderby; /* list of index ORDER BY exprs */
- List *indextlist; /* TargetEntry list describing index's cols */
+ List *indextlist; /* TargetEntry list
+ * describing index's
+ * cols */
ScanDirection indexorderdir; /* forward or backward or don't care */
} IndexOnlyScan;
@@ -535,9 +572,11 @@ typedef struct BitmapIndexScan
{
Scan scan;
Oid indexid; /* OID of index to scan */
- bool isshared; /* Create shared bitmap if set */
+ bool isshared; /* Create shared bitmap
+ * if set */
List *indexqual; /* list of index quals (OpExprs) */
- List *indexqualorig; /* the same in original form */
+ List *indexqualorig; /* the same in original
+ * form */
} BitmapIndexScan;
/* ----------------
@@ -552,7 +591,8 @@ typedef struct BitmapIndexScan
typedef struct BitmapHeapScan
{
Scan scan;
- List *bitmapqualorig; /* index quals, in standard expr form */
+ List *bitmapqualorig; /* index quals, in
+ * standard expr form */
} BitmapHeapScan;
/* ----------------
@@ -612,7 +652,7 @@ typedef enum SubqueryScanStatus
typedef struct SubqueryScan
{
Scan scan;
- Plan *subplan;
+ Plan *subplan pg_node_attr(query_jumble_ignore);
SubqueryScanStatus scanstatus;
} SubqueryScan;
@@ -654,8 +694,11 @@ typedef struct TableFuncScan
typedef struct CteScan
{
Scan scan;
- int ctePlanId; /* ID of init SubPlan for CTE */
- int cteParam; /* ID of Param representing CTE output */
+ int ctePlanId; /* ID of init SubPlan
+ * for CTE */
+ int cteParam; /* ID of Param
+ * representing CTE
+ * output */
} CteScan;
/* ----------------
@@ -675,7 +718,9 @@ typedef struct NamedTuplestoreScan
typedef struct WorkTableScan
{
Scan scan;
- int wtParam; /* ID of Param representing work table */
+ int wtParam; /* ID of Param
+ * representing work
+ * table */
} WorkTableScan;
/* ----------------
@@ -722,17 +767,26 @@ typedef struct ForeignScan
{
Scan scan;
CmdType operation; /* SELECT/INSERT/UPDATE/DELETE */
- Index resultRelation; /* direct modification target's RT index */
- Oid checkAsUser; /* user to perform the scan as; 0 means to
- * check as current user */
+ Index resultRelation; /* direct modification
+ * target's RT index */
+ Oid checkAsUser; /* user to perform the
+ * scan as; 0 means to
+ * check as current user */
Oid fs_server; /* OID of foreign server */
- List *fdw_exprs; /* expressions that FDW may evaluate */
- List *fdw_private; /* private data for FDW */
- List *fdw_scan_tlist; /* optional tlist describing scan tuple */
- List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
- Bitmapset *fs_relids; /* base+OJ RTIs generated by this scan */
- Bitmapset *fs_base_relids; /* base RTIs generated by this scan */
- bool fsSystemCol; /* true if any "system column" is needed */
+ List *fdw_exprs; /* expressions that FDW
+ * may evaluate */
+ List *fdw_private pg_node_attr(query_jumble_ignore); /* private data for FDW */
+ List *fdw_scan_tlist; /* optional tlist
+ * describing scan tuple */
+ List *fdw_recheck_quals; /* original quals not in
+ * scan.plan.qual */
+ Bitmapset *fs_relids; /* base+OJ RTIs
+ * generated by this
+ * scan */
+ Bitmapset *fs_base_relids; /* base RTIs generated
+ * by this scan */
+ bool fsSystemCol; /* true if any "system
+ * column" is needed */
} ForeignScan;
/* ----------------
@@ -753,20 +807,27 @@ struct CustomScanMethods;
typedef struct CustomScan
{
Scan scan;
- uint32 flags; /* mask of CUSTOMPATH_* flags, see
- * nodes/extensible.h */
- List *custom_plans; /* list of Plan nodes, if any */
- List *custom_exprs; /* expressions that custom code may evaluate */
- List *custom_private; /* private data for custom code */
- List *custom_scan_tlist; /* optional tlist describing scan tuple */
- Bitmapset *custom_relids; /* RTIs generated by this scan */
+ uint32 flags; /* mask of CUSTOMPATH_*
+ * flags, see
+ * nodes/extensible.h */
+ List *custom_plans; /* list of Plan nodes,
+ * if any */
+ List *custom_exprs; /* expressions that
+ * custom code may
+ * evaluate */
+ List *custom_private pg_node_attr(query_jumble_ignore); /* private data for
+ * custom code */
+ List *custom_scan_tlist; /* optional tlist
+ * describing scan tuple */
+ Bitmapset *custom_relids; /* RTIs generated by
+ * this scan */
/*
* NOTE: The method field of CustomScan is required to be a pointer to a
* static table of callback functions. So we don't copy the table itself,
* just reference the original one.
*/
- const struct CustomScanMethods *methods;
+ const struct CustomScanMethods *methods pg_node_attr(query_jumble_ignore);
} CustomScan;
/*
@@ -826,7 +887,7 @@ typedef struct NestLoop
typedef struct NestLoopParam
{
- pg_node_attr(no_equal, no_query_jumble)
+ pg_node_attr(no_equal)
NodeTag type;
int paramno; /* number of the PARAM_EXEC Param to set */
@@ -932,7 +993,7 @@ typedef struct Memoize
* The maximum number of entries that the planner expects will fit in the
* cache, or 0 if unknown
*/
- uint32 est_entries;
+ uint32 est_entries pg_node_attr(query_jumble_ignore);
/* paramids from param_exprs */
Bitmapset *keyparamids;
@@ -1028,7 +1089,7 @@ typedef struct Agg
Oid *grpCollations pg_node_attr(array_size(numCols));
/* estimated number of groups in input */
- long numGroups;
+ long numGroups pg_node_attr(query_jumble_ignore);
/* for pass-by-ref transition data */
uint64 transitionSpace;
@@ -1154,12 +1215,19 @@ typedef struct Unique
typedef struct Gather
{
Plan plan;
- int num_workers; /* planned number of worker processes */
- int rescan_param; /* ID of Param that signals a rescan, or -1 */
- bool single_copy; /* don't execute plan more than once */
- bool invisible; /* suppress EXPLAIN display (for testing)? */
- Bitmapset *initParam; /* param id's of initplans which are referred
- * at gather or one of its child nodes */
+ /* planned number of worker processes */
+ int num_workers;
+ /* ID of Param that signals a rescan, or -1 */
+ int rescan_param;
+ /* don't execute plan more than once */
+ bool single_copy;
+ /* suppress EXPLAIN display (for testing)? */
+ bool invisible;
+ /*
+ * param id's of initplans which are referred at gather or one of its
+ * child nodes
+ */
+ Bitmapset *initParam;
} Gather;
/* ------------
@@ -1217,11 +1285,16 @@ typedef struct Hash
* needed to put them into the hashtable.
*/
List *hashkeys; /* hash keys for the hashjoin condition */
- Oid skewTable; /* outer join key's table OID, or InvalidOid */
- AttrNumber skewColumn; /* outer join key's column #, or zero */
- bool skewInherit; /* is outer join rel an inheritance tree? */
+ Oid skewTable; /* outer join key's
+ * table OID, or
+ * InvalidOid */
+ AttrNumber skewColumn; /* outer join key's
+ * column #, or zero */
+ bool skewInherit; /* is outer join rel an
+ * inheritance tree? */
/* all other info is in the parent HashJoin node */
- Cardinality rows_total; /* estimate total rows if parallel_aware */
+ Cardinality rows_total pg_node_attr(query_jumble_ignore); /* estimate total rows
+ * if parallel_aware */
} Hash;
/* ----------------
@@ -1252,7 +1325,7 @@ typedef struct SetOp
bool *cmpNullsFirst pg_node_attr(array_size(numCols));
/* estimated number of groups in left input */
- long numGroups;
+ long numGroups pg_node_attr(query_jumble_ignore);
} SetOp;
/* ----------------
@@ -1267,8 +1340,10 @@ typedef struct SetOp
typedef struct LockRows
{
Plan plan;
- List *rowMarks; /* a list of PlanRowMark's */
- int epqParam; /* ID of Param for EvalPlanQual re-eval */
+ List *rowMarks; /* a list of
+ * PlanRowMark's */
+ int epqParam; /* ID of Param for
+ * EvalPlanQual re-eval */
} LockRows;
/* ----------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 839e71d52f4..85702f75070 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1075,8 +1075,6 @@ typedef struct SubLink
*/
typedef struct SubPlan
{
- pg_node_attr(no_query_jumble)
-
Expr xpr;
/* Fields copied from original SubLink: */
SubLinkType subLinkType; /* see above */
@@ -1107,8 +1105,9 @@ typedef struct SubPlan
List *parParam; /* indices of input Params from parent plan */
List *args; /* exprs to pass as parParam values */
/* Estimated execution costs: */
- Cost startup_cost; /* one-time setup cost */
- Cost per_call_cost; /* cost for each subplan evaluation */
+ Cost startup_cost pg_node_attr(query_jumble_ignore); /* one-time setup cost */
+ Cost per_call_cost pg_node_attr(query_jumble_ignore); /* cost for each subplan
+ * evaluation */
} SubPlan;
/*
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 5afa6f3605f..6356dd73fbc 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -15,6 +15,7 @@
#define QUERYJUMBLE_H
#include "nodes/parsenodes.h"
+#include "nodes/pathnodes.h"
/*
* Struct for tracking locations/lengths of constants during normalization
@@ -59,15 +60,27 @@ enum ComputeQueryIdType
COMPUTE_QUERY_ID_REGRESS,
};
+/* Values for the compute_plan_id GUC */
+enum ComputePlanIdType
+{
+ COMPUTE_PLAN_ID_OFF,
+ COMPUTE_PLAN_ID_ON,
+ COMPUTE_PLAN_ID_AUTO,
+ COMPUTE_PLAN_ID_REGRESS,
+};
+
/* GUC parameters */
extern PGDLLIMPORT int compute_query_id;
+extern PGDLLIMPORT int compute_plan_id;
extern const char *CleanQuerytext(const char *query, int *location, int *len);
extern JumbleState *JumbleQuery(Query *query);
extern void EnableQueryId(void);
+extern void EnablePlanId(void);
extern PGDLLIMPORT bool query_id_enabled;
+extern PGDLLIMPORT bool plan_id_enabled;
/*
* Returns whether query identifier computation has been enabled, either
@@ -83,10 +96,25 @@ IsQueryIdEnabled(void)
return query_id_enabled;
}
-/* Functions intended for other users of jumbling (e.g. plan jumbling) */
+/*
+ * Returns whether plan identifier computation has been enabled, either
+ * directly in the GUC or by a module when the setting is 'auto'.
+ */
+static inline bool
+IsPlanIdEnabled(void)
+{
+ if (compute_plan_id == COMPUTE_PLAN_ID_OFF)
+ return false;
+ if (compute_plan_id == COMPUTE_PLAN_ID_ON)
+ return true;
+ return plan_id_enabled;
+}
+
+/* Functions called for plan jumbling or extensions doing their own jumbling */
extern JumbleState *InitializeJumbleState(bool record_clocations);
extern void AppendJumble(JumbleState *jstate, const unsigned char *item, Size size);
extern void JumbleNode(JumbleState *jstate, Node *node);
+extern void JumbleRangeTable(JumbleState *jstate, List *rtable);
extern uint64 HashJumbleState(JumbleState *jstate);
#endif /* QUERYJUMBLE_H */
diff --git a/src/include/utils/backend_status.h b/src/include/utils/backend_status.h
index d3d4ff6c5c9..437a4cec5bf 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -170,6 +170,9 @@ typedef struct PgBackendStatus
/* query identifier, optionally computed using post_parse_analyze_hook */
uint64 st_query_id;
+
+ /* plan identifier, optionally computed after planning */
+ uint64 st_plan_id;
} PgBackendStatus;
@@ -316,6 +319,7 @@ extern void pgstat_clear_backend_activity_snapshot(void);
/* Activity reporting functions */
extern void pgstat_report_activity(BackendState state, const char *cmd_str);
extern void pgstat_report_query_id(uint64 query_id, bool force);
+extern void pgstat_report_plan_id(uint64 query_id, uint64 plan_id, bool force);
extern void pgstat_report_tempfile(size_t filesize);
extern void pgstat_report_appname(const char *appname);
extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -323,6 +327,7 @@ extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
int buflen);
extern uint64 pgstat_get_my_query_id(void);
+extern uint64 pgstat_get_my_plan_id(void);
extern BackendType pgstat_get_backend_type_by_proc_number(ProcNumber procNumber);
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index ee31e41d508..8bfa3c1a5f9 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -699,6 +699,17 @@ select explain_filter('explain (verbose) create table test_ctas as select 1');
Query Identifier: N
(3 rows)
+-- Test compute_plan_id
+set compute_plan_id = on;
+select explain_filter('explain (verbose) select * from int8_tbl i8');
+ explain_filter
+----------------------------------------------------------------
+ Seq Scan on public.int8_tbl i8 (cost=N.N..N.N rows=N width=N)
+ Output: q1, q2
+ Query Identifier: N
+ Plan Identifier: N
+(4 rows)
+
-- Test SERIALIZE option
select explain_filter('explain (analyze,buffers off,serialize) select * from int8_tbl i8');
explain_filter
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 3361f6a69c9..6778b79e658 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1760,9 +1760,10 @@ pg_stat_activity| SELECT s.datid,
s.backend_xid,
s.backend_xmin,
s.query_id,
+ s.plan_id,
s.query,
s.backend_type
- FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
LEFT JOIN pg_database d ON ((s.datid = d.oid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
pg_stat_all_indexes| SELECT c.oid AS relid,
@@ -1890,7 +1891,7 @@ pg_stat_gssapi| SELECT pid,
gss_princ AS principal,
gss_enc AS encrypted,
gss_delegation AS credentials_delegated
- FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
WHERE (client_port IS NOT NULL);
pg_stat_io| SELECT backend_type,
object,
@@ -2096,7 +2097,7 @@ pg_stat_replication| SELECT s.pid,
w.sync_priority,
w.sync_state,
w.reply_time
- FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
pg_stat_replication_slots| SELECT s.slot_name,
@@ -2130,7 +2131,7 @@ pg_stat_ssl| SELECT pid,
ssl_client_dn AS client_dn,
ssl_client_serial AS client_serial,
ssl_issuer_dn AS issuer_dn
- FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
WHERE (client_port IS NOT NULL);
pg_stat_subscription| SELECT su.oid AS subid,
su.subname,
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index 0bafa870496..d787ad2cda3 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -167,6 +167,10 @@ select explain_filter('explain (verbose) select * from int8_tbl i8');
select explain_filter('explain (verbose) declare test_cur cursor for select * from int8_tbl');
select explain_filter('explain (verbose) create table test_ctas as select 1');
+-- Test compute_plan_id
+set compute_plan_id = on;
+select explain_filter('explain (verbose) select * from int8_tbl i8');
+
-- Test SERIALIZE option
select explain_filter('explain (analyze,buffers off,serialize) select * from int8_tbl i8');
select explain_filter('explain (analyze,serialize text,buffers,timing off) select * from int8_tbl i8');
--
2.47.1