Dear PostgreSQL Hackers,
I’ve been testing the proposed v5 plan id work and found out instability of
computing the plan identifier after feeding different query texts that
produces the same physical plan trees but with different plan ids. The main
pattern regards with fields of Plan node structures that depend on positions of
RTEs in a RTE list.
For your convenience, I’ve attached minimal reproducible examples that
demonstrate this pattern. Both are based on the v6-0001 patch, which
incorporates v5-0001..v5-0003 changes and fixes jumbling empty arrays.
```
create table foo1 (num int);
create table foo2 (num int);
insert into foo1 (num)
select *
from generate_series(1, 1000);
insert into foo2 (num)
select *
from generate_series(1, 10);
analyze foo1;
analyze foo2;
set compute_plan_id to true;
```
The first example changes join order between two tables:
```
explain (costs off, verbose)
select 1 from foo1 join foo2 on foo1.num = foo2.num;
QUERY PLAN
-------------------------------------
Hash Join
Output: 1
Hash Cond: (foo1.num = foo2.num)
-> Seq Scan on public.foo1
Output: foo1.num
-> Hash
Output: foo2.num
-> Seq Scan on public.foo2
Output: foo2.num
Plan Identifier: 538643160186222168
explain (costs off, verbose)
select 1 from foo2 join foo1 on foo1.num = foo2.num;
QUERY PLAN
--------------------------------------
Hash Join
Output: 1
Hash Cond: (foo1.num = foo2.num)
-> Seq Scan on public.foo1
Output: foo1.num
-> Hash
Output: foo2.num
-> Seq Scan on public.foo2
Output: foo2.num
Plan Identifier: -953143034841089498
```
Here the reordering of relations in the JOIN operator changes their order in
the RTE list, which in turn changes RTE position-sensitive fields like `varno`
in Vars, and those differences leak into jumbling.
The second example assumes transformation of some table expressions to a
new form like CTE-inlining or completely its eliminating. Let's see example:
```
explain (costs off, verbose)
WITH foo_cte as (
SELECT num FROM foo1)
select * from foo_cte;
QUERY PLAN
--------------------------------------
Seq Scan on public.foo1
Output: foo1.num
Plan Identifier: 3494394630757173099
explain (costs off, verbose)
select * from foo1;
QUERY PLAN
--------------------------------------
Seq Scan on public.foo1
Output: num
Plan Identifier: 8116143677260771228
```
In the example with a CTE, RTE list contains the outdated subquery item and the
relation one, whereas without a CTE we have just the relation item. Although
the CTE is inlined, its RTE is not removed from the rtable; as a result,
position-sensitive fields (such as `varno` for Vars) differ from the no-CTE
case.
A possible way to address it during jumbling process:
1. Remove/skip unused RTE list elements.
2. Sort active RTE list elements by some stable criteria.
3. Adjust fields referring to active RTEs.
But the main challenge is identifying all “position-sensitive” fields across
node types efficiently and maintainably. I’d happy to see your feedback on
this issue.
Additionally, I noticed that the `location` field is being jumbled for several
structures (for example, `Var`). Since it’s only a token location, I believe we
should not include it in the final plan id value.
--
Sincerely,
Andrey Kazachkov
From 3d40912f2cba7ba2c2edae06a4a3d016607a1513 Mon Sep 17 00:00:00 2001 From: Andrey Kazachkov <[email protected]> Date: Tue, 16 Dec 2025 19:52:39 +0300 Subject: [PATCH v6] Add plan_id support to the core.
---
doc/src/sgml/config.sgml | 34 ++++
doc/src/sgml/monitoring.sgml | 16 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/commands/explain.c | 17 ++
src/backend/executor/execMain.c | 10 +-
src/backend/nodes/gen_node_support.pl | 34 +++-
src/backend/nodes/queryjumblefuncs.c | 183 +++++++++++-------
src/backend/optimizer/plan/planner.c | 19 ++
src/backend/optimizer/plan/setrefs.c | 9 +
src/backend/postmaster/launch_backend.c | 3 +
src/backend/utils/adt/pgstatfuncs.c | 7 +-
src/backend/utils/misc/guc_parameters.dat | 7 +
src/backend/utils/misc/guc_tables.c | 18 ++
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 | 50 ++---
src/include/nodes/primnodes.h | 4 +-
src/include/nodes/queryjumble.h | 35 +++-
src/include/utils/backend_status.h | 2 +-
src/test/regress/expected/explain.out | 11 ++
src/test/regress/expected/rules.out | 9 +-
src/test/regress/sql/explain.sql | 4 +
23 files changed, 374 insertions(+), 109 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..4cdaf9c49ba 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8864,6 +8864,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 817fd9f4ca7..9e98fd2c198 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -983,6 +983,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 0a0f95f6bb9..35744a82f0b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -919,6 +919,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 5a6390631eb..b21cd142111 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -818,6 +818,23 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
ExplainPropertyInteger("Query Identifier", NULL,
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,
+ queryDesc->plannedstmt->planId, es);
+ }
+
}
/*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 797d8b1ca1c..14f107d45b1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -123,13 +123,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 or plan_id has
+ * already been reported.
*/
pgstat_report_query_id(queryDesc->plannedstmt->queryId, false);
+ pgstat_report_plan_id(queryDesc->plannedstmt->planId, false);
if (ExecutorStart_hook)
(*ExecutorStart_hook) (queryDesc, eflags);
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 9ecddb14231..eab144bd962 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -1291,6 +1291,7 @@ _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_custom = 0;
my $query_jumble_location = 0;
@@ -1303,7 +1304,11 @@ _jumble${n}(JumbleState *jstate, Node *node)
{
$query_jumble_custom = 1;
}
- if ($a eq 'query_jumble_ignore')
+ elsif ($a =~ /^array_size\(([\w.]+)\)$/)
+ {
+ $array_size_field = $1;
+ }
+ elsif ($a eq 'query_jumble_ignore')
{
$query_jumble_ignore = 1;
}
@@ -1317,12 +1322,19 @@ _jumble${n}(JumbleState *jstate, Node *node)
}
}
+ next if $query_jumble_ignore;
+
if ($query_jumble_custom)
{
# Custom function that applies to one field of a node.
print $jff "\tJUMBLE_CUSTOM($n, $f);\n"
unless $query_jumble_ignore;
}
+ elsif ($t eq 'Bitmapset*')
+ {
+ print $jff "\tJUMBLE_BITMAPSET($f);\n"
+ unless $query_jumble_ignore;
+ }
elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
and elem $1, @node_types)
{
@@ -1352,6 +1364,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 ffc230af427..a0d28827269 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -3,32 +3,31 @@
* queryjumblefuncs.c
* Query normalization and fingerprinting.
*
- * Normalization is a process whereby similar queries, typically differing only
- * in their constants (though the exact rules are somewhat more subtle than
- * that) are recognized as equivalent, and are tracked as a single entry. This
- * is particularly useful for non-prepared queries.
+ * Fingerprinting selectively serializes key fields within a tree structure,
+ * such as a Query or Plan tree, to create a unique identifier while ignoring
+ * extraneous details. These essential fields are concatenated into a jumble,
+ * from which a 64-bit hash is computed. Unlike regular serialization, this
+ * approach excludes irrelevant information.
+ *
+ * Use Cases:
*
- * Normalization is implemented by fingerprinting queries, selectively
- * serializing those fields of each query tree's nodes that are judged to be
- * essential to the query. This is referred to as a query jumble. This is
- * distinct from a regular serialization in that various extraneous
- * information is ignored as irrelevant or not essential to the query, such
- * as the collations of Vars and, most notably, the values of constants.
- *
- * This jumble is acquired at the end of parse analysis of each query, and
- * a 64-bit hash of it is stored into the query's Query.queryId field.
- * The server then copies this value around, making it available in plan
- * tree(s) generated from the query. The executor can then use this value
- * to blame query costs on the proper queryId.
- *
- * Arrays of two or more constants and PARAM_EXTERN parameters are "squashed"
- * and contribute only once to the jumble. This has the effect that queries
- * that differ only on the length of such lists have the same queryId.
- *
- *
- * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
+ * 1. In-Core Query Normalization & Identification
*
+ * Fingerprinting is used to normalize query trees by generating a hash stored
+ * in the Query.queryId field. This ID is propagated to plan tree(s), allowing
+ * the executor to attribute query costs on the proper queryId. The process
+ * excludes information like typmod, collation, and most notably, the values
+ * of constants.
+ *
+ * Example: The following queries produce the same queryId:
+ *
+ * SELECT t.* FROM s1.t WHERE c1 = 1;
+ * SELECT t.* FROM s1.t WHERE c1 = 2;
+ *
+ * 2. Modified jumbling logic for extensions
+ *
+ * Extensions can modify the fingerprinting logic for queryId, or fingerprint
+ * other types of trees, such as a plan tree, to compute a plan identifier.
*
* IDENTIFICATION
* src/backend/nodes/queryjumblefuncs.c
@@ -50,6 +49,7 @@
/* 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.
@@ -60,15 +60,21 @@ int compute_query_id = COMPUTE_QUERY_ID_AUTO;
*/
bool query_id_enabled = false;
-static JumbleState *InitJumble(void);
-static int64 DoJumble(JumbleState *jstate, Node *node);
+/*
+ * 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 compute_plan_id directly when we want to know
+ * whether query identifiers are computed in the core or not.
+ */
+bool plan_id_enabled = false;
+
static void AppendJumble(JumbleState *jstate,
const unsigned char *value, Size size);
static void FlushPendingNulls(JumbleState *jstate);
static void RecordConstLocation(JumbleState *jstate,
bool extern_param,
int location, int len);
-static void _jumbleNode(JumbleState *jstate, Node *node);
static void _jumbleList(JumbleState *jstate, Node *node);
static void _jumbleElements(JumbleState *jstate, List *elements, Node *node);
static void _jumbleParam(JumbleState *jstate, Node *node);
@@ -136,13 +142,13 @@ CleanQuerytext(const char *query, int *location, int *len)
JumbleState *
JumbleQuery(Query *query)
{
- JumbleState *jstate;
+ JumbleState *jstate = InitializeJumbleState(true);;
Assert(IsQueryIdEnabled());
- jstate = InitJumble();
-
- query->queryId = DoJumble(jstate, (Node *) query);
+ /* Compute query ID and mark the Query node with it */
+ JumbleNode(jstate, (Node *) query);
+ query->queryId = HashJumbleState(jstate);
/*
* If we are unlucky enough to get a hash of zero, use 1 instead for
@@ -173,44 +179,44 @@ EnableQueryId(void)
}
/*
- * InitJumble
- * Allocate a JumbleState object and make it ready to jumble.
+ * Enables plan identifier computation.
+ *
+ * Third-party plugins can use this function to inform core that they require
+ * a query identifier to be computed.
*/
-static JumbleState *
-InitJumble(void)
+void
+EnablePlanId(void)
{
- JumbleState *jstate;
+ if (compute_plan_id != COMPUTE_PLAN_ID_OFF)
+ plan_id_enabled = true;
+}
- jstate = palloc_object(JumbleState);
+/*
+ * InitializeJumbleState
+ * Allocate a JumbleState object and make it ready to jumble.
+ */
+JumbleState *
+InitializeJumbleState(bool record_clocations)
+{
+ JumbleState *jstate = (JumbleState *) palloc0(sizeof(JumbleState));
/* Set up workspace for query jumbling */
jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
jstate->jumble_len = 0;
- jstate->clocations_buf_size = 32;
- jstate->clocations = (LocationLen *) palloc(jstate->clocations_buf_size *
- sizeof(LocationLen));
- jstate->clocations_count = 0;
- jstate->highest_extern_param_id = 0;
- jstate->pending_nulls = 0;
- jstate->has_squashed_lists = false;
-#ifdef USE_ASSERT_CHECKING
- jstate->total_jumble_len = 0;
-#endif
+
+ if (record_clocations)
+ {
+ jstate->clocations_buf_size = 32;
+ jstate->clocations = (LocationLen *)
+ palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+ }
return jstate;
}
-/*
- * DoJumble
- * Jumble the given Node using the given JumbleState and return the resulting
- * jumble hash.
- */
-static int64
-DoJumble(JumbleState *jstate, Node *node)
+uint64
+HashJumbleState(JumbleState *jstate)
{
- /* Jumble the given node */
- _jumbleNode(jstate, node);
-
/* Flush any pending NULLs before doing the final hash */
if (jstate->pending_nulls > 0)
FlushPendingNulls(jstate);
@@ -219,10 +225,9 @@ DoJumble(JumbleState *jstate, Node *node)
if (jstate->has_squashed_lists)
jstate->highest_extern_param_id = 0;
- /* Process the jumble buffer and produce the hash value */
- return DatumGetInt64(hash_any_extended(jstate->jumble,
- jstate->jumble_len,
- 0));
+ return DatumGetUInt64(hash_any_extended(jstate->jumble,
+ jstate->jumble_len,
+ 0));
}
/*
@@ -398,7 +403,7 @@ static void
RecordConstLocation(JumbleState *jstate, bool extern_param, int location, int len)
{
/* -1 indicates unknown or undefined location */
- if (location >= 0)
+ if (location >= 0 && jstate->clocations_buf_size > 0)
{
/* enlarge array if needed */
if (jstate->clocations_count >= jstate->clocations_buf_size)
@@ -526,7 +531,7 @@ IsSquashableConstantList(List *elements)
}
#define JUMBLE_NODE(item) \
- _jumbleNode(jstate, (Node *) expr->item)
+ JumbleNode(jstate, (Node *) expr->item)
#define JUMBLE_ELEMENTS(list, node) \
_jumbleElements(jstate, (List *) expr->list, node)
#define JUMBLE_LOCATION(location) \
@@ -544,6 +549,16 @@ do { \
else \
AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item)); \
} while (0)
+#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) \
+ do { \
+ if (len) \
+ AppendJumble(jstate, (const unsigned char *) expr->item, sizeof(*(expr->item)) * len); \
+ } while (0)
#define JUMBLE_STRING(str) \
do { \
if (expr->str) \
@@ -557,8 +572,8 @@ do { \
#include "queryjumblefuncs.funcs.c"
-static void
-_jumbleNode(JumbleState *jstate, Node *node)
+void
+JumbleNode(JumbleState *jstate, Node *node)
{
Node *expr = node;
#ifdef USE_ASSERT_CHECKING
@@ -612,7 +627,7 @@ _jumbleList(JumbleState *jstate, Node *node)
{
case T_List:
foreach(l, expr)
- _jumbleNode(jstate, lfirst(l));
+ JumbleNode(jstate, lfirst(l));
break;
case T_IntList:
foreach(l, expr)
@@ -668,7 +683,7 @@ _jumbleElements(JumbleState *jstate, List *elements, Node *node)
if (!normalize_list)
{
- _jumbleNode(jstate, (Node *) elements);
+ JumbleNode(jstate, (Node *) elements);
}
}
@@ -758,6 +773,40 @@ _jumbleVariableSetStmt(JumbleState *jstate, Node *node)
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;
+ }
+ }
+}
+
/*
* Custom query jumble function for RangeTblEntry.eref.
*/
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8b22c30559b..0309566f602 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"
@@ -578,6 +579,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));
@@ -645,6 +656,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);
+ }
+
/* Allow plugins to take control before we discard "glob" */
if (planner_shutdown_hook)
(*planner_shutdown_hook) (glob, parse, query_string, result);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index cd7ea1e6b58..947bd980c66 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"
@@ -1338,6 +1339,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 98f7c4848c9..b64c88db200 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -118,6 +118,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;
@@ -757,6 +758,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;
@@ -1019,6 +1021,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/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ef6fffe60b9..854c9f8bffd 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -351,7 +351,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);
@@ -665,6 +665,10 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
nulls[30] = true;
else
values[30] = Int64GetDatum(beentry->st_query_id);
+ if (beentry->st_plan_id == INT64CONST(0))
+ nulls[31] = true;
+ else
+ values[31] = UInt64GetDatum(beentry->st_plan_id);
}
else
{
@@ -694,6 +698,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_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..d9f5cf31c34 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -462,6 +462,13 @@
check_hook => 'check_commit_ts_buffers',
},
+{ name => 'compute_plan_id', type => 'enum', context => 'PGC_SUSET', group => 'STATS_MONITORING',
+ short_desc => 'Enables in-core computation of plan identifiers.',
+ variable => 'compute_plan_id',
+ boot_val => 'COMPUTE_PLAN_ID_AUTO',
+ options => 'compute_plan_id_options',
+},
+
{ name => 'compute_query_id', type => 'enum', context => 'PGC_SUSET', group => 'STATS_MONITORING',
short_desc => 'Enables in-core computation of query identifiers.',
variable => 'compute_query_id',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..3a5d7ee32ab 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -318,6 +318,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".
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..3d0e42f083b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -674,6 +674,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 fd9448ec7b9..2bf284e2732 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5653,9 +5653,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 b5ff456ef7f..c98f7b97283 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -189,6 +189,9 @@ typedef struct PlannerGlobal
/* extension state */
void **extension_state pg_node_attr(read_write_ignore);
int extension_state_allocated;
+
+ /* 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 c4393a94321..a5a2ccea1f7 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -70,7 +70,7 @@ typedef struct PlannedStmt
/* query identifier (copied from Query) */
int64 queryId;
- /* plan identifier (can be set by plugins) */
+ /* plan identifier (set when compute_plan_id is enabled or by plugins) */
int64 planId;
/* origin of plan */
@@ -184,7 +184,7 @@ typedef struct PlannedStmt
*/
typedef struct Plan
{
- pg_node_attr(abstract, no_equal, no_query_jumble)
+ pg_node_attr(abstract, no_equal)
NodeTag type;
@@ -192,19 +192,19 @@ typedef struct Plan
* estimated execution costs for plan (see costsize.c for more info)
*/
/* count of disabled nodes */
- int disabled_nodes;
+ int disabled_nodes pg_node_attr(query_jumble_ignore);
/* cost expended before fetching any tuples */
- Cost startup_cost;
+ Cost startup_cost pg_node_attr(query_jumble_ignore);
/* total cost (assuming all tuples fetched) */
- Cost total_cost;
+ Cost total_cost pg_node_attr(query_jumble_ignore);
/*
* planner's estimate of result size of this plan step
*/
/* number of rows plan is expected to emit */
- Cardinality plan_rows;
+ Cardinality plan_rows pg_node_attr(query_jumble_ignore);
/* average row width in bytes */
- int plan_width;
+ int plan_width pg_node_attr(query_jumble_ignore);
/*
* information needed for parallel query
@@ -230,10 +230,10 @@ typedef struct Plan
/* implicitly-ANDed qual conditions */
List *qual;
/* input plan tree(s) */
- struct Plan *lefttree;
- struct Plan *righttree;
+ struct Plan *lefttree pg_node_attr(query_jumble_ignore);
+ struct Plan *righttree pg_node_attr(query_jumble_ignore);
/* Init Plan nodes (un-correlated expr subselects) */
- List *initPlan;
+ List *initPlan pg_node_attr(query_jumble_ignore);
/*
* Information for management of parameter-change-driven rescanning
@@ -351,7 +351,7 @@ typedef struct ModifyTable
/* per-target-table RETURNING tlists */
List *returningLists;
/* per-target-table FDW private data lists */
- List *fdwPrivLists;
+ List *fdwPrivLists pg_node_attr(query_jumble_ignore);
/* indices of FDW DM plans */
Bitmapset *fdwDirectModifyPlans;
/* PlanRowMarks (non-locking only) */
@@ -390,7 +390,7 @@ typedef struct Append
Plan plan;
/* RTIs of appendrel(s) formed by this node */
Bitmapset *apprelids;
- List *appendplans;
+ List *appendplans pg_node_attr(query_jumble_ignore);
/* # of asynchronous plans */
int nasyncplans;
@@ -420,7 +420,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: */
@@ -475,7 +475,7 @@ typedef struct RecursiveUnion
Oid *dupCollations pg_node_attr(array_size(numCols));
/* estimated number of groups in input */
- Cardinality numGroups;
+ long numGroups pg_node_attr(query_jumble_ignore);
} RecursiveUnion;
/* ----------------
@@ -489,7 +489,7 @@ typedef struct RecursiveUnion
typedef struct BitmapAnd
{
Plan plan;
- List *bitmapplans;
+ List *bitmapplans pg_node_attr(query_jumble_ignore);
} BitmapAnd;
/* ----------------
@@ -504,7 +504,7 @@ typedef struct BitmapOr
{
Plan plan;
bool isshared;
- List *bitmapplans;
+ List *bitmapplans pg_node_attr(query_jumble_ignore);
} BitmapOr;
/*
@@ -752,7 +752,7 @@ typedef enum SubqueryScanStatus
typedef struct SubqueryScan
{
Scan scan;
- Plan *subplan;
+ Plan *subplan pg_node_attr(query_jumble_ignore);
SubqueryScanStatus scanstatus;
} SubqueryScan;
@@ -880,7 +880,7 @@ typedef struct ForeignScan
/* expressions that FDW may evaluate */
List *fdw_exprs;
/* private data for FDW */
- List *fdw_private;
+ List *fdw_private pg_node_attr(query_jumble_ignore);
/* optional tlist describing scan tuple */
List *fdw_scan_tlist;
/* original quals not in scan.plan.qual */
@@ -918,7 +918,7 @@ typedef struct CustomScan
/* expressions that custom code may evaluate */
List *custom_exprs;
/* private data for custom code */
- List *custom_private;
+ List *custom_private pg_node_attr(query_jumble_ignore);
/* optional tlist describing scan tuple */
List *custom_scan_tlist;
/* RTIs generated by this scan */
@@ -929,7 +929,7 @@ typedef struct CustomScan
* 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;
/*
@@ -991,7 +991,7 @@ typedef struct NestLoop
typedef struct NestLoopParam
{
- pg_node_attr(no_equal, no_query_jumble)
+ pg_node_attr(no_equal)
NodeTag type;
/* number of the PARAM_EXEC Param to set */
@@ -1099,7 +1099,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;
@@ -1206,7 +1206,7 @@ typedef struct Agg
Oid *grpCollations pg_node_attr(array_size(numCols));
/* estimated number of groups in input */
- Cardinality numGroups;
+ long numGroups pg_node_attr(query_jumble_ignore);
/* for pass-by-ref transition data */
uint64 transitionSpace;
@@ -1415,7 +1415,7 @@ typedef struct Hash
bool skewInherit;
/* all other info is in the parent HashJoin node */
/* estimate total rows if parallel_aware */
- Cardinality rows_total;
+ Cardinality rows_total pg_node_attr(query_jumble_ignore);
} Hash;
/* ----------------
@@ -1446,7 +1446,7 @@ typedef struct SetOp
bool *cmpNullsFirst pg_node_attr(array_size(numCols));
/* estimated number of groups in left input */
- Cardinality numGroups;
+ long numGroups pg_node_attr(query_jumble_ignore);
} SetOp;
/* ----------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4436f2ff6..4ccba6bcd03 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1123,8 +1123,8 @@ 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 dcb36dcb44f..aeb20aa0587 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
@@ -86,15 +87,26 @@ 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
@@ -110,4 +122,25 @@ IsQueryIdEnabled(void)
return query_id_enabled;
}
+/*
+ * 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 JumbleRangeTable(JumbleState *jstate, List *rtable);
+extern void JumbleNode(JumbleState *jstate, Node *node);
+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 3016501ac05..45f00eb93a8 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -172,7 +172,7 @@ typedef struct PgBackendStatus
/* query identifier, optionally computed using post_parse_analyze_hook */
int64 st_query_id;
- /* plan identifier, optionally computed using planner_hook */
+ /* plan identifier, optionally computed after planning */
int64 st_plan_id;
} PgBackendStatus;
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 7c1f26b182c..7ac025cca84 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -743,6 +743,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 4286c266e17..29af9bd2de9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1794,9 +1794,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,
@@ -1926,7 +1927,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,
@@ -2156,7 +2157,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,
@@ -2193,7 +2194,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 ebdab42604b..0449f05c1e1 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -173,6 +173,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');
base-commit: b39013b7b1b116b5d9be51f0919b472b58b3a28d
--
2.43.0
