aboutsummaryrefslogtreecommitdiff
path: root/src/bin/pg_dump/t/006_pg_dumpall.pl
blob: 5acd49f1559d2cc9cad6df7d74d5d08a14b8fb5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# Copyright (c) 2021-2025, PostgreSQL Global Development Group

use strict;
use warnings FATAL => 'all';

use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

my $tempdir = PostgreSQL::Test::Utils::tempdir;
my $run_db = 'postgres';
my $sep = $windows_os ? "\\" : "/";

# Tablespace locations used by "restore_tablespace" test case.
my $tablespace1 = "${tempdir}${sep}tbl1";
my $tablespace2 = "${tempdir}${sep}tbl2";
mkdir($tablespace1) || die "mkdir $tablespace1 $!";
mkdir($tablespace2) || die "mkdir $tablespace2 $!";

# Scape tablespace locations on Windows.
$tablespace1 = $windows_os ? ($tablespace1 =~ s/\\/\\\\/gr) : $tablespace1;
$tablespace2 = $windows_os ? ($tablespace2 =~ s/\\/\\\\/gr) : $tablespace2;

# Where pg_dumpall will be executed.
my $node = PostgreSQL::Test::Cluster->new('node');
$node->init;
$node->start;


###############################################################
# Definition of the pg_dumpall test cases to run.
#
# Each of these test cases are named and those names are used for fail
# reporting and also to save the dump and restore information needed for the
# test to assert.
#
# The "setup_sql" is a psql valid script that contains SQL commands to execute
# before of actually execute the tests. The setups are all executed before of
# any test execution.
#
# The "dump_cmd" and "restore_cmd" are the commands that will be executed. The
# "restore_cmd" must have the --file flag to save the restore output so that we
# can assert on it.
#
# The "like" and "unlike" is a regexp that is used to match the pg_restore
# output. It must have at least one of then filled per test cases but it also
# can have both. See "excluding_databases" test case for example.
my %pgdumpall_runs = (
	restore_roles => {
		setup_sql => '
		CREATE ROLE dumpall WITH ENCRYPTED PASSWORD \'admin\' SUPERUSER;
		CREATE ROLE dumpall2 WITH REPLICATION CONNECTION LIMIT 10;',
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'directory',
			'--file' => "$tempdir/restore_roles",
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'directory',
			'--file' => "$tempdir/restore_roles.sql",
			"$tempdir/restore_roles",
		],
		like => qr/
			^\s*\QCREATE ROLE dumpall;\E\s*\n
			\s*\QALTER ROLE dumpall WITH SUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256\E
			[^']+';\s*\n
			\s*\QCREATE ROLE dumpall2;\E
			\s*\QALTER ROLE dumpall2 WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN REPLICATION NOBYPASSRLS CONNECTION LIMIT 10;\E
		/xm
	},

	restore_tablespace => {
		setup_sql => "
		CREATE ROLE tap;
		CREATE TABLESPACE tbl1 OWNER tap LOCATION '$tablespace1';
		CREATE TABLESPACE tbl2 OWNER tap LOCATION '$tablespace2' WITH (seq_page_cost=1.0);",
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'directory',
			'--file' => "$tempdir/restore_tablespace",
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'directory',
			'--file' => "$tempdir/restore_tablespace.sql",
			"$tempdir/restore_tablespace",
		],
		# Match "E" as optional since it is added on LOCATION when running on
		# Windows.
		like => qr/^
			\n\QCREATE TABLESPACE tbl1 OWNER tap LOCATION \E(?:E)?\Q'$tablespace1';\E
			\n\QCREATE TABLESPACE tbl2 OWNER tap LOCATION \E(?:E)?\Q'$tablespace2';\E
			\n\QALTER TABLESPACE tbl2 SET (seq_page_cost=1.0);\E
		/xm,
	},

	restore_grants => {
		setup_sql => "
		CREATE DATABASE tapgrantsdb;
		CREATE SCHEMA private;
		CREATE SEQUENCE serial START 101;
		CREATE FUNCTION fn() RETURNS void AS \$\$
		BEGIN
		END;
		\$\$ LANGUAGE plpgsql;
		CREATE ROLE super;
		CREATE ROLE grant1;
		CREATE ROLE grant2;
		CREATE ROLE grant3;
		CREATE ROLE grant4;
		CREATE ROLE grant5;
		CREATE ROLE grant6;
		CREATE ROLE grant7;
		CREATE ROLE grant8;

		CREATE TABLE t (id int);
		INSERT INTO t VALUES (1), (2), (3), (4);

		GRANT SELECT ON TABLE t TO grant1;
		GRANT INSERT ON TABLE t TO grant2;
		GRANT ALL PRIVILEGES ON TABLE t to grant3;
		GRANT CONNECT, CREATE ON DATABASE tapgrantsdb TO grant4;
		GRANT USAGE, CREATE ON SCHEMA private TO grant5;
		GRANT USAGE, SELECT, UPDATE ON SEQUENCE serial TO grant6;
		GRANT super TO grant7;
		GRANT EXECUTE ON FUNCTION fn() TO grant8;
		",
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'directory',
			'--file' => "$tempdir/restore_grants",
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'directory',
			'--file' => "$tempdir/restore_grants.sql",
			"$tempdir/restore_grants",
		],
		like => qr/^
			\n\QGRANT super TO grant7 WITH INHERIT TRUE GRANTED BY\E
			(.*\n)*
			\n\QGRANT ALL ON SCHEMA private TO grant5;\E
			(.*\n)*
			\n\QGRANT ALL ON FUNCTION public.fn() TO grant8;\E
			(.*\n)*
			\n\QGRANT ALL ON SEQUENCE public.serial TO grant6;\E
			(.*\n)*
			\n\QGRANT SELECT ON TABLE public.t TO grant1;\E
			\n\QGRANT INSERT ON TABLE public.t TO grant2;\E
			\n\QGRANT ALL ON TABLE public.t TO grant3;\E
			(.*\n)*
			\n\QGRANT CREATE,CONNECT ON DATABASE tapgrantsdb TO grant4;\E
		/xm,
	},

	excluding_databases => {
		setup_sql => 'CREATE DATABASE db1;
		\c db1
		CREATE TABLE t1 (id int);
		INSERT INTO t1 VALUES (1), (2), (3), (4);
		CREATE TABLE t2 (id int);
		INSERT INTO t2 VALUES (1), (2), (3), (4);

		CREATE DATABASE db2;
		\c db2
		CREATE TABLE t3 (id int);
		INSERT INTO t3 VALUES (1), (2), (3), (4);
		CREATE TABLE t4 (id int);
		INSERT INTO t4 VALUES (1), (2), (3), (4);

		CREATE DATABASE dbex3;
		\c dbex3
		CREATE TABLE t5 (id int);
		INSERT INTO t5 VALUES (1), (2), (3), (4);
		CREATE TABLE t6 (id int);
		INSERT INTO t6 VALUES (1), (2), (3), (4);

		CREATE DATABASE dbex4;
		\c dbex4
		CREATE TABLE t7 (id int);
		INSERT INTO t7 VALUES (1), (2), (3), (4);
		CREATE TABLE t8 (id int);
		INSERT INTO t8 VALUES (1), (2), (3), (4);

		CREATE DATABASE db5;
		\c db5
		CREATE TABLE t9 (id int);
		INSERT INTO t9 VALUES (1), (2), (3), (4);
		CREATE TABLE t10 (id int);
		INSERT INTO t10 VALUES (1), (2), (3), (4);
		',
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'directory',
			'--file' => "$tempdir/excluding_databases",
			'--exclude-database' => 'dbex*',
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'directory',
			'--file' => "$tempdir/excluding_databases.sql",
			'--exclude-database' => 'db5',
			"$tempdir/excluding_databases",
		],
		like => qr/^
			\n\QCREATE DATABASE db1\E
			(.*\n)*
			\n\QCREATE TABLE public.t1 (\E
			(.*\n)*
			\n\QCREATE TABLE public.t2 (\E
			(.*\n)*
			\n\QCREATE DATABASE db2\E
			(.*\n)*
			\n\QCREATE TABLE public.t3 (\E
			(.*\n)*
			\n\QCREATE TABLE public.t4 (/xm,
		unlike => qr/^
			\n\QCREATE DATABASE db3\E
			(.*\n)*
			\n\QCREATE TABLE public.t5 (\E
			(.*\n)*
			\n\QCREATE TABLE public.t6 (\E
			(.*\n)*
			\n\QCREATE DATABASE db4\E
			(.*\n)*
			\n\QCREATE TABLE public.t7 (\E
			(.*\n)*
			\n\QCREATE TABLE public.t8 (\E
			\n\QCREATE DATABASE db5\E
			(.*\n)*
			\n\QCREATE TABLE public.t9 (\E
			(.*\n)*
			\n\QCREATE TABLE public.t10 (\E
		/xm,
	},

	format_directory => {
		setup_sql => "CREATE TABLE format_directory(a int, b boolean, c text);
		INSERT INTO format_directory VALUES (1, true, 'name1'), (2, false, 'name2');",
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'directory',
			'--file' => "$tempdir/format_directory",
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'directory',
			'--file' => "$tempdir/format_directory.sql",
			"$tempdir/format_directory",
		],
		like => qr/^\n\QCOPY public.format_directory (a, b, c) FROM stdin;/xm
	},

	format_tar => {
		setup_sql => "CREATE TABLE format_tar(a int, b boolean, c text);
		INSERT INTO format_tar VALUES (1, false, 'name3'), (2, true, 'name4');",
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'tar',
			'--file' => "$tempdir/format_tar",
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'tar',
			'--file' => "$tempdir/format_tar.sql",
			"$tempdir/format_tar",
		],
		like => qr/^\n\QCOPY public.format_tar (a, b, c) FROM stdin;/xm
	},

	format_custom => {
		setup_sql => "CREATE TABLE format_custom(a int, b boolean, c text);
		INSERT INTO format_custom VALUES (1, false, 'name5'), (2, true, 'name6');",
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'custom',
			'--file' => "$tempdir/format_custom",
		],
		restore_cmd => [
			'pg_restore', '-C',
			'--format' => 'custom',
			'--file' => "$tempdir/format_custom.sql",
			"$tempdir/format_custom",
		],
		like => qr/^ \n\QCOPY public.format_custom (a, b, c) FROM stdin;/xm
	},

	dump_globals_only => {
		setup_sql => "CREATE TABLE format_dir(a int, b boolean, c text);
		INSERT INTO format_dir VALUES (1, false, 'name5'), (2, true, 'name6');",
		dump_cmd => [
			'pg_dumpall',
			'--format' => 'directory',
			'--globals-only',
			'--file' => "$tempdir/dump_globals_only",
			],
			restore_cmd => [
				'pg_restore', '-C', '--globals-only',
				'--format' => 'directory',
				'--file' => "$tempdir/dump_globals_only.sql",
				"$tempdir/dump_globals_only",
				],
				like => qr/
            ^\s*\QCREATE ROLE dumpall;\E\s*\n
			/xm
			}, );

# First execute the setup_sql
foreach my $run (sort keys %pgdumpall_runs)
{
	if ($pgdumpall_runs{$run}->{setup_sql})
	{
		$node->safe_psql($run_db, $pgdumpall_runs{$run}->{setup_sql});
	}
}

# Execute the tests
foreach my $run (sort keys %pgdumpall_runs)
{
	# Create a new target cluster to pg_restore each test case run so that we
	# don't need to take care of the cleanup from the target cluster after each
	# run.
	my $target_node = PostgreSQL::Test::Cluster->new("target_$run");
	$target_node->init;
	$target_node->start;

	# Dumpall from node cluster.
	$node->command_ok(\@{ $pgdumpall_runs{$run}->{dump_cmd} },
		"$run: pg_dumpall runs");

	# Restore the dump on "target_node" cluster.
	my @restore_cmd = (
		@{ $pgdumpall_runs{$run}->{restore_cmd} },
		'--host', $target_node->host, '--port', $target_node->port);

	my ($stdout, $stderr) = run_command(\@restore_cmd);

	# pg_restore --file output file.
	my $output_file = slurp_file("$tempdir/${run}.sql");

	if (!($pgdumpall_runs{$run}->{like}) && !($pgdumpall_runs{$run}->{unlike}))
	{
		die "missing \"like\" or \"unlike\" in test \"$run\"";
	}

	if ($pgdumpall_runs{$run}->{like})
	{
		like($output_file, $pgdumpall_runs{$run}->{like}, "should dump $run");
	}

	if ($pgdumpall_runs{$run}->{unlike})
	{
		unlike(
			$output_file,
			$pgdumpall_runs{$run}->{unlike},
			"should not dump $run");
	}
}

# Some negative test case with dump of pg_dumpall and restore using pg_restore
# test case 1: when -C is not used in pg_restore with dump of pg_dumpall
$node->command_fails_like(
    [ 'pg_restore',
    "$tempdir/format_custom",
    '--format' => 'custom',
    '--file' => "$tempdir/error_test.sql", ],
    qr/\Qpg_restore: error: -C\/--create option should be specified when restoring an archive created by pg_dumpall\E/,
    'When -C is not used in pg_restore with dump of pg_dumpall');

# test case 2: When --list option is used with dump of pg_dumpall
$node->command_fails_like(
	[ 'pg_restore',
		"$tempdir/format_custom", '-C',
		'--format' => 'custom', '--list',
		'--file' => "$tempdir/error_test.sql", ],
	qr/\Qpg_restore: error: option -l\/--list cannot be used when restoring an archive created by pg_dumpall\E/,
	'When --list is used in pg_restore with dump of pg_dumpall');

# test case 3: When non-exist database is given with -d option
$node->command_fails_like(
	[ 'pg_restore',
		"$tempdir/format_custom", '-C',
		'--format' => 'custom',
		'-d' => 'dbpq', ],
	qr/\Qpg_restore: error: could not connect to database "dbpq"\E/,
	'When non-existent database is given with -d option in pg_restore with dump of pg_dumpall');

$node->stop('fast');

done_testing();