Add more tests for relation statistics with rewrites
authorMichael Paquier <[email protected]>
Mon, 10 Nov 2025 05:30:10 +0000 (14:30 +0900)
committerMichael Paquier <[email protected]>
Mon, 10 Nov 2025 05:30:10 +0000 (14:30 +0900)
While there are many tests related to relation rewrites, nothing existed
to check how the cumulative statistics behave in such cases for
relations.

A different patch is under discussion to move the relation statistics to
be tracked on a per-relfilenode basis, so as these could be rebuilt
during crash recovery.  This commit gives us a way to check (and perhaps
change) the existing behaviors for several rewrite scenarios, mixing
transactions, sub-transactions, two-phase commit and VACUUM.

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Discussion: https://postgr.es/m/[email protected]

src/test/regress/expected/stats_rewrite.out [new file with mode: 0644]
src/test/regress/expected/stats_rewrite_1.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/sql/stats_rewrite.sql [new file with mode: 0644]

diff --git a/src/test/regress/expected/stats_rewrite.out b/src/test/regress/expected/stats_rewrite.out
new file mode 100644 (file)
index 0000000..93752ba
--- /dev/null
@@ -0,0 +1,355 @@
+--
+-- Test cumulative statistics with relation rewrites
+--
+-- Two-phase commit.
+-- Table-level stats with VACUUM and rewrite after 2PC commit.
+CREATE TABLE test_2pc_timestamp (a int) WITH (autovacuum_enabled = false);
+VACUUM ANALYZE test_2pc_timestamp;
+SELECT last_analyze AS last_vacuum_analyze
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_timestamp' \gset
+BEGIN;
+ALTER TABLE test_2pc_timestamp ALTER COLUMN a TYPE int;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT last_analyze = :'last_vacuum_analyze'::timestamptz AS same_vacuum_ts
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_timestamp';
+ same_vacuum_ts 
+----------------
+ t
+(1 row)
+
+DROP TABLE test_2pc_timestamp;
+-- Table-level stats with single rewrite after 2PC commit.
+CREATE TABLE test_2pc_rewrite_alone (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_rewrite_alone VALUES (1);
+BEGIN;
+ALTER TABLE test_2pc_rewrite_alone ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_rewrite_alone';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_2pc_rewrite_alone;
+-- Table-level stats with rewrite and DMLs after 2PC commit.
+CREATE TABLE test_2pc (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc VALUES (1);
+BEGIN;
+INSERT INTO test_2pc VALUES (1);
+INSERT INTO test_2pc VALUES (2);
+INSERT INTO test_2pc VALUES (3);
+ALTER TABLE test_2pc ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          4 |          0
+(1 row)
+
+DROP TABLE test_2pc;
+-- Table-level stats with multiple rewrites after 2PC commit.
+CREATE TABLE test_2pc_multi (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_multi VALUES (1);
+BEGIN;
+INSERT INTO test_2pc_multi VALUES (1);
+INSERT INTO test_2pc_multi VALUES (2);
+ALTER TABLE test_2pc_multi ALTER COLUMN a TYPE bigint;
+INSERT INTO test_2pc_multi VALUES (3);
+INSERT INTO test_2pc_multi VALUES (4);
+ALTER TABLE test_2pc_multi ALTER COLUMN a TYPE int;
+INSERT INTO test_2pc_multi VALUES (5);
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_multi';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          6 |          0
+(1 row)
+
+DROP TABLE test_2pc_multi;
+-- Table-level stats with single rewrite after 2PC abort.
+CREATE TABLE test_2pc_rewrite_alone_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_rewrite_alone_abort VALUES (1);
+BEGIN;
+ALTER TABLE test_2pc_rewrite_alone_abort ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+ROLLBACK PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_rewrite_alone_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_2pc_rewrite_alone_abort;
+-- Table-level stats with rewrite and DMLs after 2PC abort.
+CREATE TABLE test_2pc_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_abort VALUES (1);
+BEGIN;
+INSERT INTO test_2pc_abort VALUES (1);
+INSERT INTO test_2pc_abort VALUES (2);
+ALTER TABLE test_2pc_abort ALTER COLUMN a TYPE bigint;
+INSERT INTO test_2pc_abort VALUES (3);
+PREPARE TRANSACTION 'test';
+ROLLBACK PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          1 |          3
+(1 row)
+
+DROP TABLE test_2pc_abort;
+-- Table-level stats with rewrites and subtransactions after 2PC commit.
+CREATE TABLE test_2pc_savepoint (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_savepoint VALUES (1);
+BEGIN;
+SAVEPOINT a;
+INSERT INTO test_2pc_savepoint VALUES (1);
+INSERT INTO test_2pc_savepoint VALUES (2);
+ALTER TABLE test_2pc_savepoint ALTER COLUMN a TYPE bigint;
+SAVEPOINT b;
+INSERT INTO test_2pc_savepoint VALUES (3);
+ALTER TABLE test_2pc_savepoint ALTER COLUMN a TYPE int;
+SAVEPOINT c;
+INSERT INTO test_2pc_savepoint VALUES (4);
+INSERT INTO test_2pc_savepoint VALUES (5);
+ROLLBACK TO SAVEPOINT b;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_savepoint';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          3 |          3
+(1 row)
+
+DROP TABLE test_2pc_savepoint;
+-- Table-level stats with single rewrite and VACUUM
+CREATE TABLE test_timestamp (a int) WITH (autovacuum_enabled = false);
+VACUUM ANALYZE test_timestamp;
+SELECT last_analyze AS last_vacuum_analyze
+  FROM pg_stat_all_tables WHERE relname = 'test_timestamp' \gset
+ALTER TABLE test_timestamp ALTER COLUMN a TYPE bigint;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT last_analyze = :'last_vacuum_analyze'::timestamptz AS same_vacuum_ts
+  FROM pg_stat_all_tables WHERE relname = 'test_timestamp';
+ same_vacuum_ts 
+----------------
+ t
+(1 row)
+
+DROP TABLE test_timestamp;
+-- Table-level stats with single rewrite.
+CREATE TABLE test_alone (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_alone VALUES (1);
+BEGIN;
+ALTER TABLE test_alone ALTER COLUMN a TYPE bigint;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_alone';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_alone;
+-- Table-level stats with rewrite and DMLs.
+CREATE TABLE test (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test VALUES (1);
+BEGIN;
+INSERT INTO test VALUES (1);
+INSERT INTO test VALUES (2);
+INSERT INTO test VALUES (3);
+ALTER TABLE test ALTER COLUMN a TYPE bigint;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          4 |          0
+(1 row)
+
+DROP TABLE test;
+-- Table-level stats with multiple rewrites and DMLs.
+CREATE TABLE test_multi (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_multi VALUES (1);
+BEGIN;
+INSERT INTO test_multi VALUES (1);
+INSERT INTO test_multi VALUES (2);
+ALTER TABLE test_multi ALTER COLUMN a TYPE bigint;
+INSERT INTO test_multi VALUES (3);
+INSERT INTO test_multi VALUES (4);
+ALTER TABLE test_multi ALTER COLUMN a TYPE int;
+INSERT INTO test_multi VALUES (5);
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_multi';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          6 |          0
+(1 row)
+
+DROP TABLE test_multi;
+-- Table-level stats with rewrite and rollback.
+CREATE TABLE test_rewrite_alone_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_rewrite_alone_abort VALUES (1);
+BEGIN;
+ALTER TABLE test_rewrite_alone_abort ALTER COLUMN a TYPE bigint;
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_rewrite_alone_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_rewrite_alone_abort;
+-- Table-level stats with rewrite, DMLs and rollback.
+CREATE TABLE test_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_abort VALUES (1);
+BEGIN;
+INSERT INTO test_abort VALUES (1);
+INSERT INTO test_abort VALUES (2);
+ALTER TABLE test_abort ALTER COLUMN a TYPE bigint;
+INSERT INTO test_abort VALUES (3);
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          1 |          3
+(1 row)
+
+DROP TABLE test_abort;
+-- Table-level stats with rewrites and subtransactions.
+CREATE TABLE test_savepoint (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_savepoint VALUES (1);
+BEGIN;
+SAVEPOINT a;
+INSERT INTO test_savepoint VALUES (1);
+INSERT INTO test_savepoint VALUES (2);
+ALTER TABLE test_savepoint ALTER COLUMN a TYPE bigint;
+SAVEPOINT b;
+INSERT INTO test_savepoint VALUES (3);
+ALTER TABLE test_savepoint ALTER COLUMN a TYPE int;
+SAVEPOINT c;
+INSERT INTO test_savepoint VALUES (4);
+INSERT INTO test_savepoint VALUES (5);
+ROLLBACK TO SAVEPOINT b;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_savepoint';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          3 |          3
+(1 row)
+
+DROP TABLE test_savepoint;
+-- Table-level stats with tablespace rewrite.
+CREATE TABLE test_tbs (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_tbs VALUES (1);
+ALTER TABLE test_tbs SET TABLESPACE pg_default;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_tbs';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_tbs;
diff --git a/src/test/regress/expected/stats_rewrite_1.out b/src/test/regress/expected/stats_rewrite_1.out
new file mode 100644 (file)
index 0000000..909188b
--- /dev/null
@@ -0,0 +1,376 @@
+--
+-- Test cumulative statistics with relation rewrites
+--
+-- Two-phase commit.
+-- Table-level stats with VACUUM and rewrite after 2PC commit.
+CREATE TABLE test_2pc_timestamp (a int) WITH (autovacuum_enabled = false);
+VACUUM ANALYZE test_2pc_timestamp;
+SELECT last_analyze AS last_vacuum_analyze
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_timestamp' \gset
+BEGIN;
+ALTER TABLE test_2pc_timestamp ALTER COLUMN a TYPE int;
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+COMMIT PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT last_analyze = :'last_vacuum_analyze'::timestamptz AS same_vacuum_ts
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_timestamp';
+ same_vacuum_ts 
+----------------
+ t
+(1 row)
+
+DROP TABLE test_2pc_timestamp;
+-- Table-level stats with single rewrite after 2PC commit.
+CREATE TABLE test_2pc_rewrite_alone (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_rewrite_alone VALUES (1);
+BEGIN;
+ALTER TABLE test_2pc_rewrite_alone ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+COMMIT PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_rewrite_alone';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_2pc_rewrite_alone;
+-- Table-level stats with rewrite and DMLs after 2PC commit.
+CREATE TABLE test_2pc (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc VALUES (1);
+BEGIN;
+INSERT INTO test_2pc VALUES (1);
+INSERT INTO test_2pc VALUES (2);
+INSERT INTO test_2pc VALUES (3);
+ALTER TABLE test_2pc ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+COMMIT PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          1 |          3
+(1 row)
+
+DROP TABLE test_2pc;
+-- Table-level stats with multiple rewrites after 2PC commit.
+CREATE TABLE test_2pc_multi (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_multi VALUES (1);
+BEGIN;
+INSERT INTO test_2pc_multi VALUES (1);
+INSERT INTO test_2pc_multi VALUES (2);
+ALTER TABLE test_2pc_multi ALTER COLUMN a TYPE bigint;
+INSERT INTO test_2pc_multi VALUES (3);
+INSERT INTO test_2pc_multi VALUES (4);
+ALTER TABLE test_2pc_multi ALTER COLUMN a TYPE int;
+INSERT INTO test_2pc_multi VALUES (5);
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+COMMIT PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_multi';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          1 |          5
+(1 row)
+
+DROP TABLE test_2pc_multi;
+-- Table-level stats with single rewrite after 2PC abort.
+CREATE TABLE test_2pc_rewrite_alone_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_rewrite_alone_abort VALUES (1);
+BEGIN;
+ALTER TABLE test_2pc_rewrite_alone_abort ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+ROLLBACK PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_rewrite_alone_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_2pc_rewrite_alone_abort;
+-- Table-level stats with rewrite and DMLs after 2PC abort.
+CREATE TABLE test_2pc_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_abort VALUES (1);
+BEGIN;
+INSERT INTO test_2pc_abort VALUES (1);
+INSERT INTO test_2pc_abort VALUES (2);
+ALTER TABLE test_2pc_abort ALTER COLUMN a TYPE bigint;
+INSERT INTO test_2pc_abort VALUES (3);
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+ROLLBACK PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          1 |          3
+(1 row)
+
+DROP TABLE test_2pc_abort;
+-- Table-level stats with rewrites and subtransactions after 2PC commit.
+CREATE TABLE test_2pc_savepoint (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_savepoint VALUES (1);
+BEGIN;
+SAVEPOINT a;
+INSERT INTO test_2pc_savepoint VALUES (1);
+INSERT INTO test_2pc_savepoint VALUES (2);
+ALTER TABLE test_2pc_savepoint ALTER COLUMN a TYPE bigint;
+SAVEPOINT b;
+INSERT INTO test_2pc_savepoint VALUES (3);
+ALTER TABLE test_2pc_savepoint ALTER COLUMN a TYPE int;
+SAVEPOINT c;
+INSERT INTO test_2pc_savepoint VALUES (4);
+INSERT INTO test_2pc_savepoint VALUES (5);
+ROLLBACK TO SAVEPOINT b;
+PREPARE TRANSACTION 'test';
+ERROR:  prepared transactions are disabled
+HINT:  Set "max_prepared_transactions" to a nonzero value.
+COMMIT PREPARED 'test';
+ERROR:  prepared transaction with identifier "test" does not exist
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_savepoint';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          1 |          5
+(1 row)
+
+DROP TABLE test_2pc_savepoint;
+-- Table-level stats with single rewrite and VACUUM
+CREATE TABLE test_timestamp (a int) WITH (autovacuum_enabled = false);
+VACUUM ANALYZE test_timestamp;
+SELECT last_analyze AS last_vacuum_analyze
+  FROM pg_stat_all_tables WHERE relname = 'test_timestamp' \gset
+ALTER TABLE test_timestamp ALTER COLUMN a TYPE bigint;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT last_analyze = :'last_vacuum_analyze'::timestamptz AS same_vacuum_ts
+  FROM pg_stat_all_tables WHERE relname = 'test_timestamp';
+ same_vacuum_ts 
+----------------
+ t
+(1 row)
+
+DROP TABLE test_timestamp;
+-- Table-level stats with single rewrite.
+CREATE TABLE test_alone (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_alone VALUES (1);
+BEGIN;
+ALTER TABLE test_alone ALTER COLUMN a TYPE bigint;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_alone';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_alone;
+-- Table-level stats with rewrite and DMLs.
+CREATE TABLE test (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test VALUES (1);
+BEGIN;
+INSERT INTO test VALUES (1);
+INSERT INTO test VALUES (2);
+INSERT INTO test VALUES (3);
+ALTER TABLE test ALTER COLUMN a TYPE bigint;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          4 |          0
+(1 row)
+
+DROP TABLE test;
+-- Table-level stats with multiple rewrites and DMLs.
+CREATE TABLE test_multi (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_multi VALUES (1);
+BEGIN;
+INSERT INTO test_multi VALUES (1);
+INSERT INTO test_multi VALUES (2);
+ALTER TABLE test_multi ALTER COLUMN a TYPE bigint;
+INSERT INTO test_multi VALUES (3);
+INSERT INTO test_multi VALUES (4);
+ALTER TABLE test_multi ALTER COLUMN a TYPE int;
+INSERT INTO test_multi VALUES (5);
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_multi';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          6 |          0
+(1 row)
+
+DROP TABLE test_multi;
+-- Table-level stats with rewrite and rollback.
+CREATE TABLE test_rewrite_alone_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_rewrite_alone_abort VALUES (1);
+BEGIN;
+ALTER TABLE test_rewrite_alone_abort ALTER COLUMN a TYPE bigint;
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_rewrite_alone_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_rewrite_alone_abort;
+-- Table-level stats with rewrite, DMLs and rollback.
+CREATE TABLE test_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_abort VALUES (1);
+BEGIN;
+INSERT INTO test_abort VALUES (1);
+INSERT INTO test_abort VALUES (2);
+ALTER TABLE test_abort ALTER COLUMN a TYPE bigint;
+INSERT INTO test_abort VALUES (3);
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_abort';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         4 |          1 |          3
+(1 row)
+
+DROP TABLE test_abort;
+-- Table-level stats with rewrites and subtransactions.
+CREATE TABLE test_savepoint (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_savepoint VALUES (1);
+BEGIN;
+SAVEPOINT a;
+INSERT INTO test_savepoint VALUES (1);
+INSERT INTO test_savepoint VALUES (2);
+ALTER TABLE test_savepoint ALTER COLUMN a TYPE bigint;
+SAVEPOINT b;
+INSERT INTO test_savepoint VALUES (3);
+ALTER TABLE test_savepoint ALTER COLUMN a TYPE int;
+SAVEPOINT c;
+INSERT INTO test_savepoint VALUES (4);
+INSERT INTO test_savepoint VALUES (5);
+ROLLBACK TO SAVEPOINT b;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_savepoint';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         6 |          3 |          3
+(1 row)
+
+DROP TABLE test_savepoint;
+-- Table-level stats with tablespace rewrite.
+CREATE TABLE test_tbs (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_tbs VALUES (1);
+ALTER TABLE test_tbs SET TABLESPACE pg_default;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+(1 row)
+
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_tbs';
+ n_tup_ins | n_live_tup | n_dead_tup 
+-----------+------------+------------
+         1 |          1 |          0
+(1 row)
+
+DROP TABLE test_tbs;
index a0f5fab0f5df8dc37ef5205f6b63881895adc80c..f56482fb9f1211edc8a1d4b0011ff6a2778c8d95 100644 (file)
@@ -102,7 +102,7 @@ test: publication subscription
 # Another group of parallel tests
 # select_views depends on create_view
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass stats_rewrite
 
 # ----------
 # Another group of parallel tests (JSON related)
diff --git a/src/test/regress/sql/stats_rewrite.sql b/src/test/regress/sql/stats_rewrite.sql
new file mode 100644 (file)
index 0000000..b01ad75
--- /dev/null
@@ -0,0 +1,220 @@
+--
+-- Test cumulative statistics with relation rewrites
+--
+
+-- Two-phase commit.
+-- Table-level stats with VACUUM and rewrite after 2PC commit.
+CREATE TABLE test_2pc_timestamp (a int) WITH (autovacuum_enabled = false);
+VACUUM ANALYZE test_2pc_timestamp;
+SELECT last_analyze AS last_vacuum_analyze
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_timestamp' \gset
+BEGIN;
+ALTER TABLE test_2pc_timestamp ALTER COLUMN a TYPE int;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT last_analyze = :'last_vacuum_analyze'::timestamptz AS same_vacuum_ts
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_timestamp';
+DROP TABLE test_2pc_timestamp;
+
+-- Table-level stats with single rewrite after 2PC commit.
+CREATE TABLE test_2pc_rewrite_alone (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_rewrite_alone VALUES (1);
+BEGIN;
+ALTER TABLE test_2pc_rewrite_alone ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_rewrite_alone';
+DROP TABLE test_2pc_rewrite_alone;
+
+-- Table-level stats with rewrite and DMLs after 2PC commit.
+CREATE TABLE test_2pc (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc VALUES (1);
+BEGIN;
+INSERT INTO test_2pc VALUES (1);
+INSERT INTO test_2pc VALUES (2);
+INSERT INTO test_2pc VALUES (3);
+ALTER TABLE test_2pc ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc';
+DROP TABLE test_2pc;
+
+-- Table-level stats with multiple rewrites after 2PC commit.
+CREATE TABLE test_2pc_multi (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_multi VALUES (1);
+BEGIN;
+INSERT INTO test_2pc_multi VALUES (1);
+INSERT INTO test_2pc_multi VALUES (2);
+ALTER TABLE test_2pc_multi ALTER COLUMN a TYPE bigint;
+INSERT INTO test_2pc_multi VALUES (3);
+INSERT INTO test_2pc_multi VALUES (4);
+ALTER TABLE test_2pc_multi ALTER COLUMN a TYPE int;
+INSERT INTO test_2pc_multi VALUES (5);
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_multi';
+DROP TABLE test_2pc_multi;
+
+-- Table-level stats with single rewrite after 2PC abort.
+CREATE TABLE test_2pc_rewrite_alone_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_rewrite_alone_abort VALUES (1);
+BEGIN;
+ALTER TABLE test_2pc_rewrite_alone_abort ALTER COLUMN a TYPE bigint;
+PREPARE TRANSACTION 'test';
+ROLLBACK PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_rewrite_alone_abort';
+DROP TABLE test_2pc_rewrite_alone_abort;
+
+-- Table-level stats with rewrite and DMLs after 2PC abort.
+CREATE TABLE test_2pc_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_abort VALUES (1);
+BEGIN;
+INSERT INTO test_2pc_abort VALUES (1);
+INSERT INTO test_2pc_abort VALUES (2);
+ALTER TABLE test_2pc_abort ALTER COLUMN a TYPE bigint;
+INSERT INTO test_2pc_abort VALUES (3);
+PREPARE TRANSACTION 'test';
+ROLLBACK PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_abort';
+DROP TABLE test_2pc_abort;
+
+-- Table-level stats with rewrites and subtransactions after 2PC commit.
+CREATE TABLE test_2pc_savepoint (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_2pc_savepoint VALUES (1);
+BEGIN;
+SAVEPOINT a;
+INSERT INTO test_2pc_savepoint VALUES (1);
+INSERT INTO test_2pc_savepoint VALUES (2);
+ALTER TABLE test_2pc_savepoint ALTER COLUMN a TYPE bigint;
+SAVEPOINT b;
+INSERT INTO test_2pc_savepoint VALUES (3);
+ALTER TABLE test_2pc_savepoint ALTER COLUMN a TYPE int;
+SAVEPOINT c;
+INSERT INTO test_2pc_savepoint VALUES (4);
+INSERT INTO test_2pc_savepoint VALUES (5);
+ROLLBACK TO SAVEPOINT b;
+PREPARE TRANSACTION 'test';
+COMMIT PREPARED 'test';
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_2pc_savepoint';
+DROP TABLE test_2pc_savepoint;
+
+-- Table-level stats with single rewrite and VACUUM
+CREATE TABLE test_timestamp (a int) WITH (autovacuum_enabled = false);
+VACUUM ANALYZE test_timestamp;
+SELECT last_analyze AS last_vacuum_analyze
+  FROM pg_stat_all_tables WHERE relname = 'test_timestamp' \gset
+ALTER TABLE test_timestamp ALTER COLUMN a TYPE bigint;
+SELECT pg_stat_force_next_flush();
+SELECT last_analyze = :'last_vacuum_analyze'::timestamptz AS same_vacuum_ts
+  FROM pg_stat_all_tables WHERE relname = 'test_timestamp';
+DROP TABLE test_timestamp;
+
+-- Table-level stats with single rewrite.
+CREATE TABLE test_alone (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_alone VALUES (1);
+BEGIN;
+ALTER TABLE test_alone ALTER COLUMN a TYPE bigint;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_alone';
+DROP TABLE test_alone;
+
+-- Table-level stats with rewrite and DMLs.
+CREATE TABLE test (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test VALUES (1);
+BEGIN;
+INSERT INTO test VALUES (1);
+INSERT INTO test VALUES (2);
+INSERT INTO test VALUES (3);
+ALTER TABLE test ALTER COLUMN a TYPE bigint;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test';
+DROP TABLE test;
+
+-- Table-level stats with multiple rewrites and DMLs.
+CREATE TABLE test_multi (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_multi VALUES (1);
+BEGIN;
+INSERT INTO test_multi VALUES (1);
+INSERT INTO test_multi VALUES (2);
+ALTER TABLE test_multi ALTER COLUMN a TYPE bigint;
+INSERT INTO test_multi VALUES (3);
+INSERT INTO test_multi VALUES (4);
+ALTER TABLE test_multi ALTER COLUMN a TYPE int;
+INSERT INTO test_multi VALUES (5);
+COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_multi';
+DROP TABLE test_multi;
+
+-- Table-level stats with rewrite and rollback.
+CREATE TABLE test_rewrite_alone_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_rewrite_alone_abort VALUES (1);
+BEGIN;
+ALTER TABLE test_rewrite_alone_abort ALTER COLUMN a TYPE bigint;
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_rewrite_alone_abort';
+DROP TABLE test_rewrite_alone_abort;
+
+-- Table-level stats with rewrite, DMLs and rollback.
+CREATE TABLE test_abort (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_abort VALUES (1);
+BEGIN;
+INSERT INTO test_abort VALUES (1);
+INSERT INTO test_abort VALUES (2);
+ALTER TABLE test_abort ALTER COLUMN a TYPE bigint;
+INSERT INTO test_abort VALUES (3);
+ROLLBACK;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_abort';
+DROP TABLE test_abort;
+
+-- Table-level stats with rewrites and subtransactions.
+CREATE TABLE test_savepoint (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_savepoint VALUES (1);
+BEGIN;
+SAVEPOINT a;
+INSERT INTO test_savepoint VALUES (1);
+INSERT INTO test_savepoint VALUES (2);
+ALTER TABLE test_savepoint ALTER COLUMN a TYPE bigint;
+SAVEPOINT b;
+INSERT INTO test_savepoint VALUES (3);
+ALTER TABLE test_savepoint ALTER COLUMN a TYPE int;
+SAVEPOINT c;
+INSERT INTO test_savepoint VALUES (4);
+INSERT INTO test_savepoint VALUES (5);
+ROLLBACK TO SAVEPOINT b;
+COMMIT;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_savepoint';
+DROP TABLE test_savepoint;
+
+-- Table-level stats with tablespace rewrite.
+CREATE TABLE test_tbs (a int) WITH (autovacuum_enabled = false);
+INSERT INTO test_tbs VALUES (1);
+ALTER TABLE test_tbs SET TABLESPACE pg_default;
+SELECT pg_stat_force_next_flush();
+SELECT n_tup_ins, n_live_tup, n_dead_tup
+  FROM pg_stat_all_tables WHERE relname = 'test_tbs';
+DROP TABLE test_tbs;