Hi Alena,

On Wed, 2023-10-25 at 16:25 +0300, Alena Rybakina wrote:
>  Hi! Thank you for your work on the subject.
> 1. I didn't understand why we first try to find pgssEntry with a
> false top_level value, and later find it with a true top_level value.

In case of pg_stat_statements the top_level field is the bool field of
the pgssHashKey struct used as the key for pgss_hash hashtable. When we
are performing a reset only userid, dbid and queryid values are
provided. We need to reset both top-level and non-top level entries. We
have only one way to get them all from a hashtable - search for entries
having top_level=true and with top_level=false in their keys.

> 2. And honestly, I think you should change 
>  "Remove the entry if it exists, starting with the non-top-level
> entry." on 
>  "Remove the entry or reset min/max time statistic information and
> the timestamp if it exists, starting with the non-top-level entry."
> And the same with the comment bellow:
> "Also reset the top-level entry if it exists."
>  "Also remove the entry or reset min/max time statistic information
> and the timestamp if it exists."

There are four such places actually - every time when the
SINGLE_ENTRY_RESET macro is used. The logic of reset performed is
described a bit in this macro definition. It seems quite redundant to
repeat this description four times. But I've noted that "remove" terms
should be replaced by "reset".

I've attached v24 with commits updated.

regards, Andrei Zubkov
Postgres Professional

From 4fadc88d5e6c1afe7558393ba99c28d070ac7244 Mon Sep 17 00:00:00 2001
From: Andrei Zubkov <zub...@moonset.ru>
Date: Wed, 25 Oct 2023 17:58:57 +0300
Subject: [PATCH 1/2] pg_stat_statements tests: Add NOT NULL checking of
 pg_stat_statements_reset

This is preliminary patch. It adds NOT NULL checking for the result of
pg_stat_statements_reset() function. It is needed for upcoming patch
"Track statement entry timestamp" that will change the result type of
this function to the timestamp of a reset performed.

Author: Andrei Zubkov (zubkov)

Reviewed by: Julien Rouhaud (rjuju), Hayato Kuroda (ha-kun),
  Yuki Seino (seinoyu), Chengxi Sun (martin-sun),
  Anton Melnikov (antonmel), Darren Rush (darrenr),
  Michael Paquier (michael-kun), Sergei Kornilov (melkij),
  Alena Rybakina (a.rybakina), Andrei Lepikhov (lepikhov)

Discussion: https://www.postgresql.org/message-id/flat/72e80e7b160a6eb189df9ef6f068cce3765d37f8.camel%40moonset.ru
---
 .../pg_stat_statements/expected/cursors.out   |  28 +--
 contrib/pg_stat_statements/expected/dml.out   |  28 +--
 .../expected/level_tracking.out               |  80 ++++----
 .../pg_stat_statements/expected/planning.out  |  18 +-
 .../pg_stat_statements/expected/select.out    |  44 ++--
 .../expected/user_activity.out                | 120 +++++------
 .../pg_stat_statements/expected/utility.out   | 192 +++++++++---------
 contrib/pg_stat_statements/expected/wal.out   |  10 +-
 contrib/pg_stat_statements/sql/cursors.sql    |   6 +-
 contrib/pg_stat_statements/sql/dml.sql        |   6 +-
 .../pg_stat_statements/sql/level_tracking.sql |  12 +-
 contrib/pg_stat_statements/sql/planning.sql   |   4 +-
 contrib/pg_stat_statements/sql/select.sql     |  10 +-
 .../pg_stat_statements/sql/user_activity.sql  |  15 +-
 contrib/pg_stat_statements/sql/utility.sql    |  34 ++--
 contrib/pg_stat_statements/sql/wal.sql        |   2 +-
 16 files changed, 307 insertions(+), 302 deletions(-)

diff --git a/contrib/pg_stat_statements/expected/cursors.out b/contrib/pg_stat_statements/expected/cursors.out
index 46375ea9051..0fc4b2c098d 100644
--- a/contrib/pg_stat_statements/expected/cursors.out
+++ b/contrib/pg_stat_statements/expected/cursors.out
@@ -3,10 +3,10 @@
 --
 -- These tests require track_utility to be enabled.
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- DECLARE
@@ -20,13 +20,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 -------+------+-------------------------------------------------------
      2 |    0 | CLOSE cursor_stats_1
      2 |    0 | DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT $1
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (3 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- FETCH
@@ -59,12 +59,12 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    0 | DECLARE cursor_stats_2 CURSOR WITH HOLD FOR SELECT $1
      1 |    1 | FETCH 1 IN cursor_stats_1
      1 |    1 | FETCH 1 IN cursor_stats_2
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (9 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/dml.out b/contrib/pg_stat_statements/expected/dml.out
index ede47a71acc..f6ac8da5ca2 100644
--- a/contrib/pg_stat_statements/expected/dml.out
+++ b/contrib/pg_stat_statements/expected/dml.out
@@ -81,16 +81,16 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |   12 | SELECT * FROM pgss_dml_tab ORDER BY a
      2 |    4 | SELECT * FROM pgss_dml_tab WHERE a > $1 ORDER BY a
      1 |    8 | SELECT * FROM pgss_dml_tab WHERE a IN ($1, $2, $3, $4, $5)
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
      1 |    0 | SET pg_stat_statements.track_utility = FALSE
      6 |    6 | UPDATE pgss_dml_tab SET b = $1 WHERE a = $2
      1 |    3 | UPDATE pgss_dml_tab SET b = $1 WHERE a > $2
 (10 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- MERGE
@@ -136,16 +136,16 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
        |      |  WHEN NOT MATCHED THEN INSERT (a, b) VALUES ($1, $2)
      1 |    0 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a)               +
        |      |  WHEN NOT MATCHED THEN INSERT (b, a) VALUES ($1, $2)
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (10 rows)
 
 -- check that [temp] table relation extensions are tracked as writes
 CREATE TABLE pgss_extend_tab (a int, b text);
 CREATE TEMP TABLE pgss_extend_temp_tab (a int, b text);
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 INSERT INTO pgss_extend_tab (a, b) SELECT generate_series(1, 1000), 'something';
@@ -166,9 +166,9 @@ FROM pg_stat_statements;
  t               | t               | t          | t
 (1 row)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out
index d924c87b41e..bad20e7f176 100644
--- a/contrib/pg_stat_statements/expected/level_tracking.out
+++ b/contrib/pg_stat_statements/expected/level_tracking.out
@@ -2,10 +2,10 @@
 -- Statement level tracking
 --
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- DO block - top-level tracking.
@@ -29,10 +29,10 @@ SELECT toplevel, calls, query FROM pg_stat_statements
           |       | $$ LANGUAGE plpgsql
 (2 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- DO block - all-level tracking.
@@ -49,31 +49,31 @@ BEGIN
 END; $$;
 SELECT toplevel, calls, query FROM pg_stat_statements
   ORDER BY query COLLATE "C", toplevel;
- toplevel | calls |                query                 
-----------+-------+--------------------------------------
+ toplevel | calls |                       query                        
+----------+-------+----------------------------------------------------
  f        |     1 | DELETE FROM stats_track_tab
  t        |     1 | DELETE FROM stats_track_tab
- t        |     1 | DO $$                               +
-          |       | BEGIN                               +
-          |       |   DELETE FROM stats_track_tab;      +
+ t        |     1 | DO $$                                             +
+          |       | BEGIN                                             +
+          |       |   DELETE FROM stats_track_tab;                    +
           |       | END; $$
- t        |     1 | DO LANGUAGE plpgsql $$              +
-          |       | BEGIN                               +
-          |       |   -- this is a SELECT               +
-          |       |   PERFORM 'hello world'::TEXT;      +
+ t        |     1 | DO LANGUAGE plpgsql $$                            +
+          |       | BEGIN                                             +
+          |       |   -- this is a SELECT                             +
+          |       |   PERFORM 'hello world'::TEXT;                    +
           |       | END; $$
  f        |     1 | SELECT $1::TEXT
- t        |     1 | SELECT pg_stat_statements_reset()
+ t        |     1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
  t        |     1 | SET pg_stat_statements.track = 'all'
 (7 rows)
 
 -- PL/pgSQL function - top-level tracking.
 SET pg_stat_statements.track = 'top';
 SET pg_stat_statements.track_utility = FALSE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$
@@ -111,19 +111,19 @@ SELECT PLUS_ONE(10);
 (1 row)
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |               query               
--------+------+-----------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      2 |    2 | SELECT PLUS_ONE($1)
      2 |    2 | SELECT PLUS_TWO($1)
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (3 rows)
 
 -- PL/pgSQL function - all-level tracking.
 SET pg_stat_statements.track = 'all';
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- we drop and recreate the functions to avoid any caching funnies
@@ -165,13 +165,13 @@ SELECT PLUS_ONE(1);
 (1 row)
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |               query               
--------+------+-----------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      2 |    2 | SELECT (i + $2 + $3)::INTEGER
      2 |    2 | SELECT (i + $2)::INTEGER LIMIT $3
      2 |    2 | SELECT PLUS_ONE($1)
      2 |    2 | SELECT PLUS_TWO($1)
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (5 rows)
 
 DROP FUNCTION PLUS_ONE(INTEGER);
@@ -179,10 +179,10 @@ DROP FUNCTION PLUS_ONE(INTEGER);
 -- pg_stat_statements.track = none
 --
 SET pg_stat_statements.track = 'none';
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 SELECT 1 AS "one";
@@ -202,9 +202,9 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 -------+------+-------
 (0 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/planning.out b/contrib/pg_stat_statements/expected/planning.out
index c3561dd7da3..9effd11fdc8 100644
--- a/contrib/pg_stat_statements/expected/planning.out
+++ b/contrib/pg_stat_statements/expected/planning.out
@@ -3,10 +3,10 @@
 --
 -- These tests require track_planning to be enabled.
 SET pg_stat_statements.track_planning = TRUE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 --
@@ -64,7 +64,7 @@ SELECT plans, calls, rows, query FROM pg_stat_statements
      0 |     1 |    0 | ALTER TABLE stats_plan_test ADD COLUMN x int
      0 |     1 |    0 | CREATE TABLE stats_plan_test ()
      3 |     3 |    3 | SELECT $1
-     0 |     1 |    1 | SELECT pg_stat_statements_reset()
+     0 |     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
      1 |     0 |    0 | SELECT plans, calls, rows, query FROM pg_stat_statements+
        |       |      |   WHERE query NOT LIKE $1 ORDER BY query COLLATE "C"
 (5 rows)
@@ -80,9 +80,9 @@ SELECT plans >= 2 AND plans <= calls AS plans_ok, calls, rows, query FROM pg_sta
 
 -- Cleanup
 DROP TABLE stats_plan_test;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out
index 972539b2c51..dd6c756f67d 100644
--- a/contrib/pg_stat_statements/expected/select.out
+++ b/contrib/pg_stat_statements/expected/select.out
@@ -4,10 +4,10 @@
 CREATE EXTENSION pg_stat_statements;
 SET pg_stat_statements.track_utility = FALSE;
 SET pg_stat_statements.track_planning = TRUE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 --
@@ -138,7 +138,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i
      1 |    1 | SELECT $1 || $2
      0 |    0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
      1 |    2 | WITH t(f) AS (                                                              +
        |      |   VALUES ($1), ($2)                                                         +
        |      | )                                                                           +
@@ -146,10 +146,10 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    1 | select $1::jsonb ? $2
 (12 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 --
@@ -157,10 +157,10 @@ SELECT pg_stat_statements_reset();
 --
 CREATE TABLE pgss_a (id integer PRIMARY KEY);
 CREATE TABLE pgss_b (id integer PRIMARY KEY, a_id integer REFERENCES pgss_a);
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- control query
@@ -236,17 +236,17 @@ SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a
      1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED
      0 | SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"
-     1 | SELECT pg_stat_statements_reset()
+     1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (12 rows)
 
 DROP TABLE pgss_a, pgss_b CASCADE;
 --
 -- access to pg_stat_statements_info view
 --
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 SELECT dealloc FROM pg_stat_statements_info;
@@ -406,9 +406,9 @@ SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%';
      2
 (1 row)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/user_activity.out b/contrib/pg_stat_statements/expected/user_activity.out
index f3c6b6ab326..38faf18c7c5 100644
--- a/contrib/pg_stat_statements/expected/user_activity.out
+++ b/contrib/pg_stat_statements/expected/user_activity.out
@@ -2,10 +2,10 @@
 -- Track user activity and reset them
 --
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 CREATE ROLE regress_stats_user1;
@@ -39,27 +39,27 @@ SELECT 1+1 AS "TWO";
 
 RESET ROLE;
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
-               query               | calls | rows 
------------------------------------+-------+------
- CREATE ROLE regress_stats_user1   |     1 |    0
- CREATE ROLE regress_stats_user2   |     1 |    0
- RESET ROLE                        |     2 |    0
- SELECT $1 AS "ONE"                |     1 |    1
- SELECT $1 AS "ONE"                |     1 |    1
- SELECT $1+$2 AS "TWO"             |     1 |    1
- SELECT $1+$2 AS "TWO"             |     1 |    1
- SELECT pg_stat_statements_reset() |     1 |    1
- SET ROLE regress_stats_user1      |     1 |    0
- SET ROLE regress_stats_user2      |     1 |    0
+                       query                        | calls | rows 
+----------------------------------------------------+-------+------
+ CREATE ROLE regress_stats_user1                    |     1 |    0
+ CREATE ROLE regress_stats_user2                    |     1 |    0
+ RESET ROLE                                         |     2 |    0
+ SELECT $1 AS "ONE"                                 |     1 |    1
+ SELECT $1 AS "ONE"                                 |     1 |    1
+ SELECT $1+$2 AS "TWO"                              |     1 |    1
+ SELECT $1+$2 AS "TWO"                              |     1 |    1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t |     1 |    1
+ SET ROLE regress_stats_user1                       |     1 |    0
+ SET ROLE regress_stats_user2                       |     1 |    0
 (10 rows)
 
 --
 -- Don't reset anything if any of the parameter is NULL
 --
-SELECT pg_stat_statements_reset(NULL);
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset(NULL) IS NOT NULL AS t;
+ t 
+---
+ f
 (1 row)
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
@@ -72,8 +72,8 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT $1 AS "ONE"                                                           |     1 |    1
  SELECT $1+$2 AS "TWO"                                                        |     1 |    1
  SELECT $1+$2 AS "TWO"                                                        |     1 |    1
- SELECT pg_stat_statements_reset($1)                                          |     1 |    1
- SELECT pg_stat_statements_reset()                                            |     1 |    1
+ SELECT pg_stat_statements_reset($1) IS NOT NULL AS t                         |     1 |    1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t                           |     1 |    1
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     1 |   10
  SET ROLE regress_stats_user1                                                 |     1 |    0
  SET ROLE regress_stats_user2                                                 |     1 |    0
@@ -87,10 +87,11 @@ SELECT pg_stat_statements_reset(
 	(SELECT r.oid FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user2'),
 	(SELECT d.oid FROM pg_database As d where datname = current_database()),
 	(SELECT s.queryid FROM pg_stat_statements AS s
-				WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1));
- pg_stat_statements_reset 
---------------------------
- 
+				WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1))
+	IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
@@ -106,9 +107,10 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
          (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1),                 +|       | 
          (SELECT d.oid FROM pg_database As d where datname = current_database()),+|       | 
          (SELECT s.queryid FROM pg_stat_statements AS s                          +|       | 
-                                 WHERE s.query = $2 LIMIT $3))                    |       | 
- SELECT pg_stat_statements_reset($1)                                              |     1 |    1
- SELECT pg_stat_statements_reset()                                                |     1 |    1
+                                 WHERE s.query = $2 LIMIT $3))                   +|       | 
+         IS NOT NULL AS t                                                         |       | 
+ SELECT pg_stat_statements_reset($1) IS NOT NULL AS t                             |     1 |    1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t                               |     1 |    1
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"     |     2 |   22
  SET ROLE regress_stats_user1                                                     |     1 |    0
  SET ROLE regress_stats_user2                                                     |     1 |    0
@@ -117,12 +119,12 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 -- remove query ('SELECT $1 AS "ONE"') executed by two users
 --
-SELECT pg_stat_statements_reset(0,0,s.queryid)
+SELECT pg_stat_statements_reset(0,0,s.queryid) IS NOT NULL AS t
 	FROM pg_stat_statements AS s WHERE s.query = 'SELECT $1 AS "ONE"';
- pg_stat_statements_reset 
---------------------------
- 
- 
+ t 
+---
+ t
+ t
 (2 rows)
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
@@ -136,11 +138,12 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
          (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1),                 +|       | 
          (SELECT d.oid FROM pg_database As d where datname = current_database()),+|       | 
          (SELECT s.queryid FROM pg_stat_statements AS s                          +|       | 
-                                 WHERE s.query = $2 LIMIT $3))                    |       | 
- SELECT pg_stat_statements_reset($1)                                              |     1 |    1
- SELECT pg_stat_statements_reset($1,$2,s.queryid)                                +|     1 |    2
+                                 WHERE s.query = $2 LIMIT $3))                   +|       | 
+         IS NOT NULL AS t                                                         |       | 
+ SELECT pg_stat_statements_reset($1) IS NOT NULL AS t                             |     1 |    1
+ SELECT pg_stat_statements_reset($1,$2,s.queryid) IS NOT NULL AS t               +|     1 |    2
          FROM pg_stat_statements AS s WHERE s.query = $3                          |       | 
- SELECT pg_stat_statements_reset()                                                |     1 |    1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t                               |     1 |    1
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"     |     3 |   34
  SET ROLE regress_stats_user1                                                     |     1 |    0
  SET ROLE regress_stats_user2                                                     |     1 |    0
@@ -149,11 +152,11 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 -- remove query of a user (regress_stats_user1)
 --
-SELECT pg_stat_statements_reset(r.oid)
+SELECT pg_stat_statements_reset(r.oid) IS NOT NULL AS t
 		FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user1';
- pg_stat_statements_reset 
---------------------------
- 
+ t 
+---
+ t
 (1 row)
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
@@ -166,12 +169,13 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
          (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1),                 +|       | 
          (SELECT d.oid FROM pg_database As d where datname = current_database()),+|       | 
          (SELECT s.queryid FROM pg_stat_statements AS s                          +|       | 
-                                 WHERE s.query = $2 LIMIT $3))                    |       | 
- SELECT pg_stat_statements_reset($1)                                              |     1 |    1
- SELECT pg_stat_statements_reset($1,$2,s.queryid)                                +|     1 |    2
+                                 WHERE s.query = $2 LIMIT $3))                   +|       | 
+         IS NOT NULL AS t                                                         |       | 
+ SELECT pg_stat_statements_reset($1) IS NOT NULL AS t                             |     1 |    1
+ SELECT pg_stat_statements_reset($1,$2,s.queryid) IS NOT NULL AS t               +|     1 |    2
          FROM pg_stat_statements AS s WHERE s.query = $3                          |       | 
- SELECT pg_stat_statements_reset()                                                |     1 |    1
- SELECT pg_stat_statements_reset(r.oid)                                          +|     1 |    1
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t                               |     1 |    1
+ SELECT pg_stat_statements_reset(r.oid) IS NOT NULL AS t                         +|     1 |    1
                  FROM pg_roles AS r WHERE r.rolname = $1                          |       | 
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"     |     4 |   45
  SET ROLE regress_stats_user2                                                     |     1 |    0
@@ -180,16 +184,16 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 -- reset all
 --
-SELECT pg_stat_statements_reset(0,0,0);
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset(0,0,0) IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
-                 query                  | calls | rows 
-----------------------------------------+-------+------
- SELECT pg_stat_statements_reset(0,0,0) |     1 |    1
+                          query                          | calls | rows 
+---------------------------------------------------------+-------+------
+ SELECT pg_stat_statements_reset(0,0,0) IS NOT NULL AS t |     1 |    1
 (1 row)
 
 --
@@ -197,9 +201,9 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 DROP ROLE regress_stats_user1;
 DROP ROLE regress_stats_user2;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/utility.out b/contrib/pg_stat_statements/expected/utility.out
index cc6e898cdf5..07502231259 100644
--- a/contrib/pg_stat_statements/expected/utility.out
+++ b/contrib/pg_stat_statements/expected/utility.out
@@ -3,10 +3,10 @@
 --
 -- These tests require track_utility to be enabled.
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Tables, indexes, triggers
@@ -33,13 +33,13 @@ NOTICE:  table "tab_stats" does not exist, skipping
      1 |    0 | CREATE TEMP TABLE tab_stats (a int, b char(20))
      3 |    0 | DROP TABLE IF EXISTS tab_stats
      1 |    0 | DROP TABLE tab_stats
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (8 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Partitions
@@ -142,13 +142,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    0 | DROP TABLE trigger_tab_stats
      1 |    0 | DROP TYPE stats_type
      1 |    0 | DROP VIEW view_stats
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (39 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Transaction statements
@@ -188,13 +188,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    0 | BEGIN TRANSACTION NOT DEFERRABLE, READ ONLY, READ WRITE, DEFERRABLE
      1 |    0 | BEGIN TRANSACTION READ ONLY, READ WRITE, DEFERRABLE, NOT DEFERRABLE
      7 |    0 | COMMIT WORK
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (8 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Two-phase transactions
@@ -205,19 +205,19 @@ BEGIN;
 PREPARE TRANSACTION 'stat_trans2';
 ROLLBACK PREPARED 'stat_trans2';
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |               query               
--------+------+-----------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      2 |    0 | BEGIN
      1 |    0 | COMMIT PREPARED $1
      2 |    0 | PREPARE TRANSACTION $1
      1 |    0 | ROLLBACK PREPARED $1
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (5 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Savepoints
@@ -235,20 +235,20 @@ ROLLBACK TO sp1;
 RELEASE SAVEPOINT sp1;
 COMMIT;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |               query               
--------+------+-----------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      1 |    0 | BEGIN
      1 |    0 | COMMIT
      3 |    0 | RELEASE $1
      4 |    0 | ROLLBACK TO $1
      4 |    0 | SAVEPOINT $1
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (6 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- EXPLAIN statements
@@ -284,7 +284,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 -------+------+---------------------------------------------------------------------------------
      2 |    0 | EXPLAIN (costs off) SELECT $1
      2 |    0 | EXPLAIN (costs off) SELECT a FROM generate_series($1,$2) AS tab(a) WHERE a = $3
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (3 rows)
 
 -- CALL
@@ -321,10 +321,10 @@ BEGIN
   i2 := i;
   i3 := i3 + i;
 END; $$ LANGUAGE plpgsql;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 CALL sum_one(3);
@@ -346,22 +346,22 @@ CALL in_out(2, 1, 2);
 (1 row)
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |               query               
--------+------+-----------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      2 |    0 | CALL in_out($1, $2, $3)
      1 |    0 | CALL overload($1)
      1 |    0 | CALL overload($1)
      2 |    0 | CALL sum_one($1)
      2 |    0 | CALL sum_two($1,$2)
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (6 rows)
 
 -- COPY
 CREATE TABLE copy_stats (a int, b int);
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Some queries with A_Const nodes.
@@ -391,14 +391,14 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    1 | COPY (SELECT 2) TO STDOUT
      1 |    2 | COPY (UPDATE copy_stats SET b = b + 1 RETURNING *) TO STDOUT
      1 |    2 | COPY (UPDATE copy_stats SET b = b + 2 RETURNING *) TO STDOUT
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (8 rows)
 
 DROP TABLE copy_stats;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- CREATE TABLE AS
@@ -424,13 +424,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
        |      |     FROM generate_series($2, $3) AS tab(a) WHERE a < $4 AND a > $5
      2 |    0 | DROP TABLE ctas_stats_1
      2 |    0 | DROP TABLE ctas_stats_2
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (5 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- CREATE MATERIALIZED VIEW
@@ -450,13 +450,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
        |      |   SELECT a AS col1, $1::int AS col2                               +
        |      |     FROM generate_series($2, $3) AS tab(a) WHERE a < $4 AND a > $5
      2 |    0 | DROP MATERIALIZED VIEW matview_stats_1
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (3 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- CREATE VIEW
@@ -478,13 +478,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
        |      |   SELECT a AS col1, 4::int AS col2                             +
        |      |     FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3
      2 |    0 | DROP VIEW view_stats_1
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (4 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Domains
@@ -499,13 +499,13 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    0 | ALTER DOMAIN domain_stats SET DEFAULT '3'
      1 |    0 | CREATE DOMAIN domain_stats AS int CHECK (VALUE > 0)
      1 |    0 | DROP DOMAIN domain_stats
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (5 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- Execution statements
@@ -534,19 +534,19 @@ DEALLOCATE PREPARE stat_select;
 DEALLOCATE ALL;
 DEALLOCATE PREPARE ALL;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |                 query                 
--------+------+---------------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      2 |    0 | DEALLOCATE $1
      2 |    0 | DEALLOCATE ALL
      2 |    2 | PREPARE stat_select AS SELECT $1 AS a
      1 |    1 | SELECT $1 as a
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (5 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- SET statements.
@@ -572,14 +572,14 @@ SET LOCAL SESSION AUTHORIZATION DEFAULT;
 RESET SESSION AUTHORIZATION;
 COMMIT;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |                      query                      
--------+------+-------------------------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      2 |    0 | BEGIN
      2 |    0 | COMMIT
      2 |    0 | RESET SESSION AUTHORIZATION
      1 |    0 | RESET enable_seqscan
      1 |    0 | RESET work_mem
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
      1 |    0 | SET LOCAL SESSION AUTHORIZATION DEFAULT
      1 |    0 | SET SESSION SESSION AUTHORIZATION DEFAULT
      1 |    0 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED
@@ -591,10 +591,10 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    0 | SET work_mem = '2MB'
 (15 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 --
@@ -652,16 +652,16 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
      1 |    1 | FETCH NEXT pgss_cursor
      1 |   13 | REFRESH MATERIALIZED VIEW pgss_matv
      1 |   10 | SELECT generate_series($1, $2) c INTO pgss_select_into
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (12 rows)
 
 DROP MATERIALIZED VIEW pgss_matv;
 DROP TABLE pgss_ctas;
 DROP TABLE pgss_select_into;
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
 -- SET statements.
@@ -674,20 +674,20 @@ SET enable_seqscan = off;
 SET enable_seqscan = on;
 RESET enable_seqscan;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
- calls | rows |               query               
--------+------+-----------------------------------
+ calls | rows |                       query                        
+-------+------+----------------------------------------------------
      1 |    0 | RESET enable_seqscan
      1 |    0 | RESET work_mem
-     1 |    1 | SELECT pg_stat_statements_reset()
+     1 |    1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
      1 |    0 | SET enable_seqscan = off
      1 |    0 | SET enable_seqscan = on
      2 |    0 | SET work_mem = '1MB'
      1 |    0 | SET work_mem = '2MB'
 (7 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/expected/wal.out b/contrib/pg_stat_statements/expected/wal.out
index 9896ba25363..34a2bf5b033 100644
--- a/contrib/pg_stat_statements/expected/wal.out
+++ b/contrib/pg_stat_statements/expected/wal.out
@@ -17,14 +17,14 @@ FROM pg_stat_statements ORDER BY query COLLATE "C";
 --------------------------------------------------------------+-------+------+---------------------+-----------------------+---------------------
  DELETE FROM pgss_wal_tab WHERE a > $1                        |     1 |    1 | t                   | t                     | t
  INSERT INTO pgss_wal_tab VALUES(generate_series($1, $2), $3) |     1 |   10 | t                   | t                     | t
- SELECT pg_stat_statements_reset()                            |     1 |    1 | f                   | f                     | f
+ SELECT pg_stat_statements_reset() IS NOT NULL AS t           |     1 |    1 | f                   | f                     | f
  SET pg_stat_statements.track_utility = FALSE                 |     1 |    0 | f                   | f                     | t
  UPDATE pgss_wal_tab SET b = $1 WHERE a > $2                  |     1 |    3 | t                   | t                     | t
 (5 rows)
 
-SELECT pg_stat_statements_reset();
- pg_stat_statements_reset 
---------------------------
- 
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
 (1 row)
 
diff --git a/contrib/pg_stat_statements/sql/cursors.sql b/contrib/pg_stat_statements/sql/cursors.sql
index cef6dc9e1b8..61738ac470e 100644
--- a/contrib/pg_stat_statements/sql/cursors.sql
+++ b/contrib/pg_stat_statements/sql/cursors.sql
@@ -4,7 +4,7 @@
 
 -- These tests require track_utility to be enabled.
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- DECLARE
 -- SELECT is normalized.
@@ -14,7 +14,7 @@ DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 2;
 CLOSE cursor_stats_1;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- FETCH
 BEGIN;
@@ -27,4 +27,4 @@ CLOSE cursor_stats_2;
 COMMIT;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/dml.sql b/contrib/pg_stat_statements/sql/dml.sql
index 3b5d2afb858..9986b0a22d3 100644
--- a/contrib/pg_stat_statements/sql/dml.sql
+++ b/contrib/pg_stat_statements/sql/dml.sql
@@ -46,7 +46,7 @@ SELECT * FROM pgss_dml_tab ORDER BY a;
 SELECT * FROM pgss_dml_tab WHERE a IN (1, 2, 3, 4, 5);
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- MERGE
 MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4)
@@ -77,7 +77,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 -- check that [temp] table relation extensions are tracked as writes
 CREATE TABLE pgss_extend_tab (a int, b text);
 CREATE TEMP TABLE pgss_extend_temp_tab (a int, b text);
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 INSERT INTO pgss_extend_tab (a, b) SELECT generate_series(1, 1000), 'something';
 INSERT INTO pgss_extend_temp_tab (a, b) SELECT generate_series(1, 1000), 'something';
 WITH sizes AS (
@@ -92,4 +92,4 @@ SELECT
     SUM(shared_blks_dirtied) >= (SELECT rel_size FROM sizes) AS dirtied_ok
 FROM pg_stat_statements;
 
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/level_tracking.sql b/contrib/pg_stat_statements/sql/level_tracking.sql
index 0c20b8ce69b..f039110601a 100644
--- a/contrib/pg_stat_statements/sql/level_tracking.sql
+++ b/contrib/pg_stat_statements/sql/level_tracking.sql
@@ -3,7 +3,7 @@
 --
 
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- DO block - top-level tracking.
 CREATE TABLE stats_track_tab (x int);
@@ -16,7 +16,7 @@ END;
 $$ LANGUAGE plpgsql;
 SELECT toplevel, calls, query FROM pg_stat_statements
   WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- DO block - all-level tracking.
 SET pg_stat_statements.track = 'all';
@@ -36,7 +36,7 @@ SELECT toplevel, calls, query FROM pg_stat_statements
 -- PL/pgSQL function - top-level tracking.
 SET pg_stat_statements.track = 'top';
 SET pg_stat_statements.track_utility = FALSE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$
 DECLARE
   r INTEGER;
@@ -59,7 +59,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 -- PL/pgSQL function - all-level tracking.
 SET pg_stat_statements.track = 'all';
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- we drop and recreate the functions to avoid any caching funnies
 DROP FUNCTION PLUS_ONE(INTEGER);
@@ -91,10 +91,10 @@ DROP FUNCTION PLUS_ONE(INTEGER);
 -- pg_stat_statements.track = none
 --
 SET pg_stat_statements.track = 'none';
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 SELECT 1 AS "one";
 SELECT 1 + 1 AS "two";
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/planning.sql b/contrib/pg_stat_statements/sql/planning.sql
index a59b9363c4b..46f5d9b951c 100644
--- a/contrib/pg_stat_statements/sql/planning.sql
+++ b/contrib/pg_stat_statements/sql/planning.sql
@@ -4,7 +4,7 @@
 
 -- These tests require track_planning to be enabled.
 SET pg_stat_statements.track_planning = TRUE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 --
 -- [re]plan counting
@@ -28,4 +28,4 @@ SELECT plans >= 2 AND plans <= calls AS plans_ok, calls, rows, query FROM pg_sta
 
 -- Cleanup
 DROP TABLE stats_plan_test;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql
index eef7b0bbf58..eb45cb81ad2 100644
--- a/contrib/pg_stat_statements/sql/select.sql
+++ b/contrib/pg_stat_statements/sql/select.sql
@@ -5,7 +5,7 @@
 CREATE EXTENSION pg_stat_statements;
 SET pg_stat_statements.track_utility = FALSE;
 SET pg_stat_statements.track_planning = TRUE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 --
 -- simple and compound statements
@@ -56,7 +56,7 @@ EXECUTE pgss_test(1);
 DEALLOCATE pgss_test;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 --
 -- queries with locking clauses
@@ -64,7 +64,7 @@ SELECT pg_stat_statements_reset();
 CREATE TABLE pgss_a (id integer PRIMARY KEY);
 CREATE TABLE pgss_b (id integer PRIMARY KEY, a_id integer REFERENCES pgss_a);
 
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- control query
 SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id;
@@ -92,7 +92,7 @@ DROP TABLE pgss_a, pgss_b CASCADE;
 --
 -- access to pg_stat_statements_info view
 --
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 SELECT dealloc FROM pg_stat_statements_info;
 
 -- FROM [ONLY]
@@ -146,4 +146,4 @@ SELECT (
 ) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
 
 SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%';
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/user_activity.sql b/contrib/pg_stat_statements/sql/user_activity.sql
index 4b95edda890..07a5f36fc12 100644
--- a/contrib/pg_stat_statements/sql/user_activity.sql
+++ b/contrib/pg_stat_statements/sql/user_activity.sql
@@ -3,7 +3,7 @@
 --
 
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 CREATE ROLE regress_stats_user1;
 CREATE ROLE regress_stats_user2;
 
@@ -24,7 +24,7 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 -- Don't reset anything if any of the parameter is NULL
 --
-SELECT pg_stat_statements_reset(NULL);
+SELECT pg_stat_statements_reset(NULL) IS NOT NULL AS t;
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
@@ -35,27 +35,28 @@ SELECT pg_stat_statements_reset(
 	(SELECT r.oid FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user2'),
 	(SELECT d.oid FROM pg_database As d where datname = current_database()),
 	(SELECT s.queryid FROM pg_stat_statements AS s
-				WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1));
+				WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1))
+	IS NOT NULL AS t;
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
 -- remove query ('SELECT $1 AS "ONE"') executed by two users
 --
-SELECT pg_stat_statements_reset(0,0,s.queryid)
+SELECT pg_stat_statements_reset(0,0,s.queryid) IS NOT NULL AS t
 	FROM pg_stat_statements AS s WHERE s.query = 'SELECT $1 AS "ONE"';
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
 -- remove query of a user (regress_stats_user1)
 --
-SELECT pg_stat_statements_reset(r.oid)
+SELECT pg_stat_statements_reset(r.oid) IS NOT NULL AS t
 		FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user1';
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
 -- reset all
 --
-SELECT pg_stat_statements_reset(0,0,0);
+SELECT pg_stat_statements_reset(0,0,0) IS NOT NULL AS t;
 SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 --
@@ -63,4 +64,4 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
 --
 DROP ROLE regress_stats_user1;
 DROP ROLE regress_stats_user2;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/utility.sql b/contrib/pg_stat_statements/sql/utility.sql
index 04598e5ae46..83d1894a092 100644
--- a/contrib/pg_stat_statements/sql/utility.sql
+++ b/contrib/pg_stat_statements/sql/utility.sql
@@ -4,7 +4,7 @@
 
 -- These tests require track_utility to be enabled.
 SET pg_stat_statements.track_utility = TRUE;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Tables, indexes, triggers
 CREATE TEMP TABLE tab_stats (a int, b char(20));
@@ -18,7 +18,7 @@ DROP TABLE IF EXISTS tab_stats \;
 DROP TABLE IF EXISTS tab_stats \;
 Drop Table If Exists tab_stats \;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Partitions
 CREATE TABLE pt_stats (a int, b int) PARTITION BY range (a);
@@ -83,7 +83,7 @@ CREATE STATISTICS tab_expr_stats_1 (mcv) ON a, (2*a), (3*b) FROM tab_expr_stats;
 DROP TABLE tab_expr_stats;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Transaction statements
 BEGIN;
@@ -113,7 +113,7 @@ COMMIT;
 BEGIN TRANSACTION NOT DEFERRABLE, READ ONLY, READ WRITE, DEFERRABLE;
 COMMIT;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Two-phase transactions
 BEGIN;
@@ -123,7 +123,7 @@ BEGIN;
 PREPARE TRANSACTION 'stat_trans2';
 ROLLBACK PREPARED 'stat_trans2';
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Savepoints
 BEGIN;
@@ -140,7 +140,7 @@ ROLLBACK TO sp1;
 RELEASE SAVEPOINT sp1;
 COMMIT;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- EXPLAIN statements
 -- A Query is used, normalized by the query jumbling.
@@ -185,7 +185,7 @@ BEGIN
   i2 := i;
   i3 := i3 + i;
 END; $$ LANGUAGE plpgsql;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 CALL sum_one(3);
 CALL sum_one(199);
 CALL sum_two(1,1);
@@ -198,7 +198,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 
 -- COPY
 CREATE TABLE copy_stats (a int, b int);
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 -- Some queries with A_Const nodes.
 COPY (SELECT 1) TO STDOUT;
 COPY (SELECT 2) TO STDOUT;
@@ -210,7 +210,7 @@ COPY (DELETE FROM copy_stats WHERE a = 1 RETURNING *) TO STDOUT;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
 DROP TABLE copy_stats;
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- CREATE TABLE AS
 -- SELECT queries are normalized, creating matching query IDs.
@@ -227,7 +227,7 @@ CREATE TABLE ctas_stats_2 AS
     FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 1;
 DROP TABLE ctas_stats_2;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- CREATE MATERIALIZED VIEW
 -- SELECT queries are normalized, creating matching query IDs.
@@ -240,7 +240,7 @@ CREATE MATERIALIZED VIEW matview_stats_1 AS
     FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3;
 DROP MATERIALIZED VIEW matview_stats_1;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- CREATE VIEW
 CREATE VIEW view_stats_1 AS
@@ -252,7 +252,7 @@ CREATE VIEW view_stats_1 AS
     FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3;
 DROP VIEW view_stats_1;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Domains
 CREATE DOMAIN domain_stats AS int CHECK (VALUE > 0);
@@ -260,7 +260,7 @@ ALTER DOMAIN domain_stats SET DEFAULT '3';
 ALTER DOMAIN domain_stats ADD CONSTRAINT higher_than_one CHECK (VALUE > 1);
 DROP DOMAIN domain_stats;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- Execution statements
 SELECT 1 as a;
@@ -273,7 +273,7 @@ DEALLOCATE PREPARE stat_select;
 DEALLOCATE ALL;
 DEALLOCATE PREPARE ALL;
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- SET statements.
 -- These use two different strings, still they count as one entry.
@@ -299,7 +299,7 @@ RESET SESSION AUTHORIZATION;
 COMMIT;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 --
 -- Track the total number of rows retrieved or affected by the utility
@@ -328,7 +328,7 @@ DROP MATERIALIZED VIEW pgss_matv;
 DROP TABLE pgss_ctas;
 DROP TABLE pgss_select_into;
 
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
 
 -- SET statements.
 -- These use two different strings, still they count as one entry.
@@ -341,4 +341,4 @@ SET enable_seqscan = on;
 RESET enable_seqscan;
 
 SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/wal.sql b/contrib/pg_stat_statements/sql/wal.sql
index 34b21c0fa98..1dc1552a81e 100644
--- a/contrib/pg_stat_statements/sql/wal.sql
+++ b/contrib/pg_stat_statements/sql/wal.sql
@@ -17,4 +17,4 @@ wal_bytes > 0 as wal_bytes_generated,
 wal_records > 0 as wal_records_generated,
 wal_records >= rows as wal_records_ge_rows
 FROM pg_stat_statements ORDER BY query COLLATE "C";
-SELECT pg_stat_statements_reset();
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
-- 
2.39.2

From 274e16cce2365fbe3d43a86d1b7208663a8f3a25 Mon Sep 17 00:00:00 2001
From: Andrei Zubkov <zub...@moonset.ru>
Date: Wed, 25 Oct 2023 18:16:57 +0300
Subject: [PATCH 2/2] pg_stat_statements: Track statement entry timestamp

This patch adds stats_since and minmax_stats_since columns to the
pg_stat_statements view and pg_stat_statements() function. The new min/max reset
mode for the pg_stat_stetments_reset() function is controlled by the
parameter minmax_only.
stat_since column is populated with the current timestamp when a new statement
is added to the pg_stat_statements hashtable. It provides clean information
about statistics collection time interval for each statement. Besides it can be
used by sampling solutions to detect situations when a statement was evicted and
stored again between samples.
Such sampling solution could derive any pg_stat_statements statistic values for
an interval between two samples with the exception of all min/max statistics. To
address this issue this patch adds the ability to reset min/max statistics
independently of the statement reset using the new minmax_only parameter
of the pg_stat_statements_reset(userid oid, dbid oid, queryid bigint,
minmax_only boolean) function. Timestamp of such reset is stored in the
minmax_stats_since field for each statement.
pg_stat_statements_reset() function now returns the timestamp of a reset as a
result.

Author: Andrei Zubkov (zubkov)

Reviewed by: Julien Rouhaud (rjuju), Hayato Kuroda (ha-kun),
  Yuki Seino (seinoyu), Chengxi Sun (martin-sun),
  Anton Melnikov (antonmel), Darren Rush (darrenr),
  Michael Paquier (michael-kun), Sergei Kornilov (melkij),
  Alena Rybakina (a.rybakina), Andrei Lepikhov (lepikhov)

Discussion: https://www.postgresql.org/message-id/flat/72e80e7b160a6eb189df9ef6f068cce3765d37f8.camel%40moonset.ru
---
 contrib/pg_stat_statements/Makefile           |   2 +-
 .../expected/entry_timestamp.out              | 159 ++++++++++++++++++
 .../expected/oldextversions.out               | 116 +++++++------
 contrib/pg_stat_statements/meson.build        |   1 +
 .../pg_stat_statements--1.10--1.11.sql        |  23 ++-
 .../pg_stat_statements/pg_stat_statements.c   | 118 ++++++++++---
 .../sql/entry_timestamp.sql                   | 114 +++++++++++++
 .../pg_stat_statements/sql/oldextversions.sql |   4 +-
 doc/src/sgml/pgstatstatements.sgml            |  69 +++++++-
 9 files changed, 510 insertions(+), 96 deletions(-)
 create mode 100644 contrib/pg_stat_statements/expected/entry_timestamp.out
 create mode 100644 contrib/pg_stat_statements/sql/entry_timestamp.sql

diff --git a/contrib/pg_stat_statements/Makefile b/contrib/pg_stat_statements/Makefile
index eba4a95d91a..aecd1d6a2a0 100644
--- a/contrib/pg_stat_statements/Makefile
+++ b/contrib/pg_stat_statements/Makefile
@@ -19,7 +19,7 @@ LDFLAGS_SL += $(filter -lm, $(LIBS))
 
 REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_stat_statements/pg_stat_statements.conf
 REGRESS = select dml cursors utility level_tracking planning \
-	user_activity wal cleanup oldextversions
+	user_activity wal entry_timestamp cleanup oldextversions
 # Disabled because these tests require "shared_preload_libraries=pg_stat_statements",
 # which typical installcheck users do not have (e.g. buildfarm clients).
 NO_INSTALLCHECK = 1
diff --git a/contrib/pg_stat_statements/expected/entry_timestamp.out b/contrib/pg_stat_statements/expected/entry_timestamp.out
new file mode 100644
index 00000000000..a10c4be6bac
--- /dev/null
+++ b/contrib/pg_stat_statements/expected/entry_timestamp.out
@@ -0,0 +1,159 @@
+--
+-- statement timestamps
+--
+-- planning time is needed during tests
+SET pg_stat_statements.track_planning = TRUE;
+SELECT 1 AS "STMTTS1";
+ STMTTS1 
+---------
+       1
+(1 row)
+
+SELECT now() AS ref_ts \gset
+SELECT 1,2 AS "STMTTS2";
+ ?column? | STMTTS2 
+----------+---------
+        1 |       2
+(1 row)
+
+SELECT stats_since >= :'ref_ts', count(*) FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%'
+GROUP BY stats_since >= :'ref_ts'
+ORDER BY stats_since >= :'ref_ts';
+ ?column? | count 
+----------+-------
+ f        |     1
+ t        |     1
+(2 rows)
+
+SELECT now() AS ref_ts \gset
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_plan_time + max_plan_time = 0
+  ) as minmax_plan_zero,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_stats_since_after_ref,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+ total | minmax_plan_zero | minmax_exec_zero | minmax_stats_since_after_ref | stats_since_after_ref 
+-------+------------------+------------------+------------------------------+-----------------------
+     2 |                0 |                0 |                            0 |                     0
+(1 row)
+
+-- Perform single min/max reset
+SELECT pg_stat_statements_reset(0, 0, queryid, true) AS minmax_reset_ts
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS1%' \gset
+-- check
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_plan_time + max_plan_time = 0
+  ) as minmax_plan_zero,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_stats_since_after_ref,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+ total | minmax_plan_zero | minmax_exec_zero | minmax_stats_since_after_ref | stats_since_after_ref 
+-------+------------------+------------------+------------------------------+-----------------------
+     2 |                1 |                1 |                            1 |                     0
+(1 row)
+
+-- check minmax reset timestamps
+SELECT
+query, minmax_stats_since = :'minmax_reset_ts' AS reset_ts_match
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%'
+ORDER BY query COLLATE "C";
+           query           | reset_ts_match 
+---------------------------+----------------
+ SELECT $1 AS "STMTTS1"    | t
+ SELECT $1,$2 AS "STMTTS2" | f
+(2 rows)
+
+-- check that minmax reset does not set stats_reset
+SELECT
+stats_reset = :'minmax_reset_ts' AS stats_reset_ts_match
+FROM pg_stat_statements_info;
+ stats_reset_ts_match 
+----------------------
+ f
+(1 row)
+
+-- Perform common min/max reset
+SELECT pg_stat_statements_reset(0, 0, 0, true) AS minmax_reset_ts \gset
+-- check again
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_plan_time + max_plan_time = 0
+  ) as minmax_plan_zero,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_ts_after_ref,
+  count(*) FILTER (
+    WHERE minmax_stats_since = :'minmax_reset_ts'
+  ) as minmax_ts_match,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+ total | minmax_plan_zero | minmax_exec_zero | minmax_ts_after_ref | minmax_ts_match | stats_since_after_ref 
+-------+------------------+------------------+---------------------+-----------------+-----------------------
+     2 |                2 |                2 |                   2 |               2 |                     0
+(1 row)
+
+-- Execute first query once more to check stats update
+SELECT 1 AS "STMTTS1";
+ STMTTS1 
+---------
+       1
+(1 row)
+
+-- check
+-- we don't check planing times here to be independent of
+-- plan caching approach
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_ts_after_ref,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+ total | minmax_exec_zero | minmax_ts_after_ref | stats_since_after_ref 
+-------+------------------+---------------------+-----------------------
+     2 |                1 |                   2 |                     0
+(1 row)
+
+-- Cleanup
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
+ t 
+---
+ t
+(1 row)
+
diff --git a/contrib/pg_stat_statements/expected/oldextversions.out b/contrib/pg_stat_statements/expected/oldextversions.out
index 5a5404bbd57..ec317b0d6be 100644
--- a/contrib/pg_stat_statements/expected/oldextversions.out
+++ b/contrib/pg_stat_statements/expected/oldextversions.out
@@ -250,59 +250,61 @@ SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
  t
 (1 row)
 
--- New views for pg_stat_statements in 1.11
+-- New functions and views for pg_stat_statements in 1.11
 AlTER EXTENSION pg_stat_statements UPDATE TO '1.11';
 \d pg_stat_statements
-                      View "public.pg_stat_statements"
-         Column         |       Type       | Collation | Nullable | Default 
-------------------------+------------------+-----------+----------+---------
- userid                 | oid              |           |          | 
- dbid                   | oid              |           |          | 
- toplevel               | boolean          |           |          | 
- queryid                | bigint           |           |          | 
- query                  | text             |           |          | 
- plans                  | bigint           |           |          | 
- total_plan_time        | double precision |           |          | 
- min_plan_time          | double precision |           |          | 
- max_plan_time          | double precision |           |          | 
- mean_plan_time         | double precision |           |          | 
- stddev_plan_time       | double precision |           |          | 
- calls                  | bigint           |           |          | 
- total_exec_time        | double precision |           |          | 
- min_exec_time          | double precision |           |          | 
- max_exec_time          | double precision |           |          | 
- mean_exec_time         | double precision |           |          | 
- stddev_exec_time       | double precision |           |          | 
- rows                   | bigint           |           |          | 
- shared_blks_hit        | bigint           |           |          | 
- shared_blks_read       | bigint           |           |          | 
- shared_blks_dirtied    | bigint           |           |          | 
- shared_blks_written    | bigint           |           |          | 
- local_blks_hit         | bigint           |           |          | 
- local_blks_read        | bigint           |           |          | 
- local_blks_dirtied     | bigint           |           |          | 
- local_blks_written     | bigint           |           |          | 
- temp_blks_read         | bigint           |           |          | 
- temp_blks_written      | bigint           |           |          | 
- shared_blk_read_time   | double precision |           |          | 
- shared_blk_write_time  | double precision |           |          | 
- local_blk_read_time    | double precision |           |          | 
- local_blk_write_time   | double precision |           |          | 
- temp_blk_read_time     | double precision |           |          | 
- temp_blk_write_time    | double precision |           |          | 
- wal_records            | bigint           |           |          | 
- wal_fpi                | bigint           |           |          | 
- wal_bytes              | numeric          |           |          | 
- jit_functions          | bigint           |           |          | 
- jit_generation_time    | double precision |           |          | 
- jit_inlining_count     | bigint           |           |          | 
- jit_inlining_time      | double precision |           |          | 
- jit_optimization_count | bigint           |           |          | 
- jit_optimization_time  | double precision |           |          | 
- jit_emission_count     | bigint           |           |          | 
- jit_emission_time      | double precision |           |          | 
- jit_deform_count       | bigint           |           |          | 
- jit_deform_time        | double precision |           |          | 
+                          View "public.pg_stat_statements"
+         Column         |           Type           | Collation | Nullable | Default 
+------------------------+--------------------------+-----------+----------+---------
+ userid                 | oid                      |           |          | 
+ dbid                   | oid                      |           |          | 
+ toplevel               | boolean                  |           |          | 
+ queryid                | bigint                   |           |          | 
+ query                  | text                     |           |          | 
+ plans                  | bigint                   |           |          | 
+ total_plan_time        | double precision         |           |          | 
+ min_plan_time          | double precision         |           |          | 
+ max_plan_time          | double precision         |           |          | 
+ mean_plan_time         | double precision         |           |          | 
+ stddev_plan_time       | double precision         |           |          | 
+ calls                  | bigint                   |           |          | 
+ total_exec_time        | double precision         |           |          | 
+ min_exec_time          | double precision         |           |          | 
+ max_exec_time          | double precision         |           |          | 
+ mean_exec_time         | double precision         |           |          | 
+ stddev_exec_time       | double precision         |           |          | 
+ rows                   | bigint                   |           |          | 
+ shared_blks_hit        | bigint                   |           |          | 
+ shared_blks_read       | bigint                   |           |          | 
+ shared_blks_dirtied    | bigint                   |           |          | 
+ shared_blks_written    | bigint                   |           |          | 
+ local_blks_hit         | bigint                   |           |          | 
+ local_blks_read        | bigint                   |           |          | 
+ local_blks_dirtied     | bigint                   |           |          | 
+ local_blks_written     | bigint                   |           |          | 
+ temp_blks_read         | bigint                   |           |          | 
+ temp_blks_written      | bigint                   |           |          | 
+ shared_blk_read_time   | double precision         |           |          | 
+ shared_blk_write_time  | double precision         |           |          | 
+ local_blk_read_time    | double precision         |           |          | 
+ local_blk_write_time   | double precision         |           |          | 
+ temp_blk_read_time     | double precision         |           |          | 
+ temp_blk_write_time    | double precision         |           |          | 
+ wal_records            | bigint                   |           |          | 
+ wal_fpi                | bigint                   |           |          | 
+ wal_bytes              | numeric                  |           |          | 
+ jit_functions          | bigint                   |           |          | 
+ jit_generation_time    | double precision         |           |          | 
+ jit_inlining_count     | bigint                   |           |          | 
+ jit_inlining_time      | double precision         |           |          | 
+ jit_optimization_count | bigint                   |           |          | 
+ jit_optimization_time  | double precision         |           |          | 
+ jit_emission_count     | bigint                   |           |          | 
+ jit_emission_time      | double precision         |           |          | 
+ jit_deform_count       | bigint                   |           |          | 
+ jit_deform_time        | double precision         |           |          | 
+ stats_since            | timestamp with time zone |           |          | 
+ minmax_stats_since     | timestamp with time zone |           |          | 
 
 SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
  has_data 
@@ -310,4 +312,16 @@ SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
  t
 (1 row)
 
+-- New parameter minmax_only of pg_stat_statements_reset function
+SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc);
+                                                                        pg_get_functiondef                                                                         
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION public.pg_stat_statements_reset(userid oid DEFAULT 0, dbid oid DEFAULT 0, queryid bigint DEFAULT 0, minmax_only boolean DEFAULT false)+
+  RETURNS timestamp with time zone                                                                                                                                +
+  LANGUAGE c                                                                                                                                                      +
+  PARALLEL SAFE STRICT                                                                                                                                            +
+ AS '$libdir/pg_stat_statements', $function$pg_stat_statements_reset_1_11$function$                                                                               +
+ 
+(1 row)
+
 DROP EXTENSION pg_stat_statements;
diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build
index 15b7c7f2b02..81fe1eb917d 100644
--- a/contrib/pg_stat_statements/meson.build
+++ b/contrib/pg_stat_statements/meson.build
@@ -49,6 +49,7 @@ tests += {
       'planning',
       'user_activity',
       'wal',
+      'entry_timestamp',
       'cleanup',
       'oldextversions',
     ],
diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.10--1.11.sql b/contrib/pg_stat_statements/pg_stat_statements--1.10--1.11.sql
index 5fe211184bf..0bb2c397711 100644
--- a/contrib/pg_stat_statements/pg_stat_statements--1.10--1.11.sql
+++ b/contrib/pg_stat_statements/pg_stat_statements--1.10--1.11.sql
@@ -3,13 +3,10 @@
 -- complain if script is sourced in psql, rather than via ALTER EXTENSION
 \echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.11'" to load this file. \quit
 
-/* First we have to remove them from the extension */
-ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
-ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean);
-
-/* Then we can drop them */
+/* Drop old versions */
 DROP VIEW pg_stat_statements;
 DROP FUNCTION pg_stat_statements(boolean);
+DROP FUNCTION pg_stat_statements_reset(Oid, Oid, bigint);
 
 /* Now redefine */
 CREATE FUNCTION pg_stat_statements(IN showtext boolean,
@@ -59,7 +56,9 @@ CREATE FUNCTION pg_stat_statements(IN showtext boolean,
     OUT jit_emission_count int8,
     OUT jit_emission_time float8,
     OUT jit_deform_count int8,
-    OUT jit_deform_time float8
+    OUT jit_deform_time float8,
+    OUT stats_since timestamp with time zone,
+    OUT minmax_stats_since timestamp with time zone
 )
 RETURNS SETOF record
 AS 'MODULE_PATHNAME', 'pg_stat_statements_1_11'
@@ -69,3 +68,15 @@ CREATE VIEW pg_stat_statements AS
   SELECT * FROM pg_stat_statements(true);
 
 GRANT SELECT ON pg_stat_statements TO PUBLIC;
+
+CREATE FUNCTION pg_stat_statements_reset(IN userid Oid DEFAULT 0,
+	IN dbid Oid DEFAULT 0,
+	IN queryid bigint DEFAULT 0,
+	IN minmax_only boolean DEFAULT false
+)
+RETURNS timestamp with time zone
+AS 'MODULE_PATHNAME', 'pg_stat_statements_reset_1_11'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+-- Don't want this to be available to non-superusers.
+REVOKE ALL ON FUNCTION pg_stat_statements_reset(Oid, Oid, bigint, boolean) FROM PUBLIC;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index baff87b49e1..cb1ed1dd4ec 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -162,9 +162,9 @@ typedef struct Counters
 	double		total_time[PGSS_NUMKIND];	/* total planning/execution time,
 											 * in msec */
 	double		min_time[PGSS_NUMKIND]; /* minimum planning/execution time in
-										 * msec */
+										 * msec since min/max reset */
 	double		max_time[PGSS_NUMKIND]; /* maximum planning/execution time in
-										 * msec */
+										 * msec since min/max reset */
 	double		mean_time[PGSS_NUMKIND];	/* mean planning/execution time in
 											 * msec */
 	double		sum_var_time[PGSS_NUMKIND]; /* sum of variances in
@@ -235,6 +235,8 @@ typedef struct pgssEntry
 	Size		query_offset;	/* query text offset in external file */
 	int			query_len;		/* # of valid bytes in query string, or -1 */
 	int			encoding;		/* query text encoding */
+	TimestampTz	stats_since;		/* timestamp of entry allocation */
+	TimestampTz	minmax_stats_since;	/* timestamp of last min/max values reset */
 	slock_t		mutex;			/* protects the counters only */
 } pgssEntry;
 
@@ -318,6 +320,7 @@ static bool pgss_save = true;	/* whether to save stats across shutdown */
 
 PG_FUNCTION_INFO_V1(pg_stat_statements_reset);
 PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_7);
+PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_11);
 PG_FUNCTION_INFO_V1(pg_stat_statements_1_2);
 PG_FUNCTION_INFO_V1(pg_stat_statements_1_3);
 PG_FUNCTION_INFO_V1(pg_stat_statements_1_8);
@@ -369,7 +372,7 @@ static char *qtext_fetch(Size query_offset, int query_len,
 						 char *buffer, Size buffer_size);
 static bool need_gc_qtexts(void);
 static void gc_qtexts(void);
-static void entry_reset(Oid userid, Oid dbid, uint64 queryid);
+static TimestampTz entry_reset(Oid userid, Oid dbid, uint64 queryid, bool minmax_only);
 static char *generate_normalized_query(JumbleState *jstate, const char *query,
 									   int query_loc, int *query_len_p);
 static void fill_in_constant_lengths(JumbleState *jstate, const char *query,
@@ -664,6 +667,8 @@ pgss_shmem_startup(void)
 
 		/* copy in the actual stats */
 		entry->counters = temp.counters;
+		entry->stats_since = temp.stats_since;
+		entry->minmax_stats_since = temp.minmax_stats_since;
 	}
 
 	/* Read global statistics for pg_stat_statements */
@@ -1380,11 +1385,23 @@ pgss_store(const char *query, uint64 queryId,
 			e->counters.sum_var_time[kind] +=
 				(total_time - old_mean) * (total_time - e->counters.mean_time[kind]);
 
-			/* calculate min and max time */
-			if (e->counters.min_time[kind] > total_time)
+			/*
+			 * Calculate min and max time. min = 0 and max = 0
+			 * means that the min/max statistics were reset
+			 */
+			if (e->counters.min_time[kind] == 0
+				&& e->counters.max_time[kind] == 0)
+			{
 				e->counters.min_time[kind] = total_time;
-			if (e->counters.max_time[kind] < total_time)
 				e->counters.max_time[kind] = total_time;
+			}
+			else
+			{
+				if (e->counters.min_time[kind] > total_time)
+					e->counters.min_time[kind] = total_time;
+				if (e->counters.max_time[kind] < total_time)
+					e->counters.max_time[kind] = total_time;
+			}
 		}
 		e->counters.rows += rows;
 		e->counters.shared_blks_hit += bufusage->shared_blks_hit;
@@ -1454,18 +1471,34 @@ pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS)
 	dbid = PG_GETARG_OID(1);
 	queryid = (uint64) PG_GETARG_INT64(2);
 
-	entry_reset(userid, dbid, queryid);
+	entry_reset(userid, dbid, queryid, false);
 
 	PG_RETURN_VOID();
 }
 
+Datum
+pg_stat_statements_reset_1_11(PG_FUNCTION_ARGS)
+{
+	Oid			userid;
+	Oid			dbid;
+	uint64		queryid;
+	bool		minmax_only;
+
+	userid = PG_GETARG_OID(0);
+	dbid = PG_GETARG_OID(1);
+	queryid = (uint64) PG_GETARG_INT64(2);
+	minmax_only = PG_GETARG_BOOL(3);
+
+	PG_RETURN_TIMESTAMPTZ(entry_reset(userid, dbid, queryid, minmax_only));
+}
+
 /*
  * Reset statement statistics.
  */
 Datum
 pg_stat_statements_reset(PG_FUNCTION_ARGS)
 {
-	entry_reset(0, 0, 0);
+	entry_reset(0, 0, 0, false);
 
 	PG_RETURN_VOID();
 }
@@ -1478,8 +1511,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
 #define PG_STAT_STATEMENTS_COLS_V1_8	32
 #define PG_STAT_STATEMENTS_COLS_V1_9	33
 #define PG_STAT_STATEMENTS_COLS_V1_10	43
-#define PG_STAT_STATEMENTS_COLS_V1_11	47
-#define PG_STAT_STATEMENTS_COLS			47	/* maximum of above */
+#define PG_STAT_STATEMENTS_COLS_V1_11	49
+#define PG_STAT_STATEMENTS_COLS			49	/* maximum of above */
 
 /*
  * Retrieve statement statistics.
@@ -1712,6 +1745,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		Counters	tmp;
 		double		stddev;
 		int64		queryid = entry->key.queryid;
+		TimestampTz	stats_since;
+		TimestampTz	minmax_stats_since;
 
 		memset(values, 0, sizeof(values));
 		memset(nulls, 0, sizeof(nulls));
@@ -1780,6 +1815,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 
 			SpinLockAcquire(&e->mutex);
 			tmp = e->counters;
+			stats_since = e->stats_since;
+			minmax_stats_since = e->minmax_stats_since;
 			SpinLockRelease(&e->mutex);
 		}
 
@@ -1876,6 +1913,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
 		{
 			values[i++] = Int64GetDatumFast(tmp.jit_deform_count);
 			values[i++] = Float8GetDatumFast(tmp.jit_deform_time);
+			values[i++] = TimestampTzGetDatum(stats_since);
+			values[i++] = TimestampTzGetDatum(minmax_stats_since);
 		}
 
 		Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 :
@@ -1994,6 +2033,8 @@ entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding,
 		entry->query_offset = query_offset;
 		entry->query_len = query_len;
 		entry->encoding = encoding;
+		entry->stats_since = GetCurrentTimestamp();
+		entry->minmax_stats_since = entry->stats_since;
 	}
 
 	return entry;
@@ -2557,11 +2598,30 @@ gc_fail:
 	record_gc_qtexts();
 }
 
+#define SINGLE_ENTRY_RESET(e) \
+if (e) { \
+	if (minmax_only) { \
+		/* When requested reset only min/max statistics of an entry */ \
+		for (int kind = 0; kind < PGSS_NUMKIND; kind++) \
+		{ \
+			e->counters.max_time[kind] = 0; \
+			e->counters.min_time[kind] = 0; \
+		} \
+		e->minmax_stats_since = stats_reset; \
+	} \
+	else \
+	{ \
+		/* Remove the key otherwise  */ \
+		hash_search(pgss_hash, &e->key, HASH_REMOVE, NULL); \
+		num_remove++; \
+	} \
+}
+
 /*
- * Release entries corresponding to parameters passed.
+ * Reset entries corresponding to parameters passed.
  */
-static void
-entry_reset(Oid userid, Oid dbid, uint64 queryid)
+static TimestampTz
+entry_reset(Oid userid, Oid dbid, uint64 queryid, bool minmax_only)
 {
 	HASH_SEQ_STATUS hash_seq;
 	pgssEntry  *entry;
@@ -2569,6 +2629,7 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid)
 	long		num_entries;
 	long		num_remove = 0;
 	pgssHashKey key;
+	TimestampTz stats_reset;
 
 	if (!pgss || !pgss_hash)
 		ereport(ERROR,
@@ -2578,6 +2639,8 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid)
 	LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
 	num_entries = hash_get_num_entries(pgss_hash);
 
+	stats_reset = GetCurrentTimestamp();
+
 	if (userid != 0 && dbid != 0 && queryid != UINT64CONST(0))
 	{
 		/* If all the parameters are available, use the fast path. */
@@ -2587,22 +2650,22 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid)
 		key.queryid = queryid;
 
 		/*
-		 * Remove the key if it exists, starting with the non-top-level entry.
+		 * Reset the entry if it exists, starting with the non-top-level entry.
 		 */
 		key.toplevel = false;
-		entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_REMOVE, NULL);
-		if (entry)				/* found */
-			num_remove++;
+		entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
 
-		/* Also remove the top-level entry if it exists. */
+		SINGLE_ENTRY_RESET(entry);
+
+		/* Also reset the top-level entry if it exists. */
 		key.toplevel = true;
-		entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_REMOVE, NULL);
-		if (entry)				/* found */
-			num_remove++;
+		entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
+
+		SINGLE_ENTRY_RESET(entry);
 	}
 	else if (userid != 0 || dbid != 0 || queryid != UINT64CONST(0))
 	{
-		/* Remove entries corresponding to valid parameters. */
+		/* Reset entries corresponding to valid parameters. */
 		hash_seq_init(&hash_seq, pgss_hash);
 		while ((entry = hash_seq_search(&hash_seq)) != NULL)
 		{
@@ -2610,19 +2673,17 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid)
 				(!dbid || entry->key.dbid == dbid) &&
 				(!queryid || entry->key.queryid == queryid))
 			{
-				hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
-				num_remove++;
+				SINGLE_ENTRY_RESET(entry);
 			}
 		}
 	}
 	else
 	{
-		/* Remove all entries. */
+		/* Reset all entries. */
 		hash_seq_init(&hash_seq, pgss_hash);
 		while ((entry = hash_seq_search(&hash_seq)) != NULL)
 		{
-			hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
-			num_remove++;
+			SINGLE_ENTRY_RESET(entry);
 		}
 	}
 
@@ -2636,7 +2697,6 @@ entry_reset(Oid userid, Oid dbid, uint64 queryid)
 	 */
 	{
 		volatile pgssSharedState *s = (volatile pgssSharedState *) pgss;
-		TimestampTz stats_reset = GetCurrentTimestamp();
 
 		SpinLockAcquire(&s->mutex);
 		s->stats.dealloc = 0;
@@ -2674,6 +2734,8 @@ done:
 
 release_lock:
 	LWLockRelease(pgss->lock);
+
+	return stats_reset;
 }
 
 /*
diff --git a/contrib/pg_stat_statements/sql/entry_timestamp.sql b/contrib/pg_stat_statements/sql/entry_timestamp.sql
new file mode 100644
index 00000000000..d6d3027ab4f
--- /dev/null
+++ b/contrib/pg_stat_statements/sql/entry_timestamp.sql
@@ -0,0 +1,114 @@
+--
+-- statement timestamps
+--
+
+-- planning time is needed during tests
+SET pg_stat_statements.track_planning = TRUE;
+
+SELECT 1 AS "STMTTS1";
+SELECT now() AS ref_ts \gset
+SELECT 1,2 AS "STMTTS2";
+SELECT stats_since >= :'ref_ts', count(*) FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%'
+GROUP BY stats_since >= :'ref_ts'
+ORDER BY stats_since >= :'ref_ts';
+
+SELECT now() AS ref_ts \gset
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_plan_time + max_plan_time = 0
+  ) as minmax_plan_zero,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_stats_since_after_ref,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+
+-- Perform single min/max reset
+SELECT pg_stat_statements_reset(0, 0, queryid, true) AS minmax_reset_ts
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS1%' \gset
+
+-- check
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_plan_time + max_plan_time = 0
+  ) as minmax_plan_zero,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_stats_since_after_ref,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+
+-- check minmax reset timestamps
+SELECT
+query, minmax_stats_since = :'minmax_reset_ts' AS reset_ts_match
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%'
+ORDER BY query COLLATE "C";
+
+-- check that minmax reset does not set stats_reset
+SELECT
+stats_reset = :'minmax_reset_ts' AS stats_reset_ts_match
+FROM pg_stat_statements_info;
+
+-- Perform common min/max reset
+SELECT pg_stat_statements_reset(0, 0, 0, true) AS minmax_reset_ts \gset
+
+-- check again
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_plan_time + max_plan_time = 0
+  ) as minmax_plan_zero,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_ts_after_ref,
+  count(*) FILTER (
+    WHERE minmax_stats_since = :'minmax_reset_ts'
+  ) as minmax_ts_match,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+
+-- Execute first query once more to check stats update
+SELECT 1 AS "STMTTS1";
+
+-- check
+-- we don't check planing times here to be independent of
+-- plan caching approach
+SELECT
+  count(*) as total,
+  count(*) FILTER (
+    WHERE min_exec_time + max_exec_time = 0
+  ) as minmax_exec_zero,
+  count(*) FILTER (
+    WHERE minmax_stats_since >= :'ref_ts'
+  ) as minmax_ts_after_ref,
+  count(*) FILTER (
+    WHERE stats_since >= :'ref_ts'
+  ) as stats_since_after_ref
+FROM pg_stat_statements
+WHERE query LIKE '%STMTTS%';
+
+-- Cleanup
+SELECT pg_stat_statements_reset() IS NOT NULL AS t;
diff --git a/contrib/pg_stat_statements/sql/oldextversions.sql b/contrib/pg_stat_statements/sql/oldextversions.sql
index 2435c0c576e..ec06caa5ddc 100644
--- a/contrib/pg_stat_statements/sql/oldextversions.sql
+++ b/contrib/pg_stat_statements/sql/oldextversions.sql
@@ -48,9 +48,11 @@ AlTER EXTENSION pg_stat_statements UPDATE TO '1.10';
 \d pg_stat_statements
 SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
 
--- New views for pg_stat_statements in 1.11
+-- New functions and views for pg_stat_statements in 1.11
 AlTER EXTENSION pg_stat_statements UPDATE TO '1.11';
 \d pg_stat_statements
 SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
+-- New parameter minmax_only of pg_stat_statements_reset function
+SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc);
 
 DROP EXTENSION pg_stat_statements;
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index 6c7ca962f81..15c589432e5 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -140,9 +140,12 @@
        <structfield>min_plan_time</structfield> <type>double precision</type>
       </para>
       <para>
-       Minimum time spent planning the statement, in milliseconds
-       (if <varname>pg_stat_statements.track_planning</varname> is enabled,
-       otherwise zero)
+       Minimum time spent planning the statement, in milliseconds.
+       This field will be zero if <varname>pg_stat_statements.track_planning</varname>
+       is disabled, or if the counter has been reset using the
+       <function>pg_stat_statements_reset</function> function with the
+       <structfield>minmax_only</structfield> parameter set to <literal>true</literal>
+       and never been planned since.
       </para></entry>
      </row>
 
@@ -151,9 +154,12 @@
        <structfield>max_plan_time</structfield> <type>double precision</type>
       </para>
       <para>
-       Maximum time spent planning the statement, in milliseconds
-       (if <varname>pg_stat_statements.track_planning</varname> is enabled,
-       otherwise zero)
+       Maximum time spent planning the statement, in milliseconds.
+       This field will be zero if <varname>pg_stat_statements.track_planning</varname>
+       is disabled, or if the counter has been reset using the
+       <function>pg_stat_statements_reset</function> function with the
+       <structfield>minmax_only</structfield> parameter set to <literal>true</literal>
+       and never been planned since.
       </para></entry>
      </row>
 
@@ -203,7 +209,11 @@
        <structfield>min_exec_time</structfield> <type>double precision</type>
       </para>
       <para>
-       Minimum time spent executing the statement, in milliseconds
+       Minimum time spent executing the statement, in milliseconds,
+       this field will be zero until this statement
+       is executed first time after reset performed by the
+       <function>pg_stat_statements_reset</function> function with the
+       <structfield>minmax_only</structfield> parameter set to <literal>true</literal>
       </para></entry>
      </row>
 
@@ -212,7 +222,11 @@
        <structfield>max_exec_time</structfield> <type>double precision</type>
       </para>
       <para>
-       Maximum time spent executing the statement, in milliseconds
+       Maximum time spent executing the statement, in milliseconds,
+       this field will be zero until this statement
+       is executed first time after reset performed by the
+       <function>pg_stat_statements_reset</function> function with the
+       <structfield>minmax_only</structfield> parameter set to <literal>true</literal>
       </para></entry>
      </row>
 
@@ -512,6 +526,28 @@
        Total time spent by the statement on emitting code, in milliseconds
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>stats_since</structfield> <type>timestamp with time zone</type>
+      </para>
+      <para>
+       Time at which statistics gathering started for this statement
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>minmax_stats_since</structfield> <type>timestamp with time zone</type>
+      </para>
+      <para>
+       Time at which min/max statistics gathering started for this
+       statement (fields <structfield>min_plan_time</structfield>,
+       <structfield>max_plan_time</structfield>,
+       <structfield>min_exec_time</structfield> and
+       <structfield>max_exec_time</structfield>)
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -713,7 +749,8 @@
   <variablelist>
    <varlistentry>
     <term>
-     <function>pg_stat_statements_reset(userid Oid, dbid Oid, queryid bigint) returns void</function>
+     <function>pg_stat_statements_reset(userid Oid, dbid Oid, queryid
+     bigint, minmax_only boolean) returns timestamp with time zone</function>
      <indexterm>
       <primary>pg_stat_statements_reset</primary>
      </indexterm>
@@ -732,6 +769,20 @@
       If all statistics in the <filename>pg_stat_statements</filename>
       view are discarded, it will also reset the statistics in the
       <structname>pg_stat_statements_info</structname> view.
+      When <structfield>minmax_only</structfield> is <literal>true</literal> only the
+      values of minimun and maximum planning and execution time will be reset (i.e.
+      <structfield>min_plan_time</structfield>, <structfield>max_plan_time</structfield>,
+      <structfield>min_exec_time</structfield> and <structfield>max_exec_time</structfield>
+      fields). The default value for <structfield>minmax_only</structfield> parameter is
+      <literal>false</literal>. Time of last min/max reset performed is shown in
+      <structfield>minmax_stats_since</structfield> field of the
+      <structname>pg_stat_statements</structname> view.
+      This function returns the time of a reset. This time is saved to
+      <structfield>stats_reset</structfield> field of
+      <structname>pg_stat_statements_info</structname> view or to
+      <structfield>minmax_stats_since</structfield> field of the
+      <structname>pg_stat_statements</structname> view if the corresponding reset was
+      actually performed.
       By default, this function can only be executed by superusers.
       Access may be granted to others using <command>GRANT</command>.
      </para>
-- 
2.39.2

Reply via email to