Skip to content

Commit

Permalink
Add support for --extension in pg_dump
Browse files Browse the repository at this point in the history
When specified, only extensions matching the given pattern are included
in dumps.  Similarly to --table and --schema, when --strict-names is
used,  a perfect match is required.  Also, like the two other options,
this new option offers no guarantee that dependent objects have been
dumped, so a restore may fail on a clean database.

Tests are added in test_pg_dump/, checking after a set of positive and
negative cases, with or without an extension's contents added to the
dump generated.

Author: Guillaume Lelarge
Reviewed-by: David Fetter, Tom Lane, Michael Paquier, Asif Rehman,
Julien Rouhaud
Discussion: https://postgr.es/m/CAECtzeXOt4cnMU5+XMZzxBPJ_wu76pNy6HZKPRBL-j7yj1E4+g@mail.gmail.com
  • Loading branch information
michaelpq committed Mar 31, 2021
1 parent 65158f4 commit 6568cef
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 28 deletions.
43 changes: 38 additions & 5 deletions doc/src/sgml/ref/pg_dump.sgml
Expand Up @@ -215,6 +215,38 @@ PostgreSQL documentation
</listitem>
</varlistentry>

<varlistentry>
<term><option>-e <replaceable class="parameter">pattern</replaceable></option></term>
<term><option>--extension=<replaceable class="parameter">pattern</replaceable></option></term>
<listitem>
<para>
Dump only extensions matching <replaceable
class="parameter">pattern</replaceable>. When this option is not
specified, all non-system extensions in the target database will be
dumped. Multiple extensions can be selected by writing multiple
<option>-e</option> switches. The <replaceable
class="parameter">pattern</replaceable> parameter is interpreted as a
pattern according to the same rules used by
<application>psql</application>'s <literal>\d</literal> commands (see
<xref linkend="app-psql-patterns"/>), so multiple extensions can also
be selected by writing wildcard characters in the pattern. When using
wildcards, be careful to quote the pattern if needed to prevent the
shell from expanding the wildcards.
</para>

<note>
<para>
When <option>-e</option> is specified,
<application>pg_dump</application> makes no attempt to dump any other
database objects that the selected extension(s) might depend upon.
Therefore, there is no guarantee that the results of a
specific-extension dump can be successfully restored by themselves
into a clean database.
</para>
</note>
</listitem>
</varlistentry>

<varlistentry>
<term><option>-E <replaceable class="parameter">encoding</replaceable></option></term>
<term><option>--encoding=<replaceable class="parameter">encoding</replaceable></option></term>
Expand Down Expand Up @@ -1079,11 +1111,12 @@ PostgreSQL documentation
<term><option>--strict-names</option></term>
<listitem>
<para>
Require that each schema
(<option>-n</option>/<option>--schema</option>) and table
(<option>-t</option>/<option>--table</option>) qualifier match at
least one schema/table in the database to be dumped. Note that if
none of the schema/table qualifiers find
Require that each
extension (<option>-e</option>/<option>--extension</option>),
schema (<option>-n</option>/<option>--schema</option>) and
table (<option>-t</option>/<option>--table</option>) qualifier
match at least one extension/schema/table in the database to be dumped.
Note that if none of the extension/schema/table qualifiers find
matches, <application>pg_dump</application> will generate an error
even without <option>--strict-names</option>.
</para>
Expand Down
93 changes: 87 additions & 6 deletions src/bin/pg_dump/pg_dump.c
Expand Up @@ -123,6 +123,9 @@ static SimpleOidList tabledata_exclude_oids = {NULL, NULL};
static SimpleStringList foreign_servers_include_patterns = {NULL, NULL};
static SimpleOidList foreign_servers_include_oids = {NULL, NULL};

static SimpleStringList extension_include_patterns = {NULL, NULL};
static SimpleOidList extension_include_oids = {NULL, NULL};

static const CatalogId nilCatalogId = {0, 0};

/* override for standard extra_float_digits setting */
Expand Down Expand Up @@ -151,6 +154,10 @@ static void expand_schema_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
static void expand_extension_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
static void expand_foreign_server_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids);
Expand Down Expand Up @@ -335,6 +342,7 @@ main(int argc, char **argv)
{"clean", no_argument, NULL, 'c'},
{"create", no_argument, NULL, 'C'},
{"dbname", required_argument, NULL, 'd'},
{"extension", required_argument, NULL, 'e'},
{"file", required_argument, NULL, 'f'},
{"format", required_argument, NULL, 'F'},
{"host", required_argument, NULL, 'h'},
Expand Down Expand Up @@ -426,7 +434,7 @@ main(int argc, char **argv)

InitDumpOptions(&dopt);

while ((c = getopt_long(argc, argv, "abBcCd:E:f:F:h:j:n:N:Op:RsS:t:T:U:vwWxZ:",
while ((c = getopt_long(argc, argv, "abBcCd:e:E:f:F:h:j:n:N:Op:RsS:t:T:U:vwWxZ:",
long_options, &optindex)) != -1)
{
switch (c)
Expand Down Expand Up @@ -455,6 +463,11 @@ main(int argc, char **argv)
dopt.cparams.dbname = pg_strdup(optarg);
break;

case 'e': /* include extension(s) */
simple_string_list_append(&extension_include_patterns, optarg);
dopt.include_everything = false;
break;

case 'E': /* Dump encoding */
dumpencoding = pg_strdup(optarg);
break;
Expand Down Expand Up @@ -834,6 +847,16 @@ main(int argc, char **argv)

/* non-matching exclusion patterns aren't an error */

/* Expand extension selection patterns into OID lists */
if (extension_include_patterns.head != NULL)
{
expand_extension_name_patterns(fout, &extension_include_patterns,
&extension_include_oids,
strict_names);
if (extension_include_oids.head == NULL)
fatal("no matching extensions were found");
}

/*
* Dumping blobs is the default for dumps where an inclusion switch is not
* used (an "include everything" dump). -B can be used to exclude blobs
Expand Down Expand Up @@ -1025,6 +1048,7 @@ help(const char *progname)
printf(_(" -B, --no-blobs exclude large objects in dump\n"));
printf(_(" -c, --clean clean (drop) database objects before recreating\n"));
printf(_(" -C, --create include commands to create database in dump\n"));
printf(_(" -e, --extension=PATTERN dump the specified extension(s) only\n"));
printf(_(" -E, --encoding=ENCODING dump the data in encoding ENCODING\n"));
printf(_(" -n, --schema=PATTERN dump the specified schema(s) only\n"));
printf(_(" -N, --exclude-schema=PATTERN do NOT dump the specified schema(s)\n"));
Expand Down Expand Up @@ -1367,6 +1391,53 @@ expand_schema_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}

/*
* Find the OIDs of all extensions matching the given list of patterns,
* and append them to the given OID list.
*/
static void
expand_extension_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names)
{
PQExpBuffer query;
PGresult *res;
SimpleStringListCell *cell;
int i;

if (patterns->head == NULL)
return; /* nothing to do */

query = createPQExpBuffer();

/*
* The loop below runs multiple SELECTs might sometimes result in
* duplicate entries in the OID list, but we don't care.
*/
for (cell = patterns->head; cell; cell = cell->next)
{
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
false, NULL, "e.extname", NULL, NULL);

res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
fatal("no matching extensions were found for pattern \"%s\"", cell->val);

for (i = 0; i < PQntuples(res); i++)
{
simple_oid_list_append(oids, atooid(PQgetvalue(res, i, 0)));
}

PQclear(res);
resetPQExpBuffer(query);
}

destroyPQExpBuffer(query);
}

/*
* Find the OIDs of all foreign servers matching the given list of patterns,
* and append them to the given OID list.
Expand Down Expand Up @@ -1793,8 +1864,9 @@ selectDumpableAccessMethod(AccessMethodInfo *method, Archive *fout)
* Built-in extensions should be skipped except for checking ACLs, since we
* assume those will already be installed in the target database. We identify
* such extensions by their having OIDs in the range reserved for initdb.
* We dump all user-added extensions by default, or none of them if
* include_everything is false (i.e., a --schema or --table switch was given).
* We dump all user-added extensions by default. No extensions are dumped
* if include_everything is false (i.e., a --schema or --table switch was
* given), except if --extension specifies a list of extensions to dump.
*/
static void
selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
Expand All @@ -1807,9 +1879,18 @@ selectDumpableExtension(ExtensionInfo *extinfo, DumpOptions *dopt)
if (extinfo->dobj.catId.oid <= (Oid) g_last_builtin_oid)
extinfo->dobj.dump = extinfo->dobj.dump_contains = DUMP_COMPONENT_ACL;
else
extinfo->dobj.dump = extinfo->dobj.dump_contains =
dopt->include_everything ? DUMP_COMPONENT_ALL :
DUMP_COMPONENT_NONE;
{
/* check if there is a list of extensions to dump */
if (extension_include_oids.head != NULL)
extinfo->dobj.dump = extinfo->dobj.dump_contains =
simple_oid_list_member(&extension_include_oids,
extinfo->dobj.catId.oid) ?
DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE;
else
extinfo->dobj.dump = extinfo->dobj.dump_contains =
dopt->include_everything ?
DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE;
}
}

/*
Expand Down
53 changes: 36 additions & 17 deletions src/test/modules/test_pg_dump/t/001_base.pl
Expand Up @@ -194,6 +194,20 @@
'pg_dump', '--no-sync', "--file=$tempdir/section_post_data.sql",
'--section=post-data', 'postgres',
],
},
with_extension => {
dump_cmd => [
'pg_dump', '--no-sync', "--file=$tempdir/with_extension.sql",
'--extension=test_pg_dump', '--no-sync', 'postgres',
],
},

# plgsql in the list blocks the dump of extension test_pg_dump
without_extension => {
dump_cmd => [
'pg_dump', '--no-sync', "--file=$tempdir/without_extension.sql",
'--extension=plpgsql', '--no-sync', 'postgres',
],
},);

###############################################################
Expand Down Expand Up @@ -228,14 +242,16 @@
# Tests which are considered 'full' dumps by pg_dump, but there
# are flags used to exclude specific items (ACLs, blobs, etc).
my %full_runs = (
binary_upgrade => 1,
clean => 1,
clean_if_exists => 1,
createdb => 1,
defaults => 1,
exclude_table => 1,
no_privs => 1,
no_owner => 1,);
binary_upgrade => 1,
clean => 1,
clean_if_exists => 1,
createdb => 1,
defaults => 1,
exclude_table => 1,
no_privs => 1,
no_owner => 1,
with_extension => 1,
without_extension => 1);

my %tests = (
'ALTER EXTENSION test_pg_dump' => {
Expand All @@ -261,7 +277,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { binary_upgrade => 1, },
unlike => { binary_upgrade => 1, without_extension => 1 },
},
'CREATE ROLE regress_dump_test_role' => {
Expand Down Expand Up @@ -320,6 +336,7 @@
section_data => 1,
extension_schema => 1,
},
unlike => { without_extension => 1, },
},
'CREATE TABLE regress_pg_dump_table' => {
Expand All @@ -343,8 +360,9 @@
extension_schema => 1,
},
unlike => {
binary_upgrade => 1,
exclude_table => 1,
binary_upgrade => 1,
exclude_table => 1,
without_extension => 1,
},
},
Expand All @@ -367,7 +385,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { no_privs => 1, },
unlike => { no_privs => 1, without_extension => 1, },
},
'REVOKE GRANT OPTION FOR UPDATE ON SEQUENCE wgo_then_regular' => {
Expand All @@ -384,7 +402,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { no_privs => 1, },
unlike => { no_privs => 1, without_extension => 1, },
},
'CREATE ACCESS METHOD regress_test_am' => {
Expand All @@ -404,6 +422,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { without_extension => 1, },
},
'GRANT SELECT regress_pg_dump_table_added pre-ALTER EXTENSION' => {
Expand All @@ -428,7 +447,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { no_privs => 1, },
unlike => { no_privs => 1, without_extension => 1, },
},
'GRANT SELECT ON TABLE regress_pg_dump_table' => {
Expand Down Expand Up @@ -462,7 +481,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { no_privs => 1, },
unlike => { no_privs => 1, without_extension => 1 },
},
'GRANT USAGE ON regress_pg_dump_table_col1_seq TO regress_dump_test_role'
Expand All @@ -478,7 +497,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { no_privs => 1, },
unlike => { no_privs => 1, without_extension => 1, },
},
'GRANT USAGE ON regress_pg_dump_seq TO regress_dump_test_role' => {
Expand All @@ -500,7 +519,7 @@
schema_only => 1,
section_pre_data => 1,
},
unlike => { no_privs => 1, },
unlike => { no_privs => 1, without_extension => 1, },
},
# Objects included in extension part of a schema created by this extension */
Expand Down

0 comments on commit 6568cef

Please sign in to comment.