OSDN Git Service

Remove the queryid_stat_statements field.
authorJulien Rouhaud <julien.rouhaud@free.fr>
Sat, 6 Nov 2021 07:00:21 +0000 (15:00 +0800)
committerKyotaro Horiguchi <horikyota.ntt@gmail.com>
Thu, 25 Nov 2021 10:37:02 +0000 (19:37 +0900)
With pg14 and above core postgres can compute a queryid without any
external extension, so we can simply rely on it.  We do so by asking
for the core to compute a queryid if users allow it.  Note that if
users explicitly disable compute_query_id, we will obey and not record
anything.

For previous versions, automatically detect if a queryid is computed
by some external extensions or not.  If yes, use it as the key queryid
otherwise fallback on internal queryid calculation.

docs/index.html
expected/store.out
expected/store_2.out
pg_store_plans--1.6.sql
pg_store_plans.c
sql/store.sql

index 9a49f99..95574fe 100644 (file)
@@ -26,7 +26,11 @@ CONTENT="text/html; charset=ISO-8859-1">
   <A HREF="http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-SHARED-PRELOAD-LIBRARIES">shared_preload_libraries</A> in
   <TT CLASS="FILENAME">postgresql.conf</TT>, because it requires
   additional shared memory.  This means that a server restart is
-  required to add or remove the module.
+  required to add or remove the
+  module.  <TT CLASS="LITERAL">pg_store_plans</TT> requires the GUC
+  variable <TT CLASS="LITERAL">compute_query_id</TT> to be "on" or
+  "auto". If it is set to
+  "no", <TT CLASS="LITERAL">pg_store_plans</TT> is silently disabled.
 </P>
 <DIV CLASS="SECT2">
 <H2 CLASS="SECT2">1. The <TT CLASS="STRUCTNAME">pg_store_plans</TT>
@@ -62,20 +66,14 @@ View</H2>
 <TR><TD><TT CLASS="STRUCTFIELD">queryid</TT></TD>
     <TD><TT CLASS="TYPE">bigint</TT></TD>
     <TD>&nbsp;</TD>
-    <TD>Internal hash code, computed from the statement's query string.</TD>
+     <TD>Core-generated query ID. If compute_query_id is set to "no", pg_store_plan is silently disabled. This is usable as the join key
+    with <TT CLASS="LITERAL">pg_stat_statements</TT>.</TD></TD>
     </TR>
 <TR><TD><TT CLASS="STRUCTFIELD">planid</TT></TD>
     <TD><TT CLASS="TYPE">bigint</TT></TD>
     <TD>&nbsp;</TD>
-    <TD>Internal hash code, computed from the statement's plan
-    representation.</TD>
-    </TR>
-<TR><TD><TT CLASS="STRUCTFIELD">queryid_stat_statements</TT></TD>
-    <TD><TT CLASS="TYPE">bigint</TT></TD>
-    <TD>&nbsp;</TD>
-    <TD>A copy of pg_stat_statements' query hash code. This is
-    available only when pg_stat_statements is installed.</TD>
-    </TR>
+    <TD>Plan hash code, computed from the normalized plan representation.
+</TR>
 <TR><TD><TT CLASS="STRUCTFIELD">plan</TT></TD>
     <TD><TT CLASS="TYPE">text</TT></TD>
     <TD>&nbsp;</TD>
@@ -210,49 +208,24 @@ calculated in a similar way. Two plans are considered the same if they
 are seemingly equivalent except for the values of literal constants
 or fluctuating values such like costs or measured time. </P>
 
-<P> For PostgreSQL 9.4 or later, you can find the corresponding query
+<P> For PostgreSQL 14 or later, you can find the corresponding query
 for a <TT CLASS="structname">pg_store_plans</TT> entry
 in <TT CLASS="structname">pg_stat_statements</TT> by joining using
-<TT CLASS="structname">queryid_stat_statements</TT>. Otherwise it is
-identified by using <TT CLASS="VARNAME">queryid</TT>
-and <CODE CLASS="FUNCTION">pg_store_plans_hash_query</CODE >, like
-following.
+<TT CLASS="structname">queryid</TT>, like the following.
 
 </P>
 <P>
-<PRE CLASS="SCREEN">SELECT s.query, p.plan FROM pg_store_plans p JOIN pg_stat_statements s ON (pg_store_plans_hash_query(s.query)) = p.queryid;</PRE>
+<PRE CLASS="SCREEN">SELECT s.query, p.plan FROM pg_store_plans p JOIN pg_stat_statements s USING (queryid);</PRE>
 </P>
 
-<P> However plan id is calculated ignoring fluctuating values, the
-values for most recent execution are still displayed
-in <TT CLASS="STRUCTNAME">pg_store_plans.plan</TT>.
+<P> Plan ID is calculated excluding fluctuating properties of plans.  On the other hand, the <TT CLASS="STRUCTNAME">pg_store_plans.plan</TT> view keeps showing the most recent values for those fluctuating properties.
 </P>
-<P> In some cases, <TT CLASS="STRUCTNAME">pg_stat_statements</TT>
-  merges semantically equivalent queries which are considered
-  different by
-<TT CLASS="STRUCTNAME">pg_stat_statements</TT>. In the cases
-correspondent in <TT CLASS="STRUCTNAME">pg_stat_statements</TT> might
-not be found, but there is a small chance that this happenes. In
-contrast, there also is a small chance that some queries might be
-regarded as equivalent and merged into one entry
-in <TT CLASS="STRUCTNAME">pg_store_plans</TT> but differentiated
-in <TT CLASS="STRUCTNAME">pg_stat_statements</TT> mainly for utility
-statements.
-</P>
-
 <P><TT CLASS="STRUCTNAME">pg_store_plans</TT>
    and <TT CLASS="STRUCTNAME">pg_stat_statements</TT> maintain thier
    entries individually so there is certain unavoidable chance
    especially for entries with low execution frequency that no
    correspondent is found.
 </P>
-<P><TT CLASS="STRUCTFIELD">queryid_stat_statements</TT> has the same
-   restriction to <TT CLASS="STRUCTNAME">pg_stat_statements</TT> in
-   terms of stability. Although <TT CLASS="STRUCTFIELD">queryid</TT>
-   and <TT CLASS="STRUCTFIELD">planid</TT>
-   in <TT CLASS="STRUCTNAME">pg_store_plans</TT> doesn't have such a
-   restriction, assuming long-term stability is also discouraged.
-  </P>
 </DIV>
 <DIV CLASS="SECT2">
 <H2 CLASS="SECT2">
@@ -523,58 +496,78 @@ pg_store_plans.log_timing = false
 bench=# SELECT pg_store_plans_reset();
 
 $ pgbench -i bench
-$ pgbench -c10 -t3000 bench
+$ pgbench -c10 -t1000 bench
 
 bench=# \x
 bench=#  SELECT s.query, p.plan,
         p.calls as "plan calls", s.calls as "stmt calls",
         p.total_time / p.calls as "time/call", p.first_call, p.last_call
         FROM pg_stat_statements s
-        JOIN pg_store_plans p ON
-        (p.queryid = pg_store_plans_hash_query(s.query) and p.calls &#60; s.calls)
+        JOIN pg_store_plans p USING (queryid) WHERE p.calls &#60; s.calls
         ORDER BY query ASC, "time/call" DESC;
--[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------
-query      | UPDATE pgbench_branches SET bbalance = bbalance + ? WHERE bid = ?;
-plan       | Update on pgbench_branches  (cost=0.00..8.01 rows=1 width=370) (actual rows=0 loops=1)
-           |   -&#62;  Seq Scan on pgbench_branches  (cost=0.00..8.01 rows=1 width=370) (actual rows=1 loops=1)
-           |         Filter: (bid = 1)
-plan calls | 15583
-stmt calls | 30000
-time/call  | 40.096513957518
-first_call | 2014-04-25 14:29:17.163924+09
-last_call  | 2014-04-25 14:31:29.421635+09
--[ RECORD 2 ]----------------------------------------------------------------------------------------------------------------------------
-query      | UPDATE pgbench_branches SET bbalance = bbalance + ? WHERE bid = ?;
-plan       | Update on pgbench_branches  (cost=0.12..8.14 rows=1 width=370) (actual rows=0 loops=1)
-           |   -&#62;  Index Scan using pgbench_branches_pkey on pgbench_branches  (cost=0.12..8.14 rows=1 width=370) (actual rows=1 loops=1)
-           |         Index Cond: (bid = 1)
-plan calls | 14417
-stmt calls | 30000
-time/call  | 39.1920771311645
-first_call | 2014-04-25 14:31:29.288913+09
-last_call  | 2014-04-25 14:33:31.287061+09
--[ RECORD 3 ]----------------------------------------------------------------------------------------------------------------------------
-query      | UPDATE pgbench_tellers SET tbalance = tbalance + ? WHERE tid = ?;
-plan       | Update on pgbench_tellers  (cost=0.14..8.16 rows=1 width=358) (actual rows=0 loops=1)
-           |   -&#62;  Index Scan using pgbench_tellers_pkey on pgbench_tellers  (cost=0.14..8.16 rows=1 width=358) (actual rows=1 loops=1)
-           |         Index Cond: (tid = 7)
-plan calls | 4
-stmt calls | 30000
-time/call  | 87.0435
-first_call | 2014-04-25 14:30:37.850293+09
-last_call  | 2014-04-25 14:32:38.083977+09
--[ RECORD 4 ]----------------------------------------------------------------------------------------------------------------------------
-query      | UPDATE pgbench_tellers SET tbalance = tbalance + ? WHERE tid = ?;
-plan       | Update on pgbench_tellers  (cost=4.14..8.16 rows=1 width=358) (actual rows=0 loops=1)
-           |   -&#62;  Bitmap Heap Scan on pgbench_tellers  (cost=4.14..8.16 rows=1 width=358) (actual rows=1 loops=1)
-           |         Recheck Cond: (tid = 10)
-           |         -&#62;  Bitmap Index Scan using pgbench_tellers_pkey  (cost=0.00..4.14 rows=1 width=0) (actual rows=1 loops=1)
-           |               Index Cond: (tid = 10)
-plan calls | 29996
-stmt calls | 30000
-time/call  | 33.6455953793834
-first_call | 2014-04-25 14:29:17.162871+09
-last_call  | 2014-04-25 14:33:31.28646+09</PRE>
+-[ RECORD 1 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+query      | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
+plan       | Update on pgbench_tellers  (cost=0.00..7.88 rows=0 width=0)                                                                                                                                                                                                            +
+           |   ->  Seq Scan on pgbench_tellers  (cost=0.00..7.88 rows=1 width=10)                                                                                                                                                                                                   +
+           |         Filter: (tid = 1)
+plan calls | 396
+stmt calls | 10000
+time/call  | 16.15434492676767
+first_call | 2021-11-25 15:11:38.258838+09
+last_call  | 2021-11-25 15:11:40.170291+09
+-[ RECORD 2 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+query      | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
+plan       | Update on pgbench_tellers  (cost=0.14..8.15 rows=0 width=0)                                                                                                                                                                                                            +
+           |   ->  Index Scan using pgbench_tellers_pkey on pgbench_tellers  (cost=0.14..8.15 rows=1 width=10)                                                                                                                                                                      +
+           |         Index Cond: (tid = 8)                                                                                                                                                                                                                                          +
+plan calls | 9604
+stmt calls | 10000
+time/call  | 10.287281695439345
+first_call | 2021-11-25 15:11:40.161556+09
+last_call  | 2021-11-25 15:12:09.957773+09
+-[ RECORD 3 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+query      | select s.query, p.plan, p.calls as "plan calls", s.calls as "stmt calls", p.total_time / p.calls as "time/call", p.first_call, p.last_call from pg_stat_statements s join pg_store_plans p using (queryid) where p.calls < s.calls order by query asc, "time/call" desc
+plan       | Sort  (cost=309.71..313.88 rows=1667 width=104)                                                                                                                                                                                                                        +
+           |   Sort Key: pg_stat_statements.query, ((pg_store_plans.total_time / (pg_store_plans.calls)::double precision)) DESC                                                                                                                                                    +
+           |   ->  Merge Join  (cost=119.66..220.50 rows=1667 width=104)                                                                                                                                                                                                            +
+           |         Merge Cond: (pg_stat_statements.queryid = pg_store_plans.queryid)                                                                                                                                                                                              +
+           |         Join Filter: (pg_store_plans.calls < pg_stat_statements.calls)                                                                                                                                                                                                 +
+           |         ->  Sort  (cost=59.83..62.33 rows=1000 width=48)                                                                                                                                                                                                               +
+           |               Sort Key: pg_stat_statements.queryid                                                                                                                                                                                                                     +
+           |               ->  Function Scan on pg_stat_statements  (cost=0.00..10.00 rows=1000 width=48)                                                                                                                                                                           +
+           |         ->  Sort  (cost=59.83..62.33 rows=1000 width=72)                                                                                                                                                                                                               +
+           |               Sort Key: pg_store_plans.queryid                                                                                                                                                                                                                         +
+           |               ->  Function Scan on pg_store_plans  (cost=0.00..10.00 rows=1000 width=72)                                                                                                                                                                               +
+plan calls | 3
+stmt calls | 4
+time/call  | 16.387161
+first_call | 2021-11-25 15:20:57.978082+09
+last_call  | 2021-11-25 15:23:48.631993+09
+-[ RECORD 4 ]-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+query      | select s.query, p.plan, p.calls as "plan calls", s.calls as "stmt calls", p.total_time / p.calls as "time/call", p.first_call, p.last_call from pg_stat_statements s join pg_store_plans p using (queryid) where p.calls < s.calls order by query asc, "time/call" desc
+plan       | Sort  (cost=309.71..313.88 rows=1667 width=104)                                                                                                                                                                                                                        +
+           |   Sort Key: pg_stat_statements.query, ((pg_store_plans.total_time / (pg_store_plans.calls)::double precision)) DESC                                                                                                                                                    +
+           |   Sort Method: quicksort  Memory: 26kB                                                                                                                                                                                                                                 +
+           |   ->  Merge Join  (cost=119.66..220.50 rows=1667 width=104)                                                                                                                                                                                                            +
+           |         Merge Cond: (pg_stat_statements.queryid = pg_store_plans.queryid)                                                                                                                                                                                              +
+           |         Join Filter: (pg_store_plans.calls < pg_stat_statements.calls)                                                                                                                                                                                                 +
+           |         Rows Removed by Join Filter: 7                                                                                                                                                                                                                                 +
+           |         ->  Sort  (cost=59.83..62.33 rows=1000 width=48)                                                                                                                                                                                                               +
+           |               Sort Key: pg_stat_statements.queryid                                                                                                                                                                                                                     +
+           |               Sort Method: quicksort  Memory: 27kB                                                                                                                                                                                                                     +
+           |               ->  Function Scan on pg_stat_statements  (cost=0.00..10.00 rows=1000 width=48)                                                                                                                                                                           +
+           |         ->  Sort  (cost=59.83..62.33 rows=1000 width=72)                                                                                                                                                                                                               +
+           |               Sort Key: pg_store_plans.queryid                                                                                                                                                                                                                         +
+           |               Sort Method: quicksort  Memory: 30kB                                                                                                                                                                                                                     +
+           |               ->  Function Scan on pg_store_plans  (cost=0.00..10.00 rows=1000 width=72)                                                                                                                                                                               +
+plan calls | 1
+stmt calls | 4
+time/call  | 4.46928
+first_call | 2021-11-25 15:12:27.142535+09
+last_call  | 2021-11-25 15:12:27.142536+09
+
+postgres=#
+</PRE>
 </DIV>
 </DIV>
 <HR>
index a99f5e2..ed526ee 100644 (file)
@@ -77,7 +77,7 @@ BEGIN
 
     FOR r IN SELECT s.query as q, p.plan as p, p.calls as c, p.rows r
              FROM pg_stat_statements s
-             JOIN pg_store_plans p ON (s.queryid = p.queryid_stat_statements)
+             JOIN pg_store_plans p USING (queryid)
              WHERE s.query = 'SELECT count(*) FROM (SELECT * FROM t1) AS x'
              ORDER BY p.calls
     LOOP
index a4c35a7..5f72262 100644 (file)
@@ -77,7 +77,7 @@ BEGIN
 
     FOR r IN SELECT s.query as q, p.plan as p, p.calls as c, p.rows r
              FROM pg_stat_statements s
-             JOIN pg_store_plans p ON (s.queryid = p.queryid_stat_statements)
+             JOIN pg_store_plans p USING (queryid)
              WHERE s.query = 'SELECT count(*) FROM (SELECT * FROM t1) AS x'
              ORDER BY p.calls
     LOOP
index 771e232..d028e17 100644 (file)
@@ -48,7 +48,6 @@ CREATE FUNCTION pg_store_plans(
     OUT dbid oid,
     OUT queryid int8,
     OUT planid int8,
-    OUT queryid_stat_statements int8,
     OUT plan text,
     OUT calls int8,
     OUT total_time float8,
@@ -73,7 +72,7 @@ CREATE FUNCTION pg_store_plans(
     OUT last_call timestamptz
 )
 RETURNS SETOF record
-AS 'MODULE_PATHNAME'
+AS 'MODULE_PATHNAME', 'pg_store_plans_1_6'
 LANGUAGE C;
 
 -- Register a view on the function for ease of use.
index 3fd8cfe..80ea111 100644 (file)
@@ -51,6 +51,9 @@
 #include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#if PG_VERSION_NUM >= 140000
+#include "utils/queryjumble.h"
+#endif
 #include "utils/timestamp.h"
 
 #include "pgsp_json.h"
@@ -73,6 +76,25 @@ static int max_plan_len = 5000;
 #define STICKY_DECREASE_FACTOR (0.50)  /* factor for sticky entries */
 #define USAGE_DEALLOC_PERCENT  5               /* free this % of entries at once */
 
+/* In PostgreSQL 11, queryid becomes a uint64 internally.
+ */
+#if PG_VERSION_NUM >= 110000
+typedef uint64 queryid_t;
+#define PGSP_NO_QUERYID                UINT64CONST(0)
+#else
+typedef uint32 queryid_t;
+#define PGSP_NO_QUERYID                0
+#endif
+
+/*
+ * Extension version number, for supporting older extension versions' objects
+ */
+typedef enum pgspVersion
+{
+       PGSP_V1_5 = 0,
+       PGSP_V1_6
+} pgspVersion;
+
 /*
  * Hashtable key that defines the identity of a hashtable entry.  We separate
  * queries by user and by database even if they are otherwise identical.
@@ -85,7 +107,7 @@ typedef struct pgspHashKey
 {
        Oid                     userid;                 /* user OID */
        Oid                     dbid;                   /* database OID */
-       uint32          queryid;                /* internal query identifier */
+       queryid_t       queryid;                /* query identifier */
        uint32          planid;                 /* plan identifier */
 } pgspHashKey;
 
@@ -119,12 +141,6 @@ typedef struct Counters
 } Counters;
 
 /*
- * The type of queryId has been widen as of PG11. Define substitute type rather
- * than put #if here and there.
- */
-typedef uint64 queryid_t;
-
-/*
  * Statistics per plan
  *
  * NB: see the file read/write code before changing field order here.
@@ -132,7 +148,6 @@ typedef uint64 queryid_t;
 typedef struct pgspEntry
 {
        pgspHashKey     key;                    /* hash key of entry - MUST BE FIRST */
-       queryid_t       queryid;                /* query identifier from stat_statements*/
        Counters        counters;               /* the statistics for this query */
        int                     plan_len;               /* # of valid bytes in query string */
        int                     encoding;               /* query encoding */
@@ -220,9 +235,22 @@ static bool log_triggers;          /* whether to log trigger statistics  */
 static int  plan_format;       /* Plan representation style in
                                                                 * pg_store_plans.plan  */
 
-#define pgsp_enabled() \
+#if PG_VERSION_NUM >= 140000
+/*
+ * For pg14 and later, we rely on core queryid calculation.  If
+ * it's not available it means that the admin explicitly refused to
+ * compute it, for performance reason or other.  In that case, we
+ * will also consider that this extension is disabled.
+ */
+#define pgsp_enabled(q) \
+       ((track_level == TRACK_LEVEL_ALL || \
+       (track_level == TRACK_LEVEL_TOP && nested_level == 0)) && \
+       (q != PGSP_NO_QUERYID))
+#else
+#define pgsp_enabled(q) \
        (track_level == TRACK_LEVEL_ALL || \
        (track_level == TRACK_LEVEL_TOP && nested_level == 0))
+#endif
 
 /*---- Function declarations ----*/
 
@@ -242,6 +270,7 @@ Datum               pg_store_plans_textplan(PG_FUNCTION_ARGS);
 PG_FUNCTION_INFO_V1(pg_store_plans_reset);
 PG_FUNCTION_INFO_V1(pg_store_plans_hash_query);
 PG_FUNCTION_INFO_V1(pg_store_plans);
+PG_FUNCTION_INFO_V1(pg_store_plans_1_6);
 PG_FUNCTION_INFO_V1(pg_store_plans_shorten);
 PG_FUNCTION_INFO_V1(pg_store_plans_normalize);
 PG_FUNCTION_INFO_V1(pg_store_plans_jsonplan);
@@ -275,9 +304,11 @@ static void pgsp_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                                        QueryEnvironment *queryEnv,
                                        DestReceiver *dest, COMPTAG_TYPE *completionTag);
 static uint32 hash_query(const char* query);
-static void pgsp_store(char *plan, uint32 queryId, queryid_t queryId_pgss,
+static void pgsp_store(char *plan, queryid_t queryId,
                   double total_time, uint64 rows,
                   const BufferUsage *bufusage);
+static void pg_store_plans_internal(FunctionCallInfo fcinfo,
+                                                                       pgspVersion api_version);
 static Size shared_mem_size(void);
 static pgspEntry *entry_alloc(pgspHashKey *key, const char *query,
                        int plan_len, bool sticky);
@@ -301,6 +332,14 @@ _PG_init(void)
        if (!process_shared_preload_libraries_in_progress)
                return;
 
+#if PG_VERSION_NUM >= 140000
+       /*
+        * Inform the postmaster that we want to enable query_id calculation if
+        * compute_query_id is set to auto.
+        */
+       EnableQueryId();
+#endif
+
        /*
         * Define (or redefine) custom GUC variables.
         */
@@ -716,6 +755,7 @@ pgsp_ExecutorStart(QueryDesc *queryDesc, int eflags)
                        (log_timing ? 0: INSTRUMENT_ROWS)|
                        (log_buffers ? INSTRUMENT_BUFFERS : 0);
        }
+
        if (prev_ExecutorStart)
                prev_ExecutorStart(queryDesc, eflags);
        else
@@ -725,7 +765,8 @@ pgsp_ExecutorStart(QueryDesc *queryDesc, int eflags)
         * Set up to track total elapsed time in ExecutorRun. Allocate in per-query
         * context so as to be free at ExecutorEnd.
         */
-       if (queryDesc->totaltime == NULL && pgsp_enabled())
+       if (queryDesc->totaltime == NULL &&
+                       pgsp_enabled(queryDesc->plannedstmt->queryId))
        {
                MemoryContext oldcxt;
 
@@ -801,12 +842,17 @@ pgsp_ExecutorEnd(QueryDesc *queryDesc)
                 */
                InstrEndLoop(queryDesc->totaltime);
 
-               if (pgsp_enabled() &&
+               if (pgsp_enabled(queryDesc->plannedstmt->queryId) &&
+                       queryDesc->totaltime->total &&
                        queryDesc->totaltime->total >=
                        (double)min_duration / 1000.0)
                {
-                       ExplainState *es     = NewExplainState();
-                       StringInfo        es_str = es->str;
+                       queryid_t         queryid;
+                       ExplainState *es;
+                       StringInfo        es_str;
+
+                       es = NewExplainState();
+                       es_str = es->str;
 
                        es->analyze = queryDesc->instrument_options;
                        es->verbose = log_verbose;
@@ -828,9 +874,24 @@ pgsp_ExecutorEnd(QueryDesc *queryDesc)
                        es_str->data[0] = '{';
                        es_str->data[es_str->len - 1] = '}';
 
+                       queryid = queryDesc->plannedstmt->queryId;
+#if PG_VERSION_NUM < 140000
+                       /*
+                        * For versions before pg14, a queryid is only available if
+                        * pg_stat_statements extension (or similar) if configured.  We
+                        * don't want a hard requirement for such an extension so fallback
+                        * to an internal queryid calculation in some case.
+                        * For pg14 and above, core postgres can compute a queryid so we
+                        * will rely on it.
+                        */
+                       if (queryid == PGSP_NO_QUERYID)
+                               queryid = (queryid_t) hash_query(queryDesc->sourceText);
+#else
+                       Assert(queryid != PGSP_NO_QUERYID);
+#endif
+
                        pgsp_store(es_str->data,
-                                               hash_query(queryDesc->sourceText),
-                                               queryDesc->plannedstmt->queryId,
+                                               queryid,
                                                queryDesc->totaltime->total * 1000.0,   /* convert to msec */
                                                queryDesc->estate->es_processed,
                                                &queryDesc->totaltime->bufusage);
@@ -895,6 +956,10 @@ hash_query(const char* query)
        queryid = hash_any((const unsigned char*)normquery, strlen(normquery));
        pfree(normquery);
 
+       /* If we are unlucky enough to get a hash of zero, use 1 instead */
+       if (queryid == 0)
+               queryid = 1;
+
        return queryid;
 }
 
@@ -906,7 +971,7 @@ hash_query(const char* query)
  * value of the given plan, which is calculated in ths function.
  */
 static void
-pgsp_store(char *plan, uint32 queryId, queryid_t queryId_pgss,
+pgsp_store(char *plan, queryid_t queryId,
                   double total_time, uint64 rows,
                   const BufferUsage *bufusage)
 {
@@ -918,7 +983,7 @@ pgsp_store(char *plan, uint32 queryId, queryid_t queryId_pgss,
        char       *shorten_plan = NULL;
        volatile pgspEntry *e;
 
-       Assert(plan != NULL);
+       Assert(plan != NULL && queryId != PGSP_NO_QUERYID);
 
        /* Safety check... */
        if (!shared_state || !hash_table)
@@ -977,8 +1042,6 @@ pgsp_store(char *plan, uint32 queryId, queryid_t queryId_pgss,
        e = (volatile pgspEntry *) entry;
        SpinLockAcquire(&e->mutex);
 
-       e->queryid = queryId_pgss;
-
        /* "Unstick" entry if it was previously sticky */
        if (e->counters.calls == 0)
        {
@@ -1058,14 +1121,34 @@ pg_store_plans_reset(PG_FUNCTION_ARGS)
        PG_RETURN_VOID();
 }
 
-#define PG_STORE_PLANS_COLS                    27
+/* Number of output arguments (columns) for various API versions */
+#define PG_STORE_PLANS_COLS_V1_5       27
+#define PG_STORE_PLANS_COLS_V1_6       26
+#define PG_STORE_PLANS_COLS                    27      /* maximum of above */
 
 /*
  * Retrieve statement statistics.
  */
 Datum
+pg_store_plans_1_6(PG_FUNCTION_ARGS)
+{
+       pg_store_plans_internal(fcinfo, PGSP_V1_6);
+
+       return (Datum) 0;
+}
+
+Datum
 pg_store_plans(PG_FUNCTION_ARGS)
 {
+       pg_store_plans_internal(fcinfo, PGSP_V1_5);
+
+       return (Datum) 0;
+}
+
+static void
+pg_store_plans_internal(FunctionCallInfo fcinfo,
+                                               pgspVersion api_version)
+{
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
        TupleDesc       tupdesc;
        Tuplestorestate *tupstore;
@@ -1115,7 +1198,6 @@ pg_store_plans(PG_FUNCTION_ARGS)
                bool            nulls[PG_STORE_PLANS_COLS];
                int                     i = 0;
                int64           queryid      = entry->key.queryid;
-               int64           queryid_stmt = entry->queryid;
                int64           planid       = entry->key.planid;
                Counters        tmp;
                double          stddev;
@@ -1129,16 +1211,17 @@ pg_store_plans(PG_FUNCTION_ARGS)
                {
                        values[i++] = Int64GetDatumFast(queryid);
                        values[i++] = Int64GetDatumFast(planid);
-                       values[i++] = Int64GetDatumFast(queryid_stmt);
+                       if (api_version == PGSP_V1_5)
+                               values[i++] = ObjectIdGetDatum(queryid);
                }
                else
                {
                        values[i++] = Int64GetDatumFast(0);
                        values[i++] = Int64GetDatumFast(0);
-                       values[i++] = Int64GetDatumFast(0);
+                       if (api_version == PGSP_V1_5)
+                               values[i++] = Int64GetDatumFast(0);
                }
 
-
                if (is_allowed_role || entry->key.userid == userid)
                {
                        char       *pstr = entry->plan;
@@ -1224,7 +1307,10 @@ pg_store_plans(PG_FUNCTION_ARGS)
                values[i++] = Float8GetDatumFast(tmp.blk_write_time);
                values[i++] = TimestampTzGetDatum(tmp.first_call);
                values[i++] = TimestampTzGetDatum(tmp.last_call);
-               Assert(i == PG_STORE_PLANS_COLS);
+
+               Assert(i == (api_version == PGSP_V1_5 ? PG_STORE_PLANS_COLS_V1_5 :
+                                        api_version == PGSP_V1_6 ? PG_STORE_PLANS_COLS_V1_6 :
+                                        -1 /* fail if you forget to update this assert */ ));
 
                tuplestore_putvalues(tupstore, tupdesc, values, nulls);
        }
@@ -1233,8 +1319,6 @@ pg_store_plans(PG_FUNCTION_ARGS)
 
        /* clean up and return the tuplestore */
        tuplestore_donestoring(tupstore);
-
-       return (Datum) 0;
 }
 
 /*
index f65c462..46e21e0 100644 (file)
@@ -39,7 +39,7 @@ BEGIN
 
     FOR r IN SELECT s.query as q, p.plan as p, p.calls as c, p.rows r
              FROM pg_stat_statements s
-             JOIN pg_store_plans p ON (s.queryid = p.queryid_stat_statements)
+             JOIN pg_store_plans p USING (queryid)
              WHERE s.query = 'SELECT count(*) FROM (SELECT * FROM t1) AS x'
              ORDER BY p.calls
     LOOP