Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
pgstat: add tests for transaction behaviour, 2PC, function stats.
authorAndres Freund <andres@anarazel.de>
Thu, 7 Apr 2022 07:03:58 +0000 (00:03 -0700)
committerAndres Freund <andres@anarazel.de>
Thu, 7 Apr 2022 07:22:49 +0000 (00:22 -0700)
Author: Andres Freund <andres@anarazel.de>
Author: Melanie Plageman <melanieplageman@gmail.com>
Discussion: https://postgr.es/m/20220303021600.hs34ghqcw6zcokdh@alap3.anarazel.de

src/test/isolation/expected/stats.out [new file with mode: 0644]
src/test/isolation/isolation_schedule
src/test/isolation/specs/stats.spec [new file with mode: 0644]
src/test/regress/expected/stats.out
src/test/regress/sql/stats.sql

diff --git a/src/test/isolation/expected/stats.out b/src/test/isolation/expected/stats.out
new file mode 100644 (file)
index 0000000..7628a5f
--- /dev/null
@@ -0,0 +1,3737 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_track_funcs_none s1_func_stats s1_func_call s1_func_call s1_ff s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_none: SET track_functions = 'none';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s1_func_stats s1_func_call s1_func_call s1_ff s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats s1_func_call s2_func_call s1_func_call s2_func_call s2_func_call s1_ff s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats s1_func_call s1_ff s2_func_call s2_func_call s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         3|t               |t              
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         3|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats s1_begin s1_func_call s1_func_call s1_commit s1_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_commit: COMMIT;
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s2_func_stats s1_commit s1_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s1_commit: COMMIT;
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s2_func_stats s1_rollback s1_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s1_rollback: ROLLBACK;
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         3|t               |t              
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         3|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s2_func_call s2_ff s2_begin s2_func_call s1_func_drop s1_func_stats s2_commit s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s2_begin s2_func_call s1_func_drop s1_func_stats s2_commit s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s2_begin: BEGIN;
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_disable_debug_discard s2_disable_debug_discard s1_track_funcs_all s2_track_funcs_all s1_func_call s2_begin s2_func_call s1_func_drop s2_func_call s2_commit s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_disable_debug_discard: SET debug_discard_caches = 0;
+step s2_disable_debug_discard: SET debug_discard_caches = 0;
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_disable_debug_discard s2_disable_debug_discard s1_track_funcs_all s2_track_funcs_none s1_func_call s2_begin s2_func_call s1_ff s1_func_stats s1_func_drop s2_track_funcs_none s1_func_stats s2_func_call s2_commit s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_disable_debug_discard: SET debug_discard_caches = 0;
+step s2_disable_debug_discard: SET debug_discard_caches = 0;
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_none: SET track_functions = 'none';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_track_funcs_none: SET track_functions = 'none';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_commit: COMMIT;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_disable_debug_discard s2_disable_debug_discard s1_track_funcs_all s2_track_funcs_none s1_func_call s2_begin s2_func_call s1_ff s1_func_stats s1_func_drop s2_track_funcs_all s1_func_stats s2_func_call s2_commit s2_ff s1_func_stats s2_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_disable_debug_discard: SET debug_discard_caches = 0;
+step s2_disable_debug_discard: SET debug_discard_caches = 0;
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_none: SET track_functions = 'none';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_begin: BEGIN;
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+ERROR:  function call to dropped function
+step s2_commit: COMMIT;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_call s2_func_call s2_func_call2 s1_ff s2_ff s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_func_stats s1_func_stats_reset s1_func_stats s1_func_stats2 s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         3|t               |t              
+(1 row)
+
+step s1_func_stats2: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+
+name           |pg_stat_get_function_calls|total_above_zero|self_above_zero
+---------------+--------------------------+----------------+---------------
+test_stat_func2|                         2|t               |t              
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         3|t               |t              
+(1 row)
+
+step s1_func_stats_reset: SELECT pg_stat_reset_single_function_counters('test_stat_func'::regproc);
+pg_stat_reset_single_function_counters
+--------------------------------------
+                                      
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         0|f               |f              
+(1 row)
+
+step s1_func_stats2: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+
+name           |pg_stat_get_function_calls|total_above_zero|self_above_zero
+---------------+--------------------------+----------------+---------------
+test_stat_func2|                         2|t               |t              
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         0|f               |f              
+(1 row)
+
+
+starting permutation: s1_func_stats_nonexistent s1_func_stats_reset_nonexistent s1_func_stats_nonexistent
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_func_stats_reset_nonexistent: SELECT pg_stat_reset_single_function_counters(12000);
+pg_stat_reset_single_function_counters
+--------------------------------------
+                                      
+(1 row)
+
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_func_call s2_func_call s2_func_call2 s1_ff s2_ff s1_func_stats s1_func_stats2 s1_func_stats s1_reset s1_func_stats s1_func_stats2 s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s1_func_stats2: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+
+name           |pg_stat_get_function_calls|total_above_zero|self_above_zero
+---------------+--------------------------+----------------+---------------
+test_stat_func2|                         1|t               |t              
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s1_reset: SELECT pg_stat_reset();
+pg_stat_reset
+-------------
+             
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         0|f               |f              
+(1 row)
+
+step s1_func_stats2: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+
+name           |pg_stat_get_function_calls|total_above_zero|self_above_zero
+---------------+--------------------------+----------------+---------------
+test_stat_func2|                         0|f               |f              
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         0|f               |f              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s1_fetch_consistency_none s1_func_call s1_ff s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s1_fetch_consistency_none: SET stats_fetch_consistency = 'none';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s1_fetch_consistency_cache s1_func_call s1_ff s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s1_fetch_consistency_cache: SET stats_fetch_consistency = 'cache';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s1_fetch_consistency_snapshot s1_func_call s1_ff s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_fetch_consistency_none s2_func_call s2_ff s1_begin s1_func_stats s2_func_call s2_ff s1_func_stats s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_fetch_consistency_none: SET stats_fetch_consistency = 'none';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         2|t               |t              
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_fetch_consistency_cache s2_func_call s2_func_call2 s2_ff s1_begin s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_fetch_consistency_cache: SET stats_fetch_consistency = 'cache';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s1_func_stats2: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+
+name           |pg_stat_get_function_calls|total_above_zero|self_above_zero
+---------------+--------------------------+----------------+---------------
+test_stat_func2|                         2|t               |t              
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_fetch_consistency_snapshot s2_func_call s2_func_call2 s2_ff s1_begin s1_func_stats s2_func_call s2_func_call2 s2_ff s1_func_stats s1_func_stats2 s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call2: SELECT test_stat_func2()
+test_stat_func2
+---------------
+               
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s1_func_stats2: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+
+name           |pg_stat_get_function_calls|total_above_zero|self_above_zero
+---------------+--------------------------+----------------+---------------
+test_stat_func2|                         1|t               |t              
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_none s1_begin s1_func_stats_nonexistent s1_func_stats_nonexistent s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_none: SET stats_fetch_consistency = 'none';
+step s1_begin: BEGIN;
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_cache s1_begin s1_func_stats_nonexistent s1_func_stats_nonexistent s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_cache: SET stats_fetch_consistency = 'cache';
+step s1_begin: BEGIN;
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_snapshot s1_begin s1_func_stats_nonexistent s1_func_stats_nonexistent s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s1_begin: BEGIN;
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_func_stats_nonexistent: 
+    SELECT pg_stat_get_function_calls(12000);
+
+pg_stat_get_function_calls
+--------------------------
+                          
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s1_prepare_a s2_func_call s2_ff s1_func_call s1_ff s1_func_stats s1_commit_prepared_a s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+step s1_commit_prepared_a: COMMIT PREPARED 'a';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s1_prepare_a s2_func_call s2_ff s1_func_call s1_ff s1_func_stats s1_rollback_prepared_a s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+step s1_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s1_prepare_a s2_func_call s2_ff s1_func_call s1_ff s1_func_stats s2_commit_prepared_a s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+step s2_commit_prepared_a: COMMIT PREPARED 'a';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+
+starting permutation: s1_track_funcs_all s2_track_funcs_all s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s1_prepare_a s2_func_call s2_ff s1_func_call s1_ff s1_func_stats s2_rollback_prepared_a s1_func_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_funcs_all: SET track_functions = 'all';
+step s2_track_funcs_all: SET track_functions = 'all';
+step s1_begin: BEGIN;
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_func_drop: DROP FUNCTION test_stat_func();
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_call: SELECT test_stat_func();
+test_stat_func
+--------------
+              
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+step s2_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         5|t               |t              
+(1 row)
+
+
+starting permutation: s1_table_select s1_table_insert s2_table_select s2_table_update_k1 s1_ff s2_table_update_k1 s1_table_drop s2_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s2_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k1 |    1
+k2 |    1
+k3 |    1
+(4 rows)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_drop: DROP TABLE test_stat_tab;
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       0|           0|        0|        0|        0|         0|         0|           0
+(1 row)
+
+
+starting permutation: s1_table_select s1_table_insert s2_table_select s2_table_update_k1 s2_table_update_k1 s1_table_drop s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s2_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k1 |    1
+k2 |    1
+k3 |    1
+(4 rows)
+
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_drop: DROP TABLE test_stat_tab;
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       0|           0|        0|        0|        0|         0|         0|           0
+(1 row)
+
+
+starting permutation: s1_track_counts_off s1_table_stats s1_track_counts_on
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_counts_off: SET track_counts = off;
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       0|           0|        1|        0|        0|         1|         0|           0
+(1 row)
+
+step s1_track_counts_on: SET track_counts = on;
+
+starting permutation: s1_table_select s1_track_counts_off s1_ff s1_table_stats s1_track_counts_on
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_track_counts_off: SET track_counts = off;
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       1|           1|        1|        0|        0|         1|         0|           0
+(1 row)
+
+step s1_track_counts_on: SET track_counts = on;
+
+starting permutation: s1_table_select s1_ff s1_track_counts_off s1_table_stats s1_track_counts_on
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_counts_off: SET track_counts = off;
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       1|           1|        1|        0|        0|         1|         0|           0
+(1 row)
+
+step s1_track_counts_on: SET track_counts = on;
+
+starting permutation: s1_track_counts_off s1_table_select s1_table_insert_k1 s1_table_update_k1 s2_table_select s1_track_counts_on s1_ff s2_ff s1_table_stats s1_table_select s1_table_update_k1 s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_track_counts_off: SET track_counts = off;
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k1 |    2
+(2 rows)
+
+step s1_track_counts_on: SET track_counts = on;
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       1|           2|        1|        0|        0|         1|         0|           0
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k1 |    2
+(2 rows)
+
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       3|           6|        1|        1|        0|         1|         1|           0
+(1 row)
+
+
+starting permutation: s1_table_select s1_table_insert_k1 s1_table_delete_k1 s1_track_counts_off s1_table_select s1_table_insert_k1 s1_table_update_k1 s2_table_select s1_track_counts_on s1_ff s2_ff s1_table_stats s1_table_select s1_table_update_k1 s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_track_counts_off: SET track_counts = off;
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s2_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k1 |    2
+(2 rows)
+
+step s1_track_counts_on: SET track_counts = on;
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       3|           5|        2|        0|        1|         1|         1|           0
+(1 row)
+
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k1 |    2
+(2 rows)
+
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       5|           9|        2|        1|        1|         1|         2|           0
+(1 row)
+
+
+starting permutation: s1_begin s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1 s1_table_select s1_prepare_a s1_table_select s1_commit_prepared_a s1_table_select s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k2 |    4
+k3 |    1
+(3 rows)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_commit_prepared_a: COMMIT PREPARED 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k2 |    4
+k3 |    1
+(3 rows)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       9|          31|        4|        5|        1|         3|         6|           0
+(1 row)
+
+
+starting permutation: s1_begin s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1 s1_table_select s1_prepare_a s1_table_select s2_commit_prepared_a s1_table_select s1_ff s2_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k2 |    4
+k3 |    1
+(3 rows)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s2_commit_prepared_a: COMMIT PREPARED 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k2 |    4
+k3 |    1
+(3 rows)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       9|          31|        4|        5|        1|         3|         6|           0
+(1 row)
+
+
+starting permutation: s1_begin s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1 s1_table_select s1_prepare_a s1_table_select s1_rollback_prepared_a s1_table_select s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k2 |    4
+k3 |    1
+(3 rows)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       9|          29|        4|        5|        1|         1|         8|           0
+(1 row)
+
+
+starting permutation: s1_begin s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1 s1_table_select s1_prepare_a s1_table_select s2_rollback_prepared_a s1_table_select s1_ff s2_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_begin: BEGIN;
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_update_k2: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+k2 |    4
+k3 |    1
+(3 rows)
+
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s2_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_table_select: SELECT * FROM test_stat_tab ORDER BY key, value;
+key|value
+---+-----
+k0 |    1
+(1 row)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       9|          29|        4|        5|        1|         1|         8|           0
+(1 row)
+
+
+starting permutation: s1_table_insert s1_begin s1_table_update_k1 s1_table_update_k1 s1_table_truncate s1_table_insert_k1 s1_table_update_k1 s1_prepare_a s1_commit_prepared_a s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_truncate: TRUNCATE test_stat_tab;
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_commit_prepared_a: COMMIT PREPARED 'a';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       3|           9|        5|        1|        0|         1|         1|           0
+(1 row)
+
+
+starting permutation: s1_table_insert s1_begin s1_table_update_k1 s1_table_update_k1 s1_table_truncate s1_table_insert_k1 s1_table_update_k1 s1_prepare_a s2_commit_prepared_a s1_ff s2_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_truncate: TRUNCATE test_stat_tab;
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_commit_prepared_a: COMMIT PREPARED 'a';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       3|           9|        5|        1|        0|         1|         1|           0
+(1 row)
+
+
+starting permutation: s1_table_insert s1_begin s1_table_update_k1 s1_table_update_k1 s1_table_truncate s1_table_insert_k1 s1_table_update_k1 s1_prepare_a s1_rollback_prepared_a s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_truncate: TRUNCATE test_stat_tab;
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       3|           9|        4|        2|        0|         4|         2|           0
+(1 row)
+
+
+starting permutation: s1_table_insert s1_begin s1_table_update_k1 s1_table_update_k1 s1_table_truncate s1_table_insert_k1 s1_table_update_k1 s1_prepare_a s2_rollback_prepared_a s1_ff s2_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_begin: BEGIN;
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_truncate: TRUNCATE test_stat_tab;
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       3|           9|        4|        2|        0|         4|         2|           0
+(1 row)
+
+
+starting permutation: s1_table_insert s1_table_update_k1 s1_begin s1_table_delete_k1 s1_table_insert_k1 s1_table_update_k1 s1_table_update_k1 s1_table_drop s1_prepare_a s1_rollback_prepared_a s1_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_begin: BEGIN;
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_drop: DROP TABLE test_stat_tab;
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s1_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       4|          16|        5|        3|        1|         4|         4|           0
+(1 row)
+
+
+starting permutation: s1_table_insert s1_table_update_k1 s1_begin s1_table_delete_k1 s1_table_insert_k1 s1_table_update_k1 s1_table_update_k1 s1_table_drop s1_prepare_a s2_rollback_prepared_a s1_ff s2_ff s1_table_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_insert: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_begin: BEGIN;
+step s1_table_delete_k1: DELETE FROM test_stat_tab WHERE key = 'k1';
+step s1_table_insert_k1: INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_update_k1: UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';
+step s1_table_drop: DROP TABLE test_stat_tab;
+step s1_prepare_a: PREPARE TRANSACTION 'a';
+step s2_rollback_prepared_a: ROLLBACK PREPARED 'a';
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_table_stats: 
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+
+seq_scan|seq_tup_read|n_tup_ins|n_tup_upd|n_tup_del|n_live_tup|n_dead_tup|vacuum_count
+--------+------------+---------+---------+---------+----------+----------+------------
+       4|          16|        5|        3|        1|         4|         4|           0
+(1 row)
+
+
+starting permutation: s1_slru_save_stats s1_listen s1_begin s1_big_notify s1_ff s1_slru_check_stats s1_commit s1_slru_check_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s1_ff: SELECT pg_stat_force_next_flush(); RESET debug_discard_caches;
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s1_commit: COMMIT;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+
+starting permutation: s1_slru_save_stats s1_listen s2_big_notify s2_ff s1_slru_check_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+
+starting permutation: s1_slru_save_stats s1_listen s2_begin s2_big_notify s2_ff s1_slru_check_stats s2_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s2_begin: BEGIN;
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_none s1_slru_save_stats s1_listen s1_begin s1_slru_check_stats s2_big_notify s2_ff s1_slru_check_stats s1_commit s1_slru_check_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_none: SET stats_fetch_consistency = 'none';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+step s1_commit: COMMIT;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+
+starting permutation: s1_fetch_consistency_cache s1_slru_save_stats s1_listen s1_begin s1_slru_check_stats s2_big_notify s2_ff s1_slru_check_stats s1_commit s1_slru_check_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_cache: SET stats_fetch_consistency = 'cache';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s1_commit: COMMIT;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+
+starting permutation: s1_fetch_consistency_snapshot s1_slru_save_stats s1_listen s1_begin s1_slru_check_stats s2_big_notify s2_ff s1_slru_check_stats s1_commit s1_slru_check_stats
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s1_commit: COMMIT;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+
+starting permutation: s1_fetch_consistency_none s1_slru_save_stats s1_listen s1_begin s1_slru_check_stats s2_big_notify s2_ff s1_slru_check_stats s1_clear_snapshot s1_slru_check_stats s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_none: SET stats_fetch_consistency = 'none';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+step s1_clear_snapshot: SELECT pg_stat_clear_snapshot();
+pg_stat_clear_snapshot
+----------------------
+                      
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_cache s1_slru_save_stats s1_listen s1_begin s1_slru_check_stats s2_big_notify s2_ff s1_slru_check_stats s1_clear_snapshot s1_slru_check_stats s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_cache: SET stats_fetch_consistency = 'cache';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s1_clear_snapshot: SELECT pg_stat_clear_snapshot();
+pg_stat_clear_snapshot
+----------------------
+                      
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_snapshot s1_slru_save_stats s1_listen s1_begin s1_slru_check_stats s2_big_notify s2_ff s1_slru_check_stats s1_clear_snapshot s1_slru_check_stats s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s1_clear_snapshot: SELECT pg_stat_clear_snapshot();
+pg_stat_clear_snapshot
+----------------------
+                      
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_snapshot s1_slru_save_stats s1_listen s1_begin s1_func_stats s2_big_notify s2_ff s1_slru_check_stats s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+f       
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_fetch_consistency_snapshot s1_slru_save_stats s1_listen s1_begin s2_big_notify s2_ff s1_slru_check_stats s2_func_call s2_ff s1_func_stats s1_clear_snapshot s1_func_stats s1_commit
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_fetch_consistency_snapshot: SET stats_fetch_consistency = 'snapshot';
+step s1_slru_save_stats: 
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+
+step s1_listen: LISTEN stats_test_nothing;
+step s1_begin: BEGIN;
+step s2_big_notify: SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+
+pg_notify
+---------
+         
+         
+         
+(3 rows)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_slru_check_stats: 
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+
+?column?
+--------
+t       
+(1 row)
+
+step s2_func_call: SELECT test_stat_func()
+test_stat_func
+--------------
+              
+(1 row)
+
+step s2_ff: SELECT pg_stat_force_next_flush();
+pg_stat_force_next_flush
+------------------------
+                        
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                          |                |               
+(1 row)
+
+step s1_clear_snapshot: SELECT pg_stat_clear_snapshot();
+pg_stat_clear_snapshot
+----------------------
+                      
+(1 row)
+
+step s1_func_stats: 
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+
+name          |pg_stat_get_function_calls|total_above_zero|self_above_zero
+--------------+--------------------------+----------------+---------------
+test_stat_func|                         1|t               |t              
+(1 row)
+
+step s1_commit: COMMIT;
index a48caae228efa6b853ee748a697121e30ef36bb9..c3066a6748d6a23cf173f40e0df12a0e7beac5e1 100644 (file)
@@ -89,6 +89,7 @@ test: timeouts
 test: vacuum-concurrent-drop
 test: vacuum-conflict
 test: vacuum-skip-locked
+test: stats
 test: horizons
 test: predicate-hash
 test: predicate-gist
diff --git a/src/test/isolation/specs/stats.spec b/src/test/isolation/specs/stats.spec
new file mode 100644 (file)
index 0000000..a3a18ca
--- /dev/null
@@ -0,0 +1,753 @@
+setup
+{
+    CREATE TABLE test_stat_oid(name text NOT NULL, oid oid);
+
+    CREATE TABLE test_stat_tab(key text not null, value int);
+    INSERT INTO test_stat_tab(key, value) VALUES('k0', 1);
+    INSERT INTO test_stat_oid(name, oid) VALUES('test_stat_tab', 'test_stat_tab'::regclass);
+
+    CREATE FUNCTION test_stat_func() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
+    INSERT INTO test_stat_oid(name, oid) VALUES('test_stat_func', 'test_stat_func'::regproc);
+
+    CREATE FUNCTION test_stat_func2() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
+    INSERT INTO test_stat_oid(name, oid) VALUES('test_stat_func2', 'test_stat_func2'::regproc);
+
+    CREATE TABLE test_slru_stats(slru TEXT, stat TEXT, value INT);
+
+    SELECT pg_stat_force_next_flush();
+}
+
+teardown
+{
+    DROP TABLE test_stat_oid;
+    DROP TABLE test_slru_stats;
+
+    DROP TABLE IF EXISTS test_stat_tab;
+    DROP FUNCTION IF EXISTS test_stat_func();
+    DROP FUNCTION IF EXISTS test_stat_func2();
+}
+
+session s1
+setup { SET stats_fetch_consistency = 'none'; }
+step s1_fetch_consistency_none { SET stats_fetch_consistency = 'none'; }
+step s1_fetch_consistency_cache { SET stats_fetch_consistency = 'cache'; }
+step s1_fetch_consistency_snapshot { SET stats_fetch_consistency = 'snapshot'; }
+step s1_disable_debug_discard { SET debug_discard_caches = 0; }
+step s1_clear_snapshot { SELECT pg_stat_clear_snapshot(); }
+step s1_begin { BEGIN; }
+step s1_commit { COMMIT; }
+step s1_rollback { ROLLBACK; }
+step s1_prepare_a { PREPARE TRANSACTION 'a'; }
+step s1_commit_prepared_a { COMMIT PREPARED 'a'; }
+step s1_rollback_prepared_a { ROLLBACK PREPARED 'a'; }
+
+# Function stats steps
+step s1_ff { SELECT pg_stat_force_next_flush(); RESET debug_discard_caches; }
+step s1_track_funcs_all { SET track_functions = 'all'; }
+step s1_track_funcs_none { SET track_functions = 'none'; }
+step s1_func_call { SELECT test_stat_func(); }
+step s1_func_drop { DROP FUNCTION test_stat_func(); }
+step s1_func_stats_reset { SELECT pg_stat_reset_single_function_counters('test_stat_func'::regproc); }
+step s1_func_stats_reset_nonexistent { SELECT pg_stat_reset_single_function_counters(12000); }
+step s1_reset { SELECT pg_stat_reset(); }
+step s1_func_stats {
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+}
+step s1_func_stats2 {
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func2'
+}
+step s1_func_stats_nonexistent {
+    SELECT pg_stat_get_function_calls(12000);
+}
+
+# Relation stats steps
+step s1_track_counts_on { SET track_counts = on; }
+step s1_track_counts_off { SET track_counts = off; }
+step s1_table_select { SELECT * FROM test_stat_tab ORDER BY key, value; }
+step s1_table_insert { INSERT INTO test_stat_tab(key, value) VALUES('k1', 1), ('k2', 1), ('k3', 1);}
+step s1_table_insert_k1 { INSERT INTO test_stat_tab(key, value) VALUES('k1', 1);}
+step s1_table_update_k1 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';}
+step s1_table_update_k2 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k2';}
+step s1_table_delete_k1 { DELETE FROM test_stat_tab WHERE key = 'k1';}
+step s1_table_truncate { TRUNCATE test_stat_tab; }
+step s1_table_drop { DROP TABLE test_stat_tab; }
+
+step s1_table_stats {
+    SELECT
+        pg_stat_get_numscans(tso.oid) AS seq_scan,
+        pg_stat_get_tuples_returned(tso.oid) AS seq_tup_read,
+        pg_stat_get_tuples_inserted(tso.oid) AS n_tup_ins,
+        pg_stat_get_tuples_updated(tso.oid) AS n_tup_upd,
+        pg_stat_get_tuples_deleted(tso.oid) AS n_tup_del,
+        pg_stat_get_live_tuples(tso.oid) AS n_live_tup,
+        pg_stat_get_dead_tuples(tso.oid) AS n_dead_tup,
+        pg_stat_get_vacuum_count(tso.oid) AS vacuum_count
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_tab'
+}
+
+# SLRU stats steps
+step s1_slru_save_stats {
+   INSERT INTO test_slru_stats VALUES('Notify', 'blks_zeroed',
+    (SELECT blks_zeroed FROM pg_stat_slru WHERE name = 'Notify'));
+}
+step s1_listen { LISTEN stats_test_nothing; }
+step s1_big_notify { SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+                }
+
+step s1_slru_check_stats {
+   SELECT current.blks_zeroed > before.value
+  FROM test_slru_stats before
+  INNER JOIN pg_stat_slru current
+  ON before.slru = current.name
+  WHERE before.stat = 'blks_zeroed';
+   }
+
+
+session s2
+setup { SET stats_fetch_consistency = 'none'; RESET debug_discard_caches; }
+step s2_begin { BEGIN; }
+step s2_disable_debug_discard { SET debug_discard_caches = 0; }
+step s2_commit { COMMIT; }
+step s2_commit_prepared_a { COMMIT PREPARED 'a'; }
+step s2_rollback_prepared_a { ROLLBACK PREPARED 'a'; }
+step s2_ff { SELECT pg_stat_force_next_flush(); }
+
+# Function stats steps
+step s2_track_funcs_all { SET track_functions = 'all'; }
+step s2_track_funcs_none { SET track_functions = 'none'; }
+step s2_func_call { SELECT test_stat_func() }
+step s2_func_call2 { SELECT test_stat_func2() }
+step s2_func_stats {
+    SELECT
+        tso.name,
+        pg_stat_get_function_calls(tso.oid),
+        pg_stat_get_function_total_time(tso.oid) > 0 total_above_zero,
+        pg_stat_get_function_self_time(tso.oid) > 0 self_above_zero
+    FROM test_stat_oid AS tso
+    WHERE tso.name = 'test_stat_func'
+}
+
+# Relation stats steps
+step s2_table_select { SELECT * FROM test_stat_tab ORDER BY key, value; }
+step s2_table_update_k1 { UPDATE test_stat_tab SET value = value + 1 WHERE key = 'k1';}
+
+# SLRU stats steps
+step s2_big_notify { SELECT pg_notify('stats_test_use',
+                repeat('0', current_setting('block_size')::int / 2)) FROM generate_series(1, 3);
+                }
+
+
+######################
+# Function stats tests
+######################
+
+# check that stats are collected iff enabled
+permutation
+  s1_track_funcs_none s1_func_stats s1_func_call s1_func_call s1_ff s1_func_stats
+permutation
+  s1_track_funcs_all s1_func_stats s1_func_call s1_func_call s1_ff s1_func_stats
+
+# multiple function calls are accurately reported, across separate connections
+permutation
+  s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats
+  s1_func_call s2_func_call s1_func_call s2_func_call s2_func_call s1_ff s2_ff s1_func_stats s2_func_stats
+permutation
+  s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats
+  s1_func_call s1_ff s2_func_call s2_func_call s2_ff s1_func_stats s2_func_stats
+permutation
+  s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats
+  s1_begin s1_func_call s1_func_call s1_commit s1_ff s1_func_stats s2_func_stats
+
+
+### Check interaction between dropping and stats reporting
+
+# Disable debug_discard_caches for a few of these tests - we precisely are
+# testing the behavior of no invalidations arriving.  "Real" invalidations
+# shouldn't trigger behavioral difference, because we are testing paths
+# precisely because they do not have AcceptInvalidationMessages calls.()
+
+# dropping a table remove stats iff committed
+permutation
+  s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats
+  s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s2_func_stats s1_commit s1_ff s1_func_stats s2_func_stats
+permutation
+  s1_track_funcs_all s2_track_funcs_all s1_func_stats s2_func_stats
+  s1_begin s1_func_call s2_func_call s1_func_drop s2_func_call s2_ff s2_func_stats s1_rollback s1_ff s1_func_stats s2_func_stats
+
+# Verify that pending stats from before a drop do not lead to
+# reviving stats for a dropped object
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s2_func_call s2_ff # this access increments refcount, preventing the shared entry from being dropped
+  s2_begin s2_func_call s1_func_drop s1_func_stats s2_commit s2_ff s1_func_stats s2_func_stats
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s2_begin s2_func_call s1_func_drop s1_func_stats s2_commit s2_ff s1_func_stats s2_func_stats
+permutation
+  s1_disable_debug_discard s2_disable_debug_discard
+  s1_track_funcs_all s2_track_funcs_all
+  s1_func_call s2_begin s2_func_call s1_func_drop s2_func_call s2_commit s2_ff s1_func_stats s2_func_stats
+
+# Function calls don't necessarily trigger cache invalidation processing. The
+# default handling of dropped stats could therefore end up with stats getting
+# revived by a function call done after stats processing - but
+# pgstat_init_function_usage() protects against that if track_functions is
+# on. Verify that the stats are indeed dropped, and document the behavioral
+# difference between track_functions settings.
+permutation
+  s1_disable_debug_discard s2_disable_debug_discard
+  s1_track_funcs_all s2_track_funcs_none
+  s1_func_call s2_begin s2_func_call s1_ff s1_func_stats s1_func_drop s2_track_funcs_none s1_func_stats s2_func_call s2_commit s2_ff s1_func_stats s2_func_stats
+permutation
+  s1_disable_debug_discard s2_disable_debug_discard
+  s1_track_funcs_all s2_track_funcs_none
+  s1_func_call s2_begin s2_func_call s1_ff s1_func_stats s1_func_drop s2_track_funcs_all s1_func_stats s2_func_call s2_commit s2_ff s1_func_stats s2_func_stats
+
+# test pg_stat_reset_single_function_counters
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_func_call
+  s2_func_call
+  s2_func_call2
+  s1_ff s2_ff
+  s1_func_stats
+  s2_func_call s2_func_call2 s2_ff
+  s1_func_stats s1_func_stats2 s1_func_stats
+  s1_func_stats_reset
+  s1_func_stats s1_func_stats2 s1_func_stats
+
+# test pg_stat_reset_single_function_counters of non-existing function
+permutation
+  s1_func_stats_nonexistent
+  s1_func_stats_reset_nonexistent
+  s1_func_stats_nonexistent
+
+# test pg_stat_reset
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_func_call
+  s2_func_call
+  s2_func_call2
+  s1_ff s2_ff
+  s1_func_stats s1_func_stats2 s1_func_stats
+  s1_reset
+  s1_func_stats s1_func_stats2 s1_func_stats
+
+
+### Check the different snapshot consistency models
+
+# First just some dead-trivial test verifying each model doesn't crash
+permutation
+  s1_track_funcs_all s1_fetch_consistency_none s1_func_call s1_ff s1_func_stats
+permutation
+  s1_track_funcs_all s1_fetch_consistency_cache s1_func_call s1_ff s1_func_stats
+permutation
+  s1_track_funcs_all s1_fetch_consistency_snapshot s1_func_call s1_ff s1_func_stats
+
+# with stats_fetch_consistency=none s1 should see flushed changes in s2, despite being in a transaction
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_fetch_consistency_none
+  s2_func_call s2_ff
+  s1_begin
+  s1_func_stats
+  s2_func_call s2_ff
+  s1_func_stats
+  s1_commit
+
+# with stats_fetch_consistency=cache s1 should not see concurrent
+# changes to the same object after the first access, but a separate
+# object should show changes
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_fetch_consistency_cache
+  s2_func_call s2_func_call2 s2_ff
+  s1_begin
+  s1_func_stats
+  s2_func_call s2_func_call2 s2_ff
+  s1_func_stats s1_func_stats2
+  s1_commit
+
+# with stats_fetch_consistency=snapshot s1 should not see any
+# concurrent changes after the first access
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_fetch_consistency_snapshot
+  s2_func_call s2_func_call2 s2_ff
+  s1_begin
+  s1_func_stats
+  s2_func_call s2_func_call2 s2_ff
+  s1_func_stats s1_func_stats2
+  s1_commit
+
+# Check access to non-existing stats works correctly and repeatedly
+permutation
+  s1_fetch_consistency_none
+  s1_begin
+  s1_func_stats_nonexistent
+  s1_func_stats_nonexistent
+  s1_commit
+permutation
+  s1_fetch_consistency_cache
+  s1_begin
+  s1_func_stats_nonexistent
+  s1_func_stats_nonexistent
+  s1_commit
+permutation
+  s1_fetch_consistency_snapshot
+  s1_begin
+  s1_func_stats_nonexistent
+  s1_func_stats_nonexistent
+  s1_commit
+
+
+### Check 2PC handling of stat drops
+
+# S1 prepared, S1 commits prepared
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_begin
+  s1_func_call
+  s2_func_call
+  s1_func_drop
+  s2_func_call
+  s2_ff
+  s1_prepare_a
+  s2_func_call
+  s2_ff
+  s1_func_call
+  s1_ff
+  s1_func_stats
+  s1_commit_prepared_a
+  s1_func_stats
+
+# S1 prepared, S1 aborts prepared
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_begin
+  s1_func_call
+  s2_func_call
+  s1_func_drop
+  s2_func_call
+  s2_ff
+  s1_prepare_a
+  s2_func_call
+  s2_ff
+  s1_func_call
+  s1_ff
+  s1_func_stats
+  s1_rollback_prepared_a
+  s1_func_stats
+
+# S1 prepares, S2 commits prepared
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_begin
+  s1_func_call
+  s2_func_call
+  s1_func_drop
+  s2_func_call
+  s2_ff
+  s1_prepare_a
+  s2_func_call
+  s2_ff
+  s1_func_call
+  s1_ff
+  s1_func_stats
+  s2_commit_prepared_a
+  s1_func_stats
+
+# S1 prepared, S2 aborts prepared
+permutation
+  s1_track_funcs_all s2_track_funcs_all
+  s1_begin
+  s1_func_call
+  s2_func_call
+  s1_func_drop
+  s2_func_call
+  s2_ff
+  s1_prepare_a
+  s2_func_call
+  s2_ff
+  s1_func_call
+  s1_ff
+  s1_func_stats
+  s2_rollback_prepared_a
+  s1_func_stats
+
+
+######################
+# Table stats tests
+######################
+
+# Most of the stats handling mechanism has already been tested in the function
+# stats tests above - that's cheaper than testing with relations. But
+# particularly for 2PC there are special cases
+
+
+### Verify that pending stats from before a drop do not lead to reviving
+### of stats for a dropped object
+
+permutation
+  s1_table_select
+  s1_table_insert
+  s2_table_select
+  s2_table_update_k1
+  s1_ff
+  s2_table_update_k1
+  s1_table_drop
+  s2_ff
+  s1_table_stats
+
+permutation
+  s1_table_select
+  s1_table_insert
+  s2_table_select
+  s2_table_update_k1
+  s2_table_update_k1
+  s1_table_drop
+  s1_table_stats
+
+
+### Check that we don't count changes with track counts off, but allow access
+### to prior stats
+
+# simple read access with stats off
+permutation
+  s1_track_counts_off
+  s1_table_stats
+  s1_track_counts_on
+
+# simple read access with stats off, previously accessed
+permutation
+  s1_table_select
+  s1_track_counts_off
+  s1_ff
+  s1_table_stats
+  s1_track_counts_on
+permutation
+  s1_table_select
+  s1_ff
+  s1_track_counts_off
+  s1_table_stats
+  s1_track_counts_on
+
+# ensure we don't count anything with stats off
+permutation
+  s1_track_counts_off
+  s1_table_select
+  s1_table_insert_k1
+  s1_table_update_k1
+  s2_table_select
+  s1_track_counts_on
+  s1_ff s2_ff
+  s1_table_stats
+  # but can count again after
+  s1_table_select
+  s1_table_update_k1
+  s1_ff
+  s1_table_stats
+permutation
+  s1_table_select
+  s1_table_insert_k1
+  s1_table_delete_k1
+  s1_track_counts_off
+  s1_table_select
+  s1_table_insert_k1
+  s1_table_update_k1
+  s2_table_select
+  s1_track_counts_on
+  s1_ff s2_ff
+  s1_table_stats
+  s1_table_select
+  s1_table_update_k1
+  s1_ff
+  s1_table_stats
+
+
+### 2PC: transactional and non-transactional counters work correctly
+
+# S1 prepares, S2 commits prepared
+permutation
+  s1_begin
+  s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1
+  s1_table_select
+  s1_prepare_a
+  s1_table_select
+  s1_commit_prepared_a
+  s1_table_select
+  s1_ff
+  s1_table_stats
+
+# S1 prepares, S2 commits prepared
+permutation
+  s1_begin
+  s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1
+  s1_table_select
+  s1_prepare_a
+  s1_table_select
+  s2_commit_prepared_a
+  s1_table_select
+  s1_ff s2_ff
+  s1_table_stats
+
+# S1 prepares, S2 commits prepared
+permutation
+  s1_begin
+  s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1
+  s1_table_select
+  s1_prepare_a
+  s1_table_select
+  s1_rollback_prepared_a
+  s1_table_select
+  s1_ff
+  s1_table_stats
+
+# S1 prepares, S1 aborts prepared
+permutation
+  s1_begin
+  s1_table_insert s1_table_update_k1 s1_table_update_k1 s1_table_update_k2 s1_table_update_k2 s1_table_update_k2 s1_table_delete_k1
+  s1_table_select
+  s1_prepare_a
+  s1_table_select
+  s2_rollback_prepared_a
+  s1_table_select
+  s1_ff s2_ff
+  s1_table_stats
+
+
+### 2PC: truncate handling
+
+# S1 prepares, S1 commits prepared
+permutation
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1 # should *not* be counted, different rel
+  s1_table_update_k1 # dito
+  s1_table_truncate
+  s1_table_insert_k1 # should be counted
+  s1_table_update_k1 # dito
+  s1_prepare_a
+  s1_commit_prepared_a
+  s1_ff
+  s1_table_stats
+
+# S1 prepares, S2 commits prepared
+permutation
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1 # should *not* be counted, different rel
+  s1_table_update_k1 # dito
+  s1_table_truncate
+  s1_table_insert_k1 # should be counted
+  s1_table_update_k1 # dito
+  s1_prepare_a
+  s2_commit_prepared_a
+  s1_ff s2_ff
+  s1_table_stats
+
+# S1 prepares, S1 aborts prepared
+permutation
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1 # should be counted
+  s1_table_update_k1 # dito
+  s1_table_truncate
+  s1_table_insert_k1 # should *not* be counted, different rel
+  s1_table_update_k1 # dito
+  s1_prepare_a
+  s1_rollback_prepared_a
+  s1_ff
+  s1_table_stats
+
+# S1 prepares, S2 aborts prepared
+permutation
+  s1_table_insert
+  s1_begin
+  s1_table_update_k1 # should be counted
+  s1_table_update_k1 # dito
+  s1_table_truncate
+  s1_table_insert_k1 # should *not* be counted, different rel
+  s1_table_update_k1 # dito
+  s1_prepare_a
+  s2_rollback_prepared_a
+  s1_ff s2_ff
+  s1_table_stats
+
+
+### 2PC: rolled back drop maintains live / dead counters
+
+# S1 prepares, S1 aborts prepared
+permutation
+  s1_table_insert
+  s1_table_update_k1
+  s1_begin
+  # should all be counted
+  s1_table_delete_k1
+  s1_table_insert_k1
+  s1_table_update_k1
+  s1_table_update_k1
+  s1_table_drop
+  s1_prepare_a
+  s1_rollback_prepared_a
+  s1_ff
+  s1_table_stats
+
+# S1 prepares, S1 aborts prepared
+permutation
+  s1_table_insert
+  s1_table_update_k1
+  s1_begin
+  # should all be counted
+  s1_table_delete_k1
+  s1_table_insert_k1
+  s1_table_update_k1
+  s1_table_update_k1
+  s1_table_drop
+  s1_prepare_a
+  s2_rollback_prepared_a
+  s1_ff s2_ff
+  s1_table_stats
+
+
+######################
+# SLRU stats tests
+######################
+
+# Verify SLRU stats generated in own transaction
+permutation
+  s1_slru_save_stats
+  s1_listen
+  s1_begin
+  s1_big_notify
+  s1_ff
+  s1_slru_check_stats
+  s1_commit
+  s1_slru_check_stats
+
+# Verify SLRU stats generated in separate transaction
+permutation
+  s1_slru_save_stats
+  s1_listen
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+
+# shouldn't see stats yet, not committed
+permutation
+  s1_slru_save_stats
+  s1_listen
+  s2_begin
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s2_commit
+
+
+### Check the different snapshot consistency models for fixed-amount statistics
+
+permutation
+  s1_fetch_consistency_none
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_slru_check_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_commit
+  s1_slru_check_stats
+permutation
+  s1_fetch_consistency_cache
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_slru_check_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_commit
+  s1_slru_check_stats
+permutation
+  s1_fetch_consistency_snapshot
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_slru_check_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_commit
+  s1_slru_check_stats
+
+# check that pg_stat_clear_snapshot(), well ...
+permutation
+  s1_fetch_consistency_none
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_slru_check_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_clear_snapshot
+  s1_slru_check_stats
+  s1_commit
+permutation
+  s1_fetch_consistency_cache
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_slru_check_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_clear_snapshot
+  s1_slru_check_stats
+  s1_commit
+permutation
+  s1_fetch_consistency_snapshot
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_slru_check_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_clear_snapshot
+  s1_slru_check_stats
+  s1_commit
+
+# check that a variable-amount stats access caches fixed-amount stat too
+permutation
+  s1_fetch_consistency_snapshot
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s1_func_stats
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s1_commit
+
+# and the other way round
+permutation
+  s1_fetch_consistency_snapshot
+  s1_slru_save_stats s1_listen
+  s1_begin
+  s2_big_notify
+  s2_ff
+  s1_slru_check_stats
+  s2_func_call
+  s2_ff
+  s1_func_stats
+  s1_clear_snapshot
+  s1_func_stats
+  s1_commit
index aa05b3b78af40ee6f0fd9cf2ff5ff13bacc9abca..9713175930331a3c57205d65f0e71daa7735966a 100644 (file)
@@ -16,6 +16,8 @@ SET enable_seqscan TO on;
 SET enable_indexscan TO on;
 -- for the moment, we don't want index-only scans here
 SET enable_indexonlyscan TO off;
+-- not enabled by default, but we want to test it...
+SET track_functions TO 'all';
 -- save counters
 BEGIN;
 SET LOCAL stats_fetch_consistency = snapshot;
@@ -146,8 +148,477 @@ FROM prevstats AS pr;
 (1 row)
 
 COMMIT;
+----
+-- Basic tests for track_functions
+---
+CREATE FUNCTION stats_test_func1() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
+SELECT 'stats_test_func1()'::regprocedure::oid AS stats_test_func1_oid \gset
+CREATE FUNCTION stats_test_func2() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
+SELECT 'stats_test_func2()'::regprocedure::oid AS stats_test_func2_oid \gset
+-- test that stats are accumulated
+BEGIN;
+SET LOCAL stats_fetch_consistency = none;
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+ pg_stat_get_function_calls 
+----------------------------
+                           
+(1 row)
+
+SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
+ pg_stat_get_xact_function_calls 
+---------------------------------
+                                
+(1 row)
+
+SELECT stats_test_func1();
+ stats_test_func1 
+------------------
+(1 row)
+
+SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
+ pg_stat_get_xact_function_calls 
+---------------------------------
+                               1
+(1 row)
+
+SELECT stats_test_func1();
+ stats_test_func1 
+------------------
+(1 row)
+
+SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
+ pg_stat_get_xact_function_calls 
+---------------------------------
+                               2
+(1 row)
+
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+ pg_stat_get_function_calls 
+----------------------------
+                          0
+(1 row)
+
+COMMIT;
+-- Verify that function stats are not transactional
+-- rolled back savepoint in committing transaction
+BEGIN;
+SELECT stats_test_func2();
+ stats_test_func2 
+------------------
+(1 row)
+
+SAVEPOINT foo;
+SELECT stats_test_func2();
+ stats_test_func2 
+------------------
+(1 row)
+
+ROLLBACK TO SAVEPOINT foo;
+SELECT pg_stat_get_xact_function_calls(:stats_test_func2_oid);
+ pg_stat_get_xact_function_calls 
+---------------------------------
+                               2
+(1 row)
+
+SELECT stats_test_func2();
+ stats_test_func2 
+------------------
+(1 row)
+
+COMMIT;
+-- rolled back transaction
+BEGIN;
+SELECT stats_test_func2();
+ stats_test_func2 
+------------------
+(1 row)
+
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+-- check collected stats
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+     funcname     | calls 
+------------------+-------
+ stats_test_func1 |     2
+(1 row)
+
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid;
+     funcname     | calls 
+------------------+-------
+ stats_test_func2 |     4
+(1 row)
+
+-- check that a rolled back drop function stats leaves stats alive
+BEGIN;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+     funcname     | calls 
+------------------+-------
+ stats_test_func1 |     2
+(1 row)
+
+DROP FUNCTION stats_test_func1();
+-- shouldn't be visible via view
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+ funcname | calls 
+----------+-------
+(0 rows)
+
+-- but still via oid access
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+ pg_stat_get_function_calls 
+----------------------------
+                          2
+(1 row)
+
+ROLLBACK;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+     funcname     | calls 
+------------------+-------
+ stats_test_func1 |     2
+(1 row)
+
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+ pg_stat_get_function_calls 
+----------------------------
+                          2
+(1 row)
+
+-- check that function dropped in main transaction leaves no stats behind
+BEGIN;
+DROP FUNCTION stats_test_func1();
+COMMIT;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+ funcname | calls 
+----------+-------
+(0 rows)
+
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+ pg_stat_get_function_calls 
+----------------------------
+                           
+(1 row)
+
+-- check that function dropped in a subtransaction leaves no stats behind
+BEGIN;
+SELECT stats_test_func2();
+ stats_test_func2 
+------------------
+(1 row)
+
+SAVEPOINT a;
+SELECT stats_test_func2();
+ stats_test_func2 
+------------------
+(1 row)
+
+SAVEPOINT b;
+DROP FUNCTION stats_test_func2();
+COMMIT;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid;
+ funcname | calls 
+----------+-------
+(0 rows)
+
+SELECT pg_stat_get_function_calls(:stats_test_func2_oid);
+ pg_stat_get_function_calls 
+----------------------------
+                           
+(1 row)
+
+-- Check that stats for relations are dropped. For that we need to access stats
+-- by oid after the DROP TABLE. Save oids.
+CREATE TABLE drop_stats_test();
+INSERT INTO drop_stats_test DEFAULT VALUES;
+SELECT 'drop_stats_test'::regclass::oid AS drop_stats_test_oid \gset
+CREATE TABLE drop_stats_test_xact();
+INSERT INTO drop_stats_test_xact DEFAULT VALUES;
+SELECT 'drop_stats_test_xact'::regclass::oid AS drop_stats_test_xact_oid \gset
+CREATE TABLE drop_stats_test_subxact();
+INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
+SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \gset
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       1
+(1 row)
+
+DROP TABLE drop_stats_test;
+SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       0
+(1 row)
+
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                0
+(1 row)
+
+-- check that rollback protects against having stats dropped and that local
+-- modifications don't pose a problem
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       1
+(1 row)
+
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tuples_inserted 
+-----------------------------
+                           1
+(1 row)
+
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                0
+(1 row)
+
+BEGIN;
+INSERT INTO drop_stats_test_xact DEFAULT VALUES;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                1
+(1 row)
+
+DROP TABLE drop_stats_test_xact;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                0
+(1 row)
+
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       1
+(1 row)
+
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tuples_inserted 
+-----------------------------
+                           2
+(1 row)
+
+-- transactional drop
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       1
+(1 row)
+
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tuples_inserted 
+-----------------------------
+                           2
+(1 row)
+
+BEGIN;
+INSERT INTO drop_stats_test_xact DEFAULT VALUES;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                1
+(1 row)
+
+DROP TABLE drop_stats_test_xact;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                0
+(1 row)
+
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       0
+(1 row)
+
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+ pg_stat_get_tuples_inserted 
+-----------------------------
+                           0
+(1 row)
+
+-- savepoint rollback (2 levels)
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       1
+(1 row)
+
+BEGIN;
+INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
+SAVEPOINT sp1;
+INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                2
+(1 row)
+
+SAVEPOINT sp2;
+DROP TABLE drop_stats_test_subxact;
+ROLLBACK TO SAVEPOINT sp2;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+ pg_stat_get_xact_tuples_inserted 
+----------------------------------
+                                2
+(1 row)
+
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       3
+(1 row)
+
+-- savepoint rolback (1 level)
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       3
+(1 row)
+
+BEGIN;
+SAVEPOINT sp1;
+DROP TABLE drop_stats_test_subxact;
+SAVEPOINT sp2;
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       3
+(1 row)
+
+-- and now actually drop
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       3
+(1 row)
+
+BEGIN;
+SAVEPOINT sp1;
+DROP TABLE drop_stats_test_subxact;
+SAVEPOINT sp2;
+RELEASE SAVEPOINT sp1;
+COMMIT;
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+ pg_stat_get_live_tuples 
+-------------------------
+                       0
+(1 row)
+
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
+----
+-- pg_stat_get_snapshot_timestamp behavior
+----
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+-- no snapshot yet, return NULL
+SELECT pg_stat_get_snapshot_timestamp();
+ pg_stat_get_snapshot_timestamp 
+--------------------------------
+(1 row)
+
+-- any attempt at accessing stats will build snapshot
+SELECT pg_stat_get_function_calls(0);
+ pg_stat_get_function_calls 
+----------------------------
+                           
+(1 row)
+
+SELECT pg_stat_get_snapshot_timestamp() >= NOW();
+ ?column? 
+----------
+ t
+(1 row)
+
+-- shows NULL again after clearing
+SELECT pg_stat_clear_snapshot();
+ pg_stat_clear_snapshot 
+------------------------
+(1 row)
+
+SELECT pg_stat_get_snapshot_timestamp();
+ pg_stat_get_snapshot_timestamp 
+--------------------------------
+(1 row)
+
+COMMIT;
+----
+-- pg_stat_have_stats behavior
+----
+-- fixed-numbered stats exist
+SELECT pg_stat_have_stats('bgwriter', 0, 0);
+ pg_stat_have_stats 
+--------------------
+ t
+(1 row)
+
+-- unknown stats kinds error out
+SELECT pg_stat_have_stats('zaphod', 0, 0);
+ERROR:  invalid statistics kind: "zaphod"
+-- db stats have objoid 0
+SELECT pg_stat_have_stats('database', (SELECT oid FROM pg_database WHERE datname = current_database()), 1);
+ pg_stat_have_stats 
+--------------------
+ f
+(1 row)
+
+SELECT pg_stat_have_stats('database', (SELECT oid FROM pg_database WHERE datname = current_database()), 0);
+ pg_stat_have_stats 
+--------------------
+ t
+(1 row)
+
 -- ensure that stats accessors handle NULL input correctly
 SELECT pg_stat_get_replication_slot(NULL);
  pg_stat_get_replication_slot 
index 7c77c3312152a28e2642053ac0aadfbcb4701a7d..4d26671da7b2b0a44b9eec079c25fcf0069d984a 100644 (file)
@@ -13,6 +13,8 @@ SET enable_seqscan TO on;
 SET enable_indexscan TO on;
 -- for the moment, we don't want index-only scans here
 SET enable_indexonlyscan TO off;
+-- not enabled by default, but we want to test it...
+SET track_functions TO 'all';
 
 -- save counters
 BEGIN;
@@ -121,9 +123,196 @@ FROM prevstats AS pr;
 
 COMMIT;
 
+----
+-- Basic tests for track_functions
+---
+CREATE FUNCTION stats_test_func1() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
+SELECT 'stats_test_func1()'::regprocedure::oid AS stats_test_func1_oid \gset
+CREATE FUNCTION stats_test_func2() RETURNS VOID LANGUAGE plpgsql AS $$BEGIN END;$$;
+SELECT 'stats_test_func2()'::regprocedure::oid AS stats_test_func2_oid \gset
+
+-- test that stats are accumulated
+BEGIN;
+SET LOCAL stats_fetch_consistency = none;
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
+SELECT stats_test_func1();
+SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
+SELECT stats_test_func1();
+SELECT pg_stat_get_xact_function_calls(:stats_test_func1_oid);
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+COMMIT;
+
+-- Verify that function stats are not transactional
+
+-- rolled back savepoint in committing transaction
+BEGIN;
+SELECT stats_test_func2();
+SAVEPOINT foo;
+SELECT stats_test_func2();
+ROLLBACK TO SAVEPOINT foo;
+SELECT pg_stat_get_xact_function_calls(:stats_test_func2_oid);
+SELECT stats_test_func2();
+COMMIT;
+
+-- rolled back transaction
+BEGIN;
+SELECT stats_test_func2();
+ROLLBACK;
+
+SELECT pg_stat_force_next_flush();
+
+-- check collected stats
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid;
+
+
+-- check that a rolled back drop function stats leaves stats alive
+BEGIN;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+DROP FUNCTION stats_test_func1();
+-- shouldn't be visible via view
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+-- but still via oid access
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+ROLLBACK;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+
+
+-- check that function dropped in main transaction leaves no stats behind
+BEGIN;
+DROP FUNCTION stats_test_func1();
+COMMIT;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func1_oid;
+SELECT pg_stat_get_function_calls(:stats_test_func1_oid);
+
+-- check that function dropped in a subtransaction leaves no stats behind
+BEGIN;
+SELECT stats_test_func2();
+SAVEPOINT a;
+SELECT stats_test_func2();
+SAVEPOINT b;
+DROP FUNCTION stats_test_func2();
+COMMIT;
+SELECT funcname, calls FROM pg_stat_user_functions WHERE funcid = :stats_test_func2_oid;
+SELECT pg_stat_get_function_calls(:stats_test_func2_oid);
+
+
+-- Check that stats for relations are dropped. For that we need to access stats
+-- by oid after the DROP TABLE. Save oids.
+CREATE TABLE drop_stats_test();
+INSERT INTO drop_stats_test DEFAULT VALUES;
+SELECT 'drop_stats_test'::regclass::oid AS drop_stats_test_oid \gset
+
+CREATE TABLE drop_stats_test_xact();
+INSERT INTO drop_stats_test_xact DEFAULT VALUES;
+SELECT 'drop_stats_test_xact'::regclass::oid AS drop_stats_test_xact_oid \gset
+
+CREATE TABLE drop_stats_test_subxact();
+INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
+SELECT 'drop_stats_test_subxact'::regclass::oid AS drop_stats_test_subxact_oid \gset
+
+SELECT pg_stat_force_next_flush();
+
+SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+DROP TABLE drop_stats_test;
+SELECT pg_stat_get_live_tuples(:drop_stats_test_oid);
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_oid);
+
+-- check that rollback protects against having stats dropped and that local
+-- modifications don't pose a problem
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+BEGIN;
+INSERT INTO drop_stats_test_xact DEFAULT VALUES;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+DROP TABLE drop_stats_test_xact;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+
+-- transactional drop
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+BEGIN;
+INSERT INTO drop_stats_test_xact DEFAULT VALUES;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+DROP TABLE drop_stats_test_xact;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_xact_oid);
+COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT pg_stat_get_live_tuples(:drop_stats_test_xact_oid);
+SELECT pg_stat_get_tuples_inserted(:drop_stats_test_xact_oid);
+
+-- savepoint rollback (2 levels)
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+BEGIN;
+INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
+SAVEPOINT sp1;
+INSERT INTO drop_stats_test_subxact DEFAULT VALUES;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+SAVEPOINT sp2;
+DROP TABLE drop_stats_test_subxact;
+ROLLBACK TO SAVEPOINT sp2;
+SELECT pg_stat_get_xact_tuples_inserted(:drop_stats_test_subxact_oid);
+COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+
+-- savepoint rolback (1 level)
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+BEGIN;
+SAVEPOINT sp1;
+DROP TABLE drop_stats_test_subxact;
+SAVEPOINT sp2;
+ROLLBACK TO SAVEPOINT sp1;
+COMMIT;
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+
+-- and now actually drop
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+BEGIN;
+SAVEPOINT sp1;
+DROP TABLE drop_stats_test_subxact;
+SAVEPOINT sp2;
+RELEASE SAVEPOINT sp1;
+COMMIT;
+SELECT pg_stat_get_live_tuples(:drop_stats_test_subxact_oid);
+
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
 
+----
+-- pg_stat_get_snapshot_timestamp behavior
+----
+BEGIN;
+SET LOCAL stats_fetch_consistency = snapshot;
+-- no snapshot yet, return NULL
+SELECT pg_stat_get_snapshot_timestamp();
+-- any attempt at accessing stats will build snapshot
+SELECT pg_stat_get_function_calls(0);
+SELECT pg_stat_get_snapshot_timestamp() >= NOW();
+-- shows NULL again after clearing
+SELECT pg_stat_clear_snapshot();
+SELECT pg_stat_get_snapshot_timestamp();
+COMMIT;
+
+----
+-- pg_stat_have_stats behavior
+----
+-- fixed-numbered stats exist
+SELECT pg_stat_have_stats('bgwriter', 0, 0);
+-- unknown stats kinds error out
+SELECT pg_stat_have_stats('zaphod', 0, 0);
+-- db stats have objoid 0
+SELECT pg_stat_have_stats('database', (SELECT oid FROM pg_database WHERE datname = current_database()), 1);
+SELECT pg_stat_have_stats('database', (SELECT oid FROM pg_database WHERE datname = current_database()), 0);
+
+
 -- ensure that stats accessors handle NULL input correctly
 SELECT pg_stat_get_replication_slot(NULL);
 SELECT pg_stat_get_subscription_stats(NULL);