1 # Copyright (c) 2021-2025, PostgreSQL Global Development Group
3 # Test integrity of intermediate states by PITR to those states
5 use warnings FATAL
=> 'all';
6 use PostgreSQL
::Test
::Cluster
;
7 use PostgreSQL
::Test
::Utils
;
10 # origin node: generate WAL records of interest.
11 my $origin = PostgreSQL
::Test
::Cluster
->new('origin');
12 $origin->init(has_archiving
=> 1, allows_streaming
=> 1);
13 $origin->append_conf('postgresql.conf', 'autovacuum = off');
15 $origin->backup('my_backup');
16 # Create a table with each of 6 PK values spanning 1/4 of a block. Delete the
17 # first four, so one index leaf is eligible for deletion. Make a replication
18 # slot just so pg_walinspect will always have access to later WAL.
21 CREATE EXTENSION amcheck;
22 CREATE EXTENSION pg_walinspect;
23 CREATE TABLE not_leftmost (c text STORAGE PLAIN);
24 INSERT INTO not_leftmost
25 SELECT repeat(n::text, database_block_size / 4)
26 FROM generate_series(1,6) t(n), pg_control_init();
27 ALTER TABLE not_leftmost ADD CONSTRAINT not_leftmost_pk PRIMARY KEY (c);
28 DELETE FROM not_leftmost WHERE c ~ '^[1-4]';
29 SELECT pg_create_physical_replication_slot('for_walinspect', true, false);
32 $origin->safe_psql('postgres', $setup);
33 my $before_vacuum_lsn =
34 $origin->safe_psql('postgres', "SELECT pg_current_wal_lsn()");
35 # VACUUM to delete the aforementioned leaf page. Force an XLogFlush() by
36 # dropping a permanent table. That way, the XLogReader infrastructure can
37 # always see VACUUM's records, even under synchronous_commit=off. Finally,
38 # find the LSN of that VACUUM's last UNLINK_PAGE record.
40 SET synchronous_commit = off;
41 VACUUM (VERBOSE, INDEX_CLEANUP ON) not_leftmost;
42 CREATE TABLE XLogFlush ();
45 FROM pg_get_wal_records_info('$before_vacuum_lsn', 'FFFFFFFF/FFFFFFFF')
46 WHERE resource_manager = 'Btree' AND record_type = 'UNLINK_PAGE';
48 my $unlink_lsn = $origin->safe_psql('postgres', $vacuum);
50 die "did not find UNLINK_PAGE record" unless $unlink_lsn;
52 # replica node: amcheck at notable points in the WAL stream
53 my $replica = PostgreSQL
::Test
::Cluster
->new('replica');
54 $replica->init_from_backup($origin, 'my_backup', has_restoring
=> 1);
55 $replica->append_conf('postgresql.conf',
56 "recovery_target_lsn = '$unlink_lsn'");
57 $replica->append_conf('postgresql.conf', 'recovery_target_inclusive = off');
58 $replica->append_conf('postgresql.conf', 'recovery_target_action = promote');
60 $replica->poll_query_until('postgres', "SELECT pg_is_in_recovery() = 'f';")
61 or die "Timed out while waiting for PITR promotion";
62 # recovery done; run amcheck
63 my $debug = "SET client_min_messages = 'debug1'";
67 "$debug; SELECT bt_index_parent_check('not_leftmost_pk', true)",
69 print STDERR
$stderr, "\n";
70 is
($rc, 0, "bt_index_parent_check passes");
73 qr/interrupted page deletion detected/,
74 "bt_index_parent_check: interrupted page deletion detected");
77 "$debug; SELECT bt_index_check('not_leftmost_pk', true)",
79 print STDERR
$stderr, "\n";
80 is
($rc, 0, "bt_index_check passes");