|
| 1 | +# Copyright (c) 2021-2025, PostgreSQL Global Development Group |
| 2 | + |
| 3 | +use strict; |
| 4 | +use warnings FATAL => 'all'; |
| 5 | + |
| 6 | +use PostgreSQL::Test::Cluster; |
| 7 | +use PostgreSQL::Test::Utils; |
| 8 | +use Test::More; |
| 9 | + |
| 10 | +my $tempdir = PostgreSQL::Test::Utils::tempdir; |
| 11 | +my $run_db = 'postgres'; |
| 12 | +my $sep = $windows_os ? "\\" : "/"; |
| 13 | + |
| 14 | +# Tablespace locations used by "restore_tablespace" test case. |
| 15 | +my $tablespace1 = "${tempdir}${sep}tbl1"; |
| 16 | +my $tablespace2 = "${tempdir}${sep}tbl2"; |
| 17 | +mkdir($tablespace1) || die "mkdir $tablespace1 $!"; |
| 18 | +mkdir($tablespace2) || die "mkdir $tablespace2 $!"; |
| 19 | + |
| 20 | +# Scape tablespace locations on Windows. |
| 21 | +$tablespace1 = $windows_os ? ($tablespace1 =~ s/\\/\\\\/gr) : $tablespace1; |
| 22 | +$tablespace2 = $windows_os ? ($tablespace2 =~ s/\\/\\\\/gr) : $tablespace2; |
| 23 | + |
| 24 | +# Where pg_dumpall will be executed. |
| 25 | +my $node = PostgreSQL::Test::Cluster->new('node'); |
| 26 | +$node->init; |
| 27 | +$node->start; |
| 28 | + |
| 29 | + |
| 30 | +############################################################### |
| 31 | +# Definition of the pg_dumpall test cases to run. |
| 32 | +# |
| 33 | +# Each of these test cases are named and those names are used for fail |
| 34 | +# reporting and also to save the dump and restore information needed for the |
| 35 | +# test to assert. |
| 36 | +# |
| 37 | +# The "setup_sql" is a psql valid script that contains SQL commands to execute |
| 38 | +# before of actually execute the tests. The setups are all executed before of |
| 39 | +# any test execution. |
| 40 | +# |
| 41 | +# The "dump_cmd" and "restore_cmd" are the commands that will be executed. The |
| 42 | +# "restore_cmd" must have the --file flag to save the restore output so that we |
| 43 | +# can assert on it. |
| 44 | +# |
| 45 | +# The "like" and "unlike" is a regexp that is used to match the pg_restore |
| 46 | +# output. It must have at least one of then filled per test cases but it also |
| 47 | +# can have both. See "excluding_databases" test case for example. |
| 48 | +my %pgdumpall_runs = ( |
| 49 | + restore_roles => { |
| 50 | + setup_sql => ' |
| 51 | + CREATE ROLE dumpall WITH ENCRYPTED PASSWORD \'admin\' SUPERUSER; |
| 52 | + CREATE ROLE dumpall2 WITH REPLICATION CONNECTION LIMIT 10;', |
| 53 | + dump_cmd => [ |
| 54 | + 'pg_dumpall', |
| 55 | + '--format' => 'directory', |
| 56 | + '--file' => "$tempdir/restore_roles", |
| 57 | + ], |
| 58 | + restore_cmd => [ |
| 59 | + 'pg_restore', '-C', |
| 60 | + '--format' => 'directory', |
| 61 | + '--file' => "$tempdir/restore_roles.sql", |
| 62 | + "$tempdir/restore_roles", |
| 63 | + ], |
| 64 | + like => qr/ |
| 65 | + ^\s*\QCREATE ROLE dumpall;\E\s*\n |
| 66 | + \s*\QALTER ROLE dumpall WITH SUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256\E |
| 67 | + [^']+';\s*\n |
| 68 | + \s*\QCREATE ROLE dumpall2;\E |
| 69 | + \s*\QALTER ROLE dumpall2 WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN REPLICATION NOBYPASSRLS CONNECTION LIMIT 10;\E |
| 70 | + /xm |
| 71 | + }, |
| 72 | + |
| 73 | + restore_tablespace => { |
| 74 | + setup_sql => " |
| 75 | + CREATE ROLE tap; |
| 76 | + CREATE TABLESPACE tbl1 OWNER tap LOCATION '$tablespace1'; |
| 77 | + CREATE TABLESPACE tbl2 OWNER tap LOCATION '$tablespace2' WITH (seq_page_cost=1.0);", |
| 78 | + dump_cmd => [ |
| 79 | + 'pg_dumpall', |
| 80 | + '--format' => 'directory', |
| 81 | + '--file' => "$tempdir/restore_tablespace", |
| 82 | + ], |
| 83 | + restore_cmd => [ |
| 84 | + 'pg_restore', '-C', |
| 85 | + '--format' => 'directory', |
| 86 | + '--file' => "$tempdir/restore_tablespace.sql", |
| 87 | + "$tempdir/restore_tablespace", |
| 88 | + ], |
| 89 | + # Match "E" as optional since it is added on LOCATION when running on |
| 90 | + # Windows. |
| 91 | + like => qr/^ |
| 92 | + \n\QCREATE TABLESPACE tbl1 OWNER tap LOCATION \E(?:E)?\Q'$tablespace1';\E |
| 93 | + \n\QCREATE TABLESPACE tbl2 OWNER tap LOCATION \E(?:E)?\Q'$tablespace2';\E |
| 94 | + \n\QALTER TABLESPACE tbl2 SET (seq_page_cost=1.0);\E |
| 95 | + /xm, |
| 96 | + }, |
| 97 | + |
| 98 | + restore_grants => { |
| 99 | + setup_sql => " |
| 100 | + CREATE DATABASE tapgrantsdb; |
| 101 | + CREATE SCHEMA private; |
| 102 | + CREATE SEQUENCE serial START 101; |
| 103 | + CREATE FUNCTION fn() RETURNS void AS \$\$ |
| 104 | + BEGIN |
| 105 | + END; |
| 106 | + \$\$ LANGUAGE plpgsql; |
| 107 | + CREATE ROLE super; |
| 108 | + CREATE ROLE grant1; |
| 109 | + CREATE ROLE grant2; |
| 110 | + CREATE ROLE grant3; |
| 111 | + CREATE ROLE grant4; |
| 112 | + CREATE ROLE grant5; |
| 113 | + CREATE ROLE grant6; |
| 114 | + CREATE ROLE grant7; |
| 115 | + CREATE ROLE grant8; |
| 116 | +
|
| 117 | + CREATE TABLE t (id int); |
| 118 | +
|
| 119 | + GRANT SELECT ON TABLE t TO grant1; |
| 120 | + GRANT INSERT ON TABLE t TO grant2; |
| 121 | + GRANT ALL PRIVILEGES ON TABLE t to grant3; |
| 122 | + GRANT CONNECT, CREATE ON DATABASE tapgrantsdb TO grant4; |
| 123 | + GRANT USAGE, CREATE ON SCHEMA private TO grant5; |
| 124 | + GRANT USAGE, SELECT, UPDATE ON SEQUENCE serial TO grant6; |
| 125 | + GRANT super TO grant7; |
| 126 | + GRANT EXECUTE ON FUNCTION fn() TO grant8; |
| 127 | + ", |
| 128 | + dump_cmd => [ |
| 129 | + 'pg_dumpall', |
| 130 | + '--format' => 'directory', |
| 131 | + '--file' => "$tempdir/restore_grants", |
| 132 | + ], |
| 133 | + restore_cmd => [ |
| 134 | + 'pg_restore', '-C', |
| 135 | + '--format' => 'directory', |
| 136 | + '--file' => "$tempdir/restore_grants.sql", |
| 137 | + "$tempdir/restore_grants", |
| 138 | + ], |
| 139 | + like => qr/^ |
| 140 | + \n\QGRANT super TO grant7 WITH INHERIT TRUE GRANTED BY\E |
| 141 | + (.*\n)* |
| 142 | + \n\QGRANT ALL ON SCHEMA private TO grant5;\E |
| 143 | + (.*\n)* |
| 144 | + \n\QGRANT ALL ON FUNCTION public.fn() TO grant8;\E |
| 145 | + (.*\n)* |
| 146 | + \n\QGRANT ALL ON SEQUENCE public.serial TO grant6;\E |
| 147 | + (.*\n)* |
| 148 | + \n\QGRANT SELECT ON TABLE public.t TO grant1;\E |
| 149 | + \n\QGRANT INSERT ON TABLE public.t TO grant2;\E |
| 150 | + \n\QGRANT ALL ON TABLE public.t TO grant3;\E |
| 151 | + (.*\n)* |
| 152 | + \n\QGRANT CREATE,CONNECT ON DATABASE tapgrantsdb TO grant4;\E |
| 153 | + /xm, |
| 154 | + }, |
| 155 | + |
| 156 | + excluding_databases => { |
| 157 | + setup_sql => 'CREATE DATABASE db1; |
| 158 | + \c db1 |
| 159 | + CREATE TABLE t1 (id int); |
| 160 | + CREATE TABLE t2 (id int); |
| 161 | +
|
| 162 | + CREATE DATABASE db2; |
| 163 | + \c db2 |
| 164 | + CREATE TABLE t3 (id int); |
| 165 | + CREATE TABLE t4 (id int); |
| 166 | +
|
| 167 | + CREATE DATABASE dbex3; |
| 168 | + \c dbex3 |
| 169 | + CREATE TABLE t5 (id int); |
| 170 | + CREATE TABLE t6 (id int); |
| 171 | +
|
| 172 | + CREATE DATABASE dbex4; |
| 173 | + \c dbex4 |
| 174 | + CREATE TABLE t7 (id int); |
| 175 | + CREATE TABLE t8 (id int); |
| 176 | +
|
| 177 | + CREATE DATABASE db5; |
| 178 | + \c db5 |
| 179 | + CREATE TABLE t9 (id int); |
| 180 | + CREATE TABLE t10 (id int); |
| 181 | + ', |
| 182 | + dump_cmd => [ |
| 183 | + 'pg_dumpall', |
| 184 | + '--format' => 'directory', |
| 185 | + '--file' => "$tempdir/excluding_databases", |
| 186 | + '--exclude-database' => 'dbex*', |
| 187 | + ], |
| 188 | + restore_cmd => [ |
| 189 | + 'pg_restore', '-C', |
| 190 | + '--format' => 'directory', |
| 191 | + '--file' => "$tempdir/excluding_databases.sql", |
| 192 | + '--exclude-database' => 'db5', |
| 193 | + "$tempdir/excluding_databases", |
| 194 | + ], |
| 195 | + like => qr/^ |
| 196 | + \n\QCREATE DATABASE db1\E |
| 197 | + (.*\n)* |
| 198 | + \n\QCREATE TABLE public.t1 (\E |
| 199 | + (.*\n)* |
| 200 | + \n\QCREATE TABLE public.t2 (\E |
| 201 | + (.*\n)* |
| 202 | + \n\QCREATE DATABASE db2\E |
| 203 | + (.*\n)* |
| 204 | + \n\QCREATE TABLE public.t3 (\E |
| 205 | + (.*\n)* |
| 206 | + \n\QCREATE TABLE public.t4 (/xm, |
| 207 | + unlike => qr/^ |
| 208 | + \n\QCREATE DATABASE db3\E |
| 209 | + (.*\n)* |
| 210 | + \n\QCREATE TABLE public.t5 (\E |
| 211 | + (.*\n)* |
| 212 | + \n\QCREATE TABLE public.t6 (\E |
| 213 | + (.*\n)* |
| 214 | + \n\QCREATE DATABASE db4\E |
| 215 | + (.*\n)* |
| 216 | + \n\QCREATE TABLE public.t7 (\E |
| 217 | + (.*\n)* |
| 218 | + \n\QCREATE TABLE public.t8 (\E |
| 219 | + \n\QCREATE DATABASE db5\E |
| 220 | + (.*\n)* |
| 221 | + \n\QCREATE TABLE public.t9 (\E |
| 222 | + (.*\n)* |
| 223 | + \n\QCREATE TABLE public.t10 (\E |
| 224 | + /xm, |
| 225 | + }, |
| 226 | +
|
| 227 | + format_directory => { |
| 228 | + setup_sql => |
| 229 | + 'CREATE TABLE format_directory(a int, b boolean, c text);', |
| 230 | + dump_cmd => [ |
| 231 | + 'pg_dumpall', |
| 232 | + '--format' => 'directory', |
| 233 | + '--file' => "$tempdir/format_directory", |
| 234 | + ], |
| 235 | + restore_cmd => [ |
| 236 | + 'pg_restore', '-C', |
| 237 | + '--format' => 'directory', |
| 238 | + '--file' => "$tempdir/format_directory.sql", |
| 239 | + "$tempdir/format_directory", |
| 240 | + ], |
| 241 | + like => qr/^\n\QCREATE TABLE public.format_directory (/xm |
| 242 | + }, |
| 243 | +
|
| 244 | + format_tar => { |
| 245 | + setup_sql => 'CREATE TABLE format_tar(id int);', |
| 246 | + dump_cmd => [ |
| 247 | + 'pg_dumpall', |
| 248 | + '--format' => 'tar', |
| 249 | + '--file' => "$tempdir/format_tar", |
| 250 | + ], |
| 251 | + restore_cmd => [ |
| 252 | + 'pg_restore', '-C', |
| 253 | + '--format' => 'tar', |
| 254 | + '--file' => "$tempdir/format_tar.sql", |
| 255 | + "$tempdir/format_tar", |
| 256 | + ], |
| 257 | + like => qr/^\n\QCREATE TABLE public.format_tar (/xm |
| 258 | + }, |
| 259 | +
|
| 260 | + format_custom => { |
| 261 | + setup_sql => 'CREATE TABLE format_custom(a int, b boolean, c text);', |
| 262 | + dump_cmd => [ |
| 263 | + 'pg_dumpall', |
| 264 | + '--format' => 'custom', |
| 265 | + '--file' => "$tempdir/format_custom", |
| 266 | + ], |
| 267 | + restore_cmd => [ |
| 268 | + 'pg_restore', '-C', |
| 269 | + '--format' => 'custom', |
| 270 | + '--file' => "$tempdir/format_custom.sql", |
| 271 | + "$tempdir/format_custom", |
| 272 | + ], |
| 273 | + like => qr/^ \n\QCREATE TABLE public.format_custom (/xm |
| 274 | + },); |
| 275 | +
|
| 276 | +
|
| 277 | +# First execute the setup_sql |
| 278 | +foreach my $run (sort keys %pgdumpall_runs) |
| 279 | +{ |
| 280 | + if ($pgdumpall_runs{$run}->{setup_sql}) |
| 281 | + { |
| 282 | + $node->safe_psql($run_db, $pgdumpall_runs{$run}->{setup_sql}); |
| 283 | + } |
| 284 | +} |
| 285 | +
|
| 286 | +# Execute the tests |
| 287 | +foreach my $run (sort keys %pgdumpall_runs) |
| 288 | +{ |
| 289 | + # Create a new target cluster to pg_restore each test case run so that we |
| 290 | + # don't need to take care of the cleanup from the target cluster after each |
| 291 | + # run. |
| 292 | + my $target_node = PostgreSQL::Test::Cluster->new("target_$run"); |
| 293 | + $target_node->init; |
| 294 | + $target_node->start; |
| 295 | +
|
| 296 | + # Dumpall from node cluster. |
| 297 | + $node->command_ok(\@{ $pgdumpall_runs{$run}->{dump_cmd} }, |
| 298 | + "$run: pg_dumpall runs"); |
| 299 | +
|
| 300 | + # Restore the dump on "target_node" cluster. |
| 301 | + my @restore_cmd = ( |
| 302 | + @{ $pgdumpall_runs{$run}->{restore_cmd} }, |
| 303 | + '--host', $target_node->host, '--port', $target_node->port); |
| 304 | +
|
| 305 | + my ($stdout, $stderr) = run_command(\@restore_cmd); |
| 306 | +
|
| 307 | + # pg_restore --file output file. |
| 308 | + my $output_file = slurp_file("$tempdir/${run}.sql"); |
| 309 | +
|
| 310 | + if (!($pgdumpall_runs{$run}->{like}) && !($pgdumpall_runs{$run}->{unlike})) |
| 311 | + { |
| 312 | + die "missing \"like\" or \"unlike\" in test \"$run\""; |
| 313 | + } |
| 314 | +
|
| 315 | + if ($pgdumpall_runs{$run}->{like}) |
| 316 | + { |
| 317 | + like($output_file, $pgdumpall_runs{$run}->{like}, "should dump $run"); |
| 318 | + } |
| 319 | +
|
| 320 | + if ($pgdumpall_runs{$run}->{unlike}) |
| 321 | + { |
| 322 | + unlike( |
| 323 | + $output_file, |
| 324 | + $pgdumpall_runs{$run}->{unlike}, |
| 325 | + "should not dump $run"); |
| 326 | + } |
| 327 | +} |
| 328 | +
|
| 329 | +$node->stop('fast'); |
| 330 | +
|
| 331 | +done_testing(); |
0 commit comments