Pre-beta mechanical code beautification.
[pgsql.git] / src / bin / pg_combinebackup / t / 002_compare_backups.pl
blobf032959ef5c47c277beda1c54c8b15e0d4fe4fb8
1 # Copyright (c) 2021-2024, PostgreSQL Global Development Group
3 use strict;
4 use warnings FATAL => 'all';
5 use File::Compare;
6 use PostgreSQL::Test::Cluster;
7 use PostgreSQL::Test::Utils;
8 use Test::More;
10 my $tempdir = PostgreSQL::Test::Utils::tempdir_short();
12 # Set up a new database instance.
13 my $primary = PostgreSQL::Test::Cluster->new('primary');
14 $primary->init(has_archiving => 1, allows_streaming => 1);
15 $primary->append_conf('postgresql.conf', 'summarize_wal = on');
16 $primary->start;
17 my $tsprimary = $tempdir . '/ts';
18 mkdir($tsprimary) || die "mkdir $tsprimary: $!";
20 # Create some test tables, each containing one row of data, plus a whole
21 # extra database.
22 $primary->safe_psql('postgres', <<EOM);
23 CREATE TABLE will_change (a int, b text);
24 INSERT INTO will_change VALUES (1, 'initial test row');
25 CREATE TABLE will_grow (a int, b text);
26 INSERT INTO will_grow VALUES (1, 'initial test row');
27 CREATE TABLE will_shrink (a int, b text);
28 INSERT INTO will_shrink VALUES (1, 'initial test row');
29 CREATE TABLE will_get_vacuumed (a int, b text);
30 INSERT INTO will_get_vacuumed VALUES (1, 'initial test row');
31 CREATE TABLE will_get_dropped (a int, b text);
32 INSERT INTO will_get_dropped VALUES (1, 'initial test row');
33 CREATE TABLE will_get_rewritten (a int, b text);
34 INSERT INTO will_get_rewritten VALUES (1, 'initial test row');
35 CREATE DATABASE db_will_get_dropped;
36 CREATE TABLESPACE ts1 LOCATION '$tsprimary';
37 CREATE TABLE will_not_change_in_ts (a int, b text) TABLESPACE ts1;
38 INSERT INTO will_not_change_in_ts VALUES (1, 'initial test row');
39 CREATE TABLE will_change_in_ts (a int, b text) TABLESPACE ts1;
40 INSERT INTO will_change_in_ts VALUES (1, 'initial test row');
41 CREATE TABLE will_get_dropped_in_ts (a int, b text);
42 INSERT INTO will_get_dropped_in_ts VALUES (1, 'initial test row');
43 EOM
45 # Read list of tablespace OIDs. There should be just one.
46 my @tsoids = grep { /^\d+/ } slurp_dir($primary->data_dir . '/pg_tblspc');
47 is(0 + @tsoids, 1, "exactly one user-defined tablespace");
48 my $tsoid = $tsoids[0];
50 # Take a full backup.
51 my $backup1path = $primary->backup_dir . '/backup1';
52 my $tsbackup1path = $tempdir . '/ts1backup';
53 mkdir($tsbackup1path) || die "mkdir $tsbackup1path: $!";
54 $primary->command_ok(
56 'pg_basebackup', '-D',
57 $backup1path, '--no-sync',
58 '-cfast', "-T${tsprimary}=${tsbackup1path}"
60 "full backup");
62 # Now make some database changes.
63 $primary->safe_psql('postgres', <<EOM);
64 UPDATE will_change SET b = 'modified value' WHERE a = 1;
65 UPDATE will_change_in_ts SET b = 'modified value' WHERE a = 1;
66 INSERT INTO will_grow
67 SELECT g, 'additional row' FROM generate_series(2, 5000) g;
68 TRUNCATE will_shrink;
69 VACUUM will_get_vacuumed;
70 DROP TABLE will_get_dropped;
71 DROP TABLE will_get_dropped_in_ts;
72 CREATE TABLE newly_created (a int, b text);
73 INSERT INTO newly_created VALUES (1, 'row for new table');
74 CREATE TABLE newly_created_in_ts (a int, b text) TABLESPACE ts1;
75 INSERT INTO newly_created_in_ts VALUES (1, 'row for new table');
76 VACUUM FULL will_get_rewritten;
77 DROP DATABASE db_will_get_dropped;
78 CREATE DATABASE db_newly_created;
79 EOM
81 # Take an incremental backup.
82 my $backup2path = $primary->backup_dir . '/backup2';
83 my $tsbackup2path = $tempdir . '/tsbackup2';
84 mkdir($tsbackup2path) || die "mkdir $tsbackup2path: $!";
85 $primary->command_ok(
87 'pg_basebackup', '-D',
88 $backup2path, '--no-sync',
89 '-cfast', "-T${tsprimary}=${tsbackup2path}",
90 '--incremental', $backup1path . '/backup_manifest'
92 "incremental backup");
94 # Find an LSN to which either backup can be recovered.
95 my $lsn = $primary->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
97 # Make sure that the WAL segment containing that LSN has been archived.
98 # PostgreSQL won't issue two consecutive XLOG_SWITCH records, and the backup
99 # just issued one, so call txid_current() to generate some WAL activity
100 # before calling pg_switch_wal().
101 $primary->safe_psql('postgres', 'SELECT txid_current();');
102 $primary->safe_psql('postgres', 'SELECT pg_switch_wal()');
104 # Now wait for the LSN we chose above to be archived.
105 my $archive_wait_query =
106 "SELECT pg_walfile_name('$lsn') <= last_archived_wal FROM pg_stat_archiver;";
107 $primary->poll_query_until('postgres', $archive_wait_query)
108 or die "Timed out while waiting for WAL segment to be archived";
110 # Perform PITR from the full backup. Disable archive_mode so that the archive
111 # doesn't find out about the new timeline; that way, the later PITR below will
112 # choose the same timeline.
113 my $tspitr1path = $tempdir . '/tspitr1';
114 my $pitr1 = PostgreSQL::Test::Cluster->new('pitr1');
115 $pitr1->init_from_backup(
116 $primary, 'backup1',
117 standby => 1,
118 has_restoring => 1,
119 tablespace_map => { $tsoid => $tspitr1path });
120 $pitr1->append_conf(
121 'postgresql.conf', qq{
122 recovery_target_lsn = '$lsn'
123 recovery_target_action = 'promote'
124 archive_mode = 'off'
126 $pitr1->start();
128 # Perform PITR to the same LSN from the incremental backup. Use the same
129 # basic configuration as before.
130 my $tspitr2path = $tempdir . '/tspitr2';
131 my $pitr2 = PostgreSQL::Test::Cluster->new('pitr2');
132 $pitr2->init_from_backup(
133 $primary, 'backup2',
134 standby => 1,
135 has_restoring => 1,
136 combine_with_prior => ['backup1'],
137 tablespace_map => { $tsbackup2path => $tspitr2path });
138 $pitr2->append_conf(
139 'postgresql.conf', qq{
140 recovery_target_lsn = '$lsn'
141 recovery_target_action = 'promote'
142 archive_mode = 'off'
144 $pitr2->start();
146 # Wait until both servers exit recovery.
147 $pitr1->poll_query_until('postgres', "SELECT NOT pg_is_in_recovery();")
148 or die "Timed out while waiting apply to reach LSN $lsn";
149 $pitr2->poll_query_until('postgres', "SELECT NOT pg_is_in_recovery();")
150 or die "Timed out while waiting apply to reach LSN $lsn";
152 # Perform a logical dump of each server, and check that they match.
153 # It would be much nicer if we could physically compare the data files, but
154 # that doesn't really work. The contents of the page hole aren't guaranteed to
155 # be identical, and there can be other discrepancies as well. To make this work
156 # we'd need the equivalent of each AM's rm_mask function written or at least
157 # callable from Perl, and that doesn't seem practical.
159 # NB: We're just using the primary's backup directory for scratch space here.
160 # This could equally well be any other directory we wanted to pick.
161 my $backupdir = $primary->backup_dir;
162 my $dump1 = $backupdir . '/pitr1.dump';
163 my $dump2 = $backupdir . '/pitr2.dump';
164 $pitr1->command_ok(
166 'pg_dumpall', '-f',
167 $dump1, '--no-sync',
168 '--no-unlogged-table-data', '-d',
169 $pitr1->connstr('postgres'),
171 'dump from PITR 1');
172 $pitr1->command_ok(
174 'pg_dumpall', '-f',
175 $dump2, '--no-sync',
176 '--no-unlogged-table-data', '-d',
177 $pitr1->connstr('postgres'),
179 'dump from PITR 2');
181 # Compare the two dumps, there should be no differences.
182 my $compare_res = compare($dump1, $dump2);
183 note($dump1);
184 note($dump2);
185 is($compare_res, 0, "dumps are identical");
187 # Provide more context if the dumps do not match.
188 if ($compare_res != 0)
190 my ($stdout, $stderr) =
191 run_command([ 'diff', '-u', $dump1, $dump2 ]);
192 print "=== diff of $dump1 and $dump2\n";
193 print "=== stdout ===\n";
194 print $stdout;
195 print "=== stderr ===\n";
196 print $stderr;
197 print "=== EOF ===\n";
200 done_testing();