# Copyright (c) 2021-2022, PostgreSQL Global Development Group use strict; use warnings; use PostgreSQL::Test::Utils; use Test::More; use FindBin; use lib $FindBin::RealBin; use RewindTest; sub run_test { my $test_mode = shift; RewindTest::setup_cluster($test_mode); RewindTest::start_primary(); # Create a test table and insert a row in primary. primary_psql("CREATE TABLE tbl1 (d text)"); primary_psql("INSERT INTO tbl1 VALUES ('in primary')"); # This test table will be used to test truncation, i.e. the table # is extended in the old primary after promotion primary_psql("CREATE TABLE trunc_tbl (d text)"); primary_psql("INSERT INTO trunc_tbl VALUES ('in primary')"); # This test table will be used to test the "copy-tail" case, i.e. the # table is truncated in the old primary after promotion primary_psql("CREATE TABLE tail_tbl (id integer, d text)"); primary_psql("INSERT INTO tail_tbl VALUES (0, 'in primary')"); # This test table is dropped in the old primary after promotion. primary_psql("CREATE TABLE drop_tbl (d text)"); primary_psql("INSERT INTO drop_tbl VALUES ('in primary')"); primary_psql("CHECKPOINT"); RewindTest::create_standby($test_mode); # Insert additional data on primary that will be replicated to standby primary_psql("INSERT INTO tbl1 values ('in primary, before promotion')"); primary_psql( "INSERT INTO trunc_tbl values ('in primary, before promotion')"); primary_psql( "INSERT INTO tail_tbl SELECT g, 'in primary, before promotion: ' || g FROM generate_series(1, 10000) g" ); primary_psql('CHECKPOINT'); RewindTest::promote_standby(); # Insert a row in the old primary. This causes the primary and standby # to have "diverged", it's no longer possible to just apply the # standy's logs over primary directory - you need to rewind. primary_psql("INSERT INTO tbl1 VALUES ('in primary, after promotion')"); # Also insert a new row in the standby, which won't be present in the # old primary. standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')"); # Insert enough rows to trunc_tbl to extend the file. pg_rewind should # truncate it back to the old size. primary_psql( "INSERT INTO trunc_tbl SELECT 'in primary, after promotion: ' || g FROM generate_series(1, 10000) g" ); # Truncate tail_tbl. pg_rewind should copy back the truncated part # (We cannot use an actual TRUNCATE command here, as that creates a # whole new relfilenode) primary_psql("DELETE FROM tail_tbl WHERE id > 10"); primary_psql("VACUUM tail_tbl"); # Drop drop_tbl. pg_rewind should copy it back. primary_psql( "insert into drop_tbl values ('in primary, after promotion')"); primary_psql("DROP TABLE drop_tbl"); # Before running pg_rewind, do a couple of extra tests with several # option combinations. As the code paths taken by those tests # do not change for the "local" and "remote" modes, just run them # in "local" mode for simplicity's sake. if ($test_mode eq 'local') { my $primary_pgdata = $node_primary->data_dir; my $standby_pgdata = $node_standby->data_dir; # First check that pg_rewind fails if the target cluster is # not stopped as it fails to start up for the forced recovery # step. command_fails( [ 'pg_rewind', '--debug', '--source-pgdata', $standby_pgdata, '--target-pgdata', $primary_pgdata, '--no-sync' ], 'pg_rewind with running target'); # Again with --no-ensure-shutdown, which should equally fail. # This time pg_rewind complains without attempting to perform # recovery once. command_fails( [ 'pg_rewind', '--debug', '--source-pgdata', $standby_pgdata, '--target-pgdata', $primary_pgdata, '--no-sync', '--no-ensure-shutdown' ], 'pg_rewind --no-ensure-shutdown with running target'); # Stop the target, and attempt to run with a local source # still running. This fails as pg_rewind requires to have # a source cleanly stopped. $node_primary->stop; command_fails( [ 'pg_rewind', '--debug', '--source-pgdata', $standby_pgdata, '--target-pgdata', $primary_pgdata, '--no-sync', '--no-ensure-shutdown' ], 'pg_rewind with unexpected running source'); # Stop the target cluster cleanly, and run again pg_rewind # with --dry-run mode. If anything gets generated in the data # folder, the follow-up run of pg_rewind will most likely fail, # so keep this test as the last one of this subset. $node_standby->stop; command_ok( [ 'pg_rewind', '--debug', '--source-pgdata', $standby_pgdata, '--target-pgdata', $primary_pgdata, '--no-sync', '--dry-run' ], 'pg_rewind --dry-run'); # Both clusters need to be alive moving forward. $node_standby->start; $node_primary->start; } RewindTest::run_pg_rewind($test_mode); check_query( 'SELECT * FROM tbl1', qq(in primary in primary, before promotion in standby, after promotion ), 'table content'); check_query( 'SELECT * FROM trunc_tbl', qq(in primary in primary, before promotion ), 'truncation'); check_query( 'SELECT count(*) FROM tail_tbl', qq(10001 ), 'tail-copy'); check_query( 'SELECT * FROM drop_tbl', qq(in primary ), 'drop'); # Permissions on PGDATA should be default SKIP: { skip "unix-style permissions not supported on Windows", 1 if ($windows_os); ok(check_mode_recursive($node_primary->data_dir(), 0700, 0600), 'check PGDATA permissions'); } RewindTest::clean_rewind_test(); return; } # Run the test in both modes run_test('local'); run_test('remote'); run_test('archive'); done_testing();