Skip to content

Commit 6d3dbf1

Browse files
authored
Merge pull request #870 from syasirs/bug-fix-pg_dump-cannot-duplicate-null-pointer
Bug fix pg dump cannot duplicate null pointer
2 parents 48d5abf + 24b1814 commit 6d3dbf1

File tree

4 files changed

+402
-14
lines changed

4 files changed

+402
-14
lines changed

src/Makefile.global.in

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ cd $(srcdir) && \
510510
TESTDIR='$(CURDIR)' PATH="$(bindir):$(CURDIR):$$PATH" \
511511
PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' \
512512
PG_REGRESS='$(CURDIR)/$(top_builddir)/src/oracle_test/regress/pg_regress' \
513-
$(PROVE) $(ORACLE_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
513+
$(PROVE) $(ORACLE_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl $(wildcard t/oracle/*.pl))
514514
endef
515515
#IvorySQL:END - SQL oracle_test
516516
else # PGXS case
@@ -537,7 +537,7 @@ cd $(srcdir) && \
537537
PATH="$(bindir):$(CURDIR):$$PATH" \
538538
PGPORT='6$(DEF_PGPORT)' \
539539
PG_REGRESS='$(top_builddir)/src/oracle_test/regress/pg_regress' \
540-
$(PROVE) $(ORACLE_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
540+
$(PROVE) $(ORACLE_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl $(wildcard t/oracle/*.pl))
541541
endef
542542
#IvorySQL:END - SQL oracle_test
543543
endif # PGXS
@@ -566,7 +566,7 @@ cd $(srcdir) && \
566566
$(with_temp_install) \
567567
PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' \
568568
PG_REGRESS='$(CURDIR)/$(top_builddir)/src/oracle_test/regress/pg_regress' \
569-
$(PROVE) $(ORACLE_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
569+
$(PROVE) $(ORACLE_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl $(wildcard t/oracle/*.pl))
570570
endef
571571
#IvorySQL:END - SQL oracle_test
572572

src/bin/pg_dump/pg_dump.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6920,7 +6920,7 @@ getPackages(Archive *fout, int *numPkgs)
69206920
i_pkgname = PQfnumber(res, "pkgname");
69216921
i_oid = PQfnumber(res, "oid");
69226922
i_pkgnamespace = PQfnumber(res, "pkgnamespace");
6923-
i_rolname = PQfnumber(res, "rolname");
6923+
i_rolname = PQfnumber(res, "pkgowner");
69246924
i_pkgacl = PQfnumber(res, "pkgacl");
69256925
i_acldefault = PQfnumber(res, "acldefault");
69266926

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
2+
# Copyright (c) 2021-2024, PostgreSQL Global Development Group
3+
# Portions Copyright (c) 2023-2025, IvorySQL Global Development Team
4+
5+
use strict;
6+
use warnings FATAL => 'all';
7+
8+
use PostgreSQL::Test::Cluster;
9+
use PostgreSQL::Test::Utils;
10+
use Test::More;
11+
12+
my $tempdir = PostgreSQL::Test::Utils::tempdir;
13+
14+
###############################################################
15+
# Definition of the pg_dump runs to make.
16+
#
17+
# Each of these runs are named and those names are used below
18+
# to define how each test should (or shouldn't) treat a result
19+
# from a given run.
20+
#
21+
# test_key indicates that a given run should simply use the same
22+
# set of like/unlike tests as another run, and which run that is.
23+
#
24+
# compile_option indicates if the commands run depend on a compilation
25+
# option, if any. This can be used to control if tests should be
26+
# skipped when a build dependency is not satisfied.
27+
#
28+
# dump_cmd is the pg_dump command to run, which is an array of
29+
# the full command and arguments to run. Note that this is run
30+
# using $node->command_ok(), so the port does not need to be
31+
# specified and is pulled from $PGPORT, which is set by the
32+
# PostgreSQL::Test::Cluster system.
33+
#
34+
# compress_cmd is the utility command for (de)compression, if any.
35+
# Note that this should generally be used on pg_dump's output
36+
# either to generate a text file to run the through the tests, or
37+
# to test pg_restore's ability to parse manually compressed files
38+
# that otherwise pg_dump does not compress on its own (e.g. *.toc).
39+
#
40+
# glob_patterns is an optional array consisting of strings compilable
41+
# with glob() to check the files generated after a dump.
42+
#
43+
# command_like is an optional utility that can be used to compare
44+
# the output generated by a command on the contents of an existing
45+
# dump, like the TOC description of a pg_restore command.
46+
#
47+
# restore_cmd is the pg_restore command to run, if any. Note
48+
# that this should generally be used when the pg_dump goes to
49+
# a non-text file and that the restore can then be used to
50+
# generate a text file to run through the tests from the
51+
# non-text file generated by pg_dump.
52+
#
53+
# TODO: Have pg_restore actually restore to an independent
54+
# database and then pg_dump *that* database (or something along
55+
# those lines) to validate that part of the process.
56+
57+
my %pgdump_runs = (
58+
packages => {
59+
dump_cmd => [
60+
'pg_dump', '--format=plain',
61+
"--file=$tempdir/packages.sql", '--no-owner','--schema-only',
62+
'testdb',
63+
],
64+
},
65+
66+
clean_if_exists => {
67+
dump_cmd => [
68+
'pg_dump',
69+
'--no-sync',
70+
"--file=$tempdir/clean_if_exists.sql",
71+
'-c',
72+
'--if-exists',
73+
'--encoding=UTF8', # no-op, just tests that option is accepted
74+
'testdb',
75+
],
76+
},
77+
);
78+
79+
###############################################################
80+
# Definition of the tests to run.
81+
#
82+
# Each test is defined using the log message that will be used.
83+
#
84+
# A regexp should be defined for each test which provides the
85+
# basis for the test. That regexp will be run against the output
86+
# file of each of the runs which the test is to be run against
87+
# and the success of the result will depend on if the regexp
88+
# result matches the expected 'like' or 'unlike' case.
89+
#
90+
# The runs listed as 'like' will be checked if they match the
91+
# regexp and, if so, the test passes. All runs which are not
92+
# listed as 'like' will be checked to ensure they don't match
93+
# the regexp; if they do, the test will fail.
94+
#
95+
# The below hashes provide convenience sets of runs. Individual
96+
# runs can be excluded from a general hash by placing that run
97+
# into the 'unlike' section.
98+
#
99+
# For example, there is an 'exclude_test_table' run which runs a
100+
# full pg_dump but with an exclude flag to not include the test
101+
# table. The CREATE TABLE test which creates the test table is
102+
# defined with %full_runs but then has 'exclude_test_table' in
103+
# its 'unlike' list, excluding that test.
104+
#
105+
# There can then be a 'create_sql' and 'create_order' for a
106+
# given test. The 'create_sql' commands are collected up in
107+
# 'create_order' and then run against the database prior to any
108+
# of the pg_dump runs happening. This is what "seeds" the
109+
# system with objects to be dumped out.
110+
#
111+
# There can be a flag called 'lz4', which can be set if the test
112+
# case depends on LZ4. Tests marked with this flag are skipped if
113+
# the build used does not support LZ4.
114+
#
115+
# Building of this hash takes a bit of time as all of the regexps
116+
# included in it are compiled. This greatly improves performance
117+
# as the regexps are used for each run the test applies to.
118+
119+
120+
# Tests which are considered 'full' dumps by pg_dump, but there
121+
# are flags used to exclude specific items (ACLs, LOs, etc).
122+
my %full_runs = (
123+
packages => 1,
124+
clean_if_exists => 1,);
125+
126+
# This is where the actual tests are defined.
127+
my %tests = (
128+
'CREATE PACKAGE test_pkg' => {
129+
create_order => 1,
130+
create_sql =>
131+
'CREATE OR REPLACE PACKAGE test_pkg AS
132+
FUNCTION hello() RETURN text;
133+
END test_pkg;',
134+
regexp => qr/^CREATE EDITIONABLE PACKAGE public.test_pkg AUTHID DEFINER AS/m,
135+
like => { %full_runs,},
136+
},
137+
138+
'CREATE PACKAGE BODY test_pkg' => {
139+
create_order => 2,
140+
create_sql =>
141+
'CREATE OR REPLACE PACKAGE BODY test_pkg AS
142+
FUNCTION hello() RETURN text IS
143+
BEGIN
144+
RETURN \'Hello from test package\';
145+
END;
146+
END test_pkg;',
147+
regexp => qr/^CREATE PACKAGE BODY public.test_pkg AS/m,
148+
like => { %full_runs, },
149+
},
150+
151+
'DROP PACKAGE IF EXISTS public.test_pkg;' => {
152+
regexp => qr/^DROP PACKAGE IF EXISTS public.test_pkg;/m,
153+
like => { clean_if_exists => 1, },
154+
},
155+
);
156+
157+
#########################################
158+
# Create a PG instance to test actually dumping from
159+
160+
my $node = PostgreSQL::Test::Cluster->new('main');
161+
$node->init;
162+
$node->start;
163+
164+
my ($connect_to_oraport);
165+
166+
# ICU doesn't work with some encodings
167+
my $encoding = $node->safe_psql('postgres', 'show server_encoding', connect_to_oraport => 1);
168+
169+
# Create additional databases for mutations of schema public
170+
$node->psql('postgres', 'create database testdb;', connect_to_oraport => 1);
171+
172+
#########################################
173+
# Set up schemas, tables, etc, to be dumped.
174+
175+
# Build up the create statements
176+
my %create_sql = ();
177+
178+
foreach my $test (
179+
sort {
180+
if ($tests{$a}->{create_order} and $tests{$b}->{create_order})
181+
{
182+
$tests{$a}->{create_order} <=> $tests{$b}->{create_order};
183+
}
184+
elsif ($tests{$a}->{create_order})
185+
{
186+
-1;
187+
}
188+
elsif ($tests{$b}->{create_order})
189+
{
190+
1;
191+
}
192+
else
193+
{
194+
0;
195+
}
196+
} keys %tests)
197+
{
198+
my $test_db = 'testdb';
199+
200+
if (defined($tests{$test}->{database}))
201+
{
202+
$test_db = $tests{$test}->{database};
203+
}
204+
205+
if ($tests{$test}->{create_sql})
206+
{
207+
# Normalize command ending: strip all line endings, add
208+
# semicolon if missing, add two newlines.
209+
my $create_sql = $tests{$test}->{create_sql};
210+
chomp $create_sql;
211+
$create_sql .= ';' unless substr($create_sql, -1) eq ';';
212+
$create_sql{$test_db} .= $create_sql . "\n\n";
213+
}
214+
}
215+
216+
# Send the combined set of commands to psql
217+
foreach my $db (sort keys %create_sql)
218+
{
219+
$node->safe_psql($db, $create_sql{$db}, connect_to_oraport => 1);
220+
}
221+
222+
#########################################
223+
# Run all runs
224+
225+
foreach my $run (sort keys %pgdump_runs)
226+
{
227+
my $test_key = $run;
228+
my $run_db = 'testdb';
229+
230+
231+
$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
232+
"$run: pg_dump runs");
233+
234+
if ($pgdump_runs{$run}->{compress_cmd})
235+
{
236+
my ($compress_cmd) = $pgdump_runs{$run}->{compress_cmd};
237+
my $compress_program = $compress_cmd->{program};
238+
239+
# Skip the rest of the test if the compression program is
240+
# not defined.
241+
next if (!defined($compress_program) || $compress_program eq '');
242+
243+
# Arguments may require globbing.
244+
my @full_compress_cmd = ($compress_program);
245+
foreach my $arg (@{ $compress_cmd->{args} })
246+
{
247+
push @full_compress_cmd, glob($arg);
248+
}
249+
250+
command_ok(\@full_compress_cmd, "$run: compression commands");
251+
}
252+
253+
if ($pgdump_runs{$run}->{glob_patterns})
254+
{
255+
my $glob_patterns = $pgdump_runs{$run}->{glob_patterns};
256+
foreach my $glob_pattern (@{$glob_patterns})
257+
{
258+
my @glob_output = glob($glob_pattern);
259+
is(scalar(@glob_output) > 0, 1, "$run: glob check for $glob_pattern");
260+
}
261+
}
262+
263+
if ($pgdump_runs{$run}->{command_like})
264+
{
265+
my $cmd_like = $pgdump_runs{$run}->{command_like};
266+
$node->command_like(\@{ $cmd_like->{command} },
267+
$cmd_like->{expected},
268+
"$run: " . $cmd_like->{name})
269+
}
270+
271+
if ($pgdump_runs{$run}->{restore_cmd})
272+
{
273+
$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
274+
"$run: pg_restore runs");
275+
}
276+
277+
if ($pgdump_runs{$run}->{test_key})
278+
{
279+
$test_key = $pgdump_runs{$run}->{test_key};
280+
}
281+
282+
my $output_file = slurp_file("$tempdir/${run}.sql");
283+
284+
#########################################
285+
# Run all tests where this run is included
286+
# as either a 'like' or 'unlike' test.
287+
288+
foreach my $test (sort keys %tests)
289+
{
290+
my $test_db = 'testdb';
291+
292+
if (defined($pgdump_runs{$run}->{database}))
293+
{
294+
$run_db = $pgdump_runs{$run}->{database};
295+
}
296+
297+
if (defined($tests{$test}->{database}))
298+
{
299+
$test_db = $tests{$test}->{database};
300+
}
301+
302+
# Check for proper test definitions
303+
#
304+
# There should be a "like" list, even if it is empty. (This
305+
# makes the test more self-documenting.)
306+
if (!defined($tests{$test}->{like}))
307+
{
308+
die "missing \"like\" in test \"$test\"";
309+
}
310+
# Check for useless entries in "unlike" list. Runs that are
311+
# not listed in "like" don't need to be excluded in "unlike".
312+
if ($tests{$test}->{unlike}->{$test_key}
313+
&& !defined($tests{$test}->{like}->{$test_key}))
314+
{
315+
die "useless \"unlike\" entry \"$test_key\" in test \"$test\"";
316+
}
317+
318+
if ($run_db ne $test_db)
319+
{
320+
next;
321+
}
322+
323+
# Run the test listed as a like, unless it is specifically noted
324+
# as an unlike (generally due to an explicit exclusion or similar).
325+
if ($tests{$test}->{like}->{$test_key}
326+
&& !defined($tests{$test}->{unlike}->{$test_key}))
327+
{
328+
if (!ok($output_file =~ $tests{$test}->{regexp},
329+
"$run: should dump $test"))
330+
{
331+
diag("Review $run results in $tempdir");
332+
}
333+
}
334+
else
335+
{
336+
if (!ok($output_file !~ $tests{$test}->{regexp},
337+
"$run: should not dump $test"))
338+
{
339+
diag("Review $run results in $tempdir");
340+
}
341+
}
342+
}
343+
}
344+
345+
#########################################
346+
# Stop the database instance, which will be removed at the end of the tests.
347+
348+
$node->stop('fast');
349+
350+
done_testing();

0 commit comments

Comments
 (0)