mirror of
https://github.com/postgres/postgres.git
synced 2026-04-15 22:10:45 -04:00
Add non-text output formats to pg_dumpall
pg_dumpall can now produce output in custom, directory, or tar formats in addition to plain text SQL scripts. When using non-text formats, pg_dumpall creates a directory containing: - toc.glo: global data (roles and tablespaces) in custom format - map.dat: mapping between database OIDs and names - databases/: subdirectory with per-database archives named by OID pg_restore is extended to handle these pg_dumpall archives, restoring globals and then each database. The --globals-only option can be used to restore only the global objects. This enables parallel restore of pg_dumpall output and selective restoration of individual databases from a cluster-wide backup. Author: Mahendra Singh Thalor <mahi6run@gmail.com> Co-Author: Andrew Dunstan <andrew@dunslane.net> Reviewed-By: Tushar Ahuja <tushar.ahuja@enterprisedb.com> Reviewed-By: Jian He <jian.universality@gmail.com> Reviewed-By: Vaibhav Dalvi <vaibhav.dalvi@enterprisedb.com> Reviewed-By: Srinath Reddy <srinath2133@gmail.com> Discussion: https://postgr.es/m/cb103623-8ee6-4ba5-a2c9-f32e3a4933fa@dunslane.net
This commit is contained in:
parent
7bb50dd7d6
commit
763aaa06f0
14 changed files with 2357 additions and 169 deletions
|
|
@ -16,7 +16,10 @@ PostgreSQL documentation
|
||||||
|
|
||||||
<refnamediv>
|
<refnamediv>
|
||||||
<refname>pg_dumpall</refname>
|
<refname>pg_dumpall</refname>
|
||||||
<refpurpose>extract a <productname>PostgreSQL</productname> database cluster into a script file</refpurpose>
|
|
||||||
|
<refpurpose>
|
||||||
|
export a <productname>PostgreSQL</productname> database cluster as an SQL script or to other formats
|
||||||
|
</refpurpose>
|
||||||
</refnamediv>
|
</refnamediv>
|
||||||
|
|
||||||
<refsynopsisdiv>
|
<refsynopsisdiv>
|
||||||
|
|
@ -33,7 +36,7 @@ PostgreSQL documentation
|
||||||
<para>
|
<para>
|
||||||
<application>pg_dumpall</application> is a utility for writing out
|
<application>pg_dumpall</application> is a utility for writing out
|
||||||
(<quote>dumping</quote>) all <productname>PostgreSQL</productname> databases
|
(<quote>dumping</quote>) all <productname>PostgreSQL</productname> databases
|
||||||
of a cluster into one script file. The script file contains
|
of a cluster into an SQL script file or an archive. The output contains
|
||||||
<acronym>SQL</acronym> commands that can be used as input to <xref
|
<acronym>SQL</acronym> commands that can be used as input to <xref
|
||||||
linkend="app-psql"/> to restore the databases. It does this by
|
linkend="app-psql"/> to restore the databases. It does this by
|
||||||
calling <xref linkend="app-pgdump"/> for each database in the cluster.
|
calling <xref linkend="app-pgdump"/> for each database in the cluster.
|
||||||
|
|
@ -52,11 +55,16 @@ PostgreSQL documentation
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The SQL script will be written to the standard output. Use the
|
Plain text SQL scripts will be written to the standard output. Use the
|
||||||
<option>-f</option>/<option>--file</option> option or shell operators to
|
<option>-f</option>/<option>--file</option> option or shell operators to
|
||||||
redirect it into a file.
|
redirect it into a file.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Archives in other formats will be placed in a directory named using the
|
||||||
|
<option>-f</option>/<option>--file</option>, which is required in this case.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<application>pg_dumpall</application> needs to connect several
|
<application>pg_dumpall</application> needs to connect several
|
||||||
times to the <productname>PostgreSQL</productname> server (once per
|
times to the <productname>PostgreSQL</productname> server (once per
|
||||||
|
|
@ -131,16 +139,93 @@ PostgreSQL documentation
|
||||||
<para>
|
<para>
|
||||||
Send output to the specified file. If this is omitted, the
|
Send output to the specified file. If this is omitted, the
|
||||||
standard output is used.
|
standard output is used.
|
||||||
|
This option can only be omitted when <option>--format</option> is plain.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>-F <replaceable class="parameter">format</replaceable></option></term>
|
||||||
|
<term><option>--format=<replaceable class="parameter">format</replaceable></option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Specify the format of dump files. In plain format, all the dump data is
|
||||||
|
sent in a single text stream. This is the default.
|
||||||
|
|
||||||
|
In all other modes, <application>pg_dumpall</application> first creates two files,
|
||||||
|
<filename>toc.glo</filename> and <filename>map.dat</filename>, in the directory
|
||||||
|
specified by <option>--file</option>.
|
||||||
|
The first file contains global data (roles and tablespaces) in custom format. The second
|
||||||
|
contains a mapping between database OIDs and names. These files are used by
|
||||||
|
<application>pg_restore</application>. Data for individual databases is placed in
|
||||||
|
the <filename>databases</filename> subdirectory, named using the database's OID.
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>d</literal></term>
|
||||||
|
<term><literal>directory</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Output directory-format archives for each database,
|
||||||
|
suitable for input into pg_restore. The directory
|
||||||
|
will have database <type>oid</type> as its name.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>p</literal></term>
|
||||||
|
<term><literal>plain</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Output a plain-text SQL script file (the default).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>c</literal></term>
|
||||||
|
<term><literal>custom</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Output a custom-format archive for each database,
|
||||||
|
suitable for input into pg_restore. The archive
|
||||||
|
will be named <filename>dboid.dmp</filename> where <type>dboid</type> is the
|
||||||
|
<type>oid</type> of the database.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>t</literal></term>
|
||||||
|
<term><literal>tar</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Output a tar-format archive for each database,
|
||||||
|
suitable for input into pg_restore. The archive
|
||||||
|
will be named <filename>dboid.tar</filename> where <type>dboid</type> is the
|
||||||
|
<type>oid</type> of the database.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
See <xref linkend="app-pgdump"/> for details on how the
|
||||||
|
various non-plain-text archive formats work.
|
||||||
|
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>-g</option></term>
|
<term><option>-g</option></term>
|
||||||
<term><option>--globals-only</option></term>
|
<term><option>--globals-only</option></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Dump only global objects (roles and tablespaces), no databases.
|
Dump only global objects (roles and tablespaces), no databases.
|
||||||
|
Note: <option>--globals-only</option> cannot be used with
|
||||||
|
<option>--clean</option> with non-text dump format.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
@ -936,13 +1021,21 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
|
||||||
<refsect1 id="app-pg-dumpall-ex">
|
<refsect1 id="app-pg-dumpall-ex">
|
||||||
<title>Examples</title>
|
<title>Examples</title>
|
||||||
<para>
|
<para>
|
||||||
To dump all databases:
|
To dump all databases in plain text format (the default):
|
||||||
|
|
||||||
<screen>
|
<screen>
|
||||||
<prompt>$</prompt> <userinput>pg_dumpall > db.out</userinput>
|
<prompt>$</prompt> <userinput>pg_dumpall > db.out</userinput>
|
||||||
</screen>
|
</screen>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
To dump all databases using other formats:
|
||||||
|
<screen>
|
||||||
|
<prompt>$</prompt> <userinput>pg_dumpall --format=directory -f db.out</userinput>
|
||||||
|
<prompt>$</prompt> <userinput>pg_dumpall --format=custom -f db.out</userinput>
|
||||||
|
<prompt>$</prompt> <userinput>pg_dumpall --format=tar -f db.out</userinput>
|
||||||
|
</screen>
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
To restore database(s) from this file, you can use:
|
To restore database(s) from this file, you can use:
|
||||||
<screen>
|
<screen>
|
||||||
|
|
@ -956,6 +1049,16 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
|
||||||
the script will attempt to drop other databases immediately, and that
|
the script will attempt to drop other databases immediately, and that
|
||||||
will fail for the database you are connected to.
|
will fail for the database you are connected to.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If the dump was taken in a non-plain-text format, use
|
||||||
|
<application>pg_restore</application> to restore the databases:
|
||||||
|
<screen>
|
||||||
|
<prompt>$</prompt> <userinput>pg_restore db.out -d postgres -C</userinput>
|
||||||
|
</screen>
|
||||||
|
This will restore all databases. To restore only some databases, use
|
||||||
|
the <option>--exclude-database</option> option to skip those not wanted.
|
||||||
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,9 @@ PostgreSQL documentation
|
||||||
<refname>pg_restore</refname>
|
<refname>pg_restore</refname>
|
||||||
|
|
||||||
<refpurpose>
|
<refpurpose>
|
||||||
restore a <productname>PostgreSQL</productname> database from an
|
restore <productname>PostgreSQL</productname> databases from archives
|
||||||
archive file created by <application>pg_dump</application>
|
created by <application>pg_dump</application> or
|
||||||
|
<application>pg_dumpall</application>
|
||||||
</refpurpose>
|
</refpurpose>
|
||||||
</refnamediv>
|
</refnamediv>
|
||||||
|
|
||||||
|
|
@ -38,13 +39,14 @@ PostgreSQL documentation
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<application>pg_restore</application> is a utility for restoring a
|
<application>pg_restore</application> is a utility for restoring a
|
||||||
<productname>PostgreSQL</productname> database from an archive
|
<productname>PostgreSQL</productname> database or cluster from an archive
|
||||||
created by <xref linkend="app-pgdump"/> in one of the non-plain-text
|
created by <xref linkend="app-pgdump"/> or
|
||||||
|
<xref linkend="app-pg-dumpall"/> in one of the non-plain-text
|
||||||
formats. It will issue the commands necessary to reconstruct the
|
formats. It will issue the commands necessary to reconstruct the
|
||||||
database to the state it was in at the time it was saved. The
|
database or cluster to the state it was in at the time it was saved. The
|
||||||
archive files also allow <application>pg_restore</application> to
|
archives also allow <application>pg_restore</application> to
|
||||||
be selective about what is restored, or even to reorder the items
|
be selective about what is restored, or even to reorder the items
|
||||||
prior to being restored. The archive files are designed to be
|
prior to being restored. The archive formats are designed to be
|
||||||
portable across architectures.
|
portable across architectures.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
|
@ -52,14 +54,34 @@ PostgreSQL documentation
|
||||||
<application>pg_restore</application> can operate in two modes.
|
<application>pg_restore</application> can operate in two modes.
|
||||||
If a database name is specified, <application>pg_restore</application>
|
If a database name is specified, <application>pg_restore</application>
|
||||||
connects to that database and restores archive contents directly into
|
connects to that database and restores archive contents directly into
|
||||||
the database. Otherwise, a script containing the SQL
|
the database.
|
||||||
commands necessary to rebuild the database is created and written
|
When restoring from a dump made by <application>pg_dumpall</application>,
|
||||||
|
each database will be created and then the restoration will be run in that
|
||||||
|
database.
|
||||||
|
|
||||||
|
Otherwise, when a database name is not specified, a script containing the SQL
|
||||||
|
commands necessary to rebuild the database or cluster is created and written
|
||||||
to a file or standard output. This script output is equivalent to
|
to a file or standard output. This script output is equivalent to
|
||||||
the plain text output format of <application>pg_dump</application>.
|
the plain text output format of <application>pg_dump</application> or
|
||||||
|
<application>pg_dumpall</application>.
|
||||||
|
|
||||||
Some of the options controlling the output are therefore analogous to
|
Some of the options controlling the output are therefore analogous to
|
||||||
<application>pg_dump</application> options.
|
<application>pg_dump</application> options.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A non-plain-text archive made using <application>pg_dumpall</application>
|
||||||
|
is a directory containing a <filename>toc.glo</filename> file with global
|
||||||
|
objects (roles and tablespaces), a <filename>map.dat</filename> file
|
||||||
|
listing the databases, and a subdirectory for each database containing
|
||||||
|
its archive. When restoring such an archive,
|
||||||
|
<application>pg_restore</application> first restores global objects from
|
||||||
|
<filename>toc.glo</filename>, then processes each database listed in
|
||||||
|
<filename>map.dat</filename>. Lines in <filename>map.dat</filename> can
|
||||||
|
be commented out with <literal>#</literal> to skip restoring specific
|
||||||
|
databases.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Obviously, <application>pg_restore</application> cannot restore information
|
Obviously, <application>pg_restore</application> cannot restore information
|
||||||
that is not present in the archive file. For instance, if the
|
that is not present in the archive file. For instance, if the
|
||||||
|
|
@ -130,6 +152,12 @@ PostgreSQL documentation
|
||||||
ignorable error messages will be reported,
|
ignorable error messages will be reported,
|
||||||
unless <option>--if-exists</option> is also specified.
|
unless <option>--if-exists</option> is also specified.
|
||||||
</para>
|
</para>
|
||||||
|
<para>
|
||||||
|
When restoring a <application>pg_dumpall</application> archive,
|
||||||
|
<option>--if-exists</option> is implied by <option>--clean</option>,
|
||||||
|
since global objects such as roles and tablespaces may not exist
|
||||||
|
in the target cluster.
|
||||||
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
|
@ -152,6 +180,8 @@ PostgreSQL documentation
|
||||||
commands that mention this database.
|
commands that mention this database.
|
||||||
Access privileges for the database itself are also restored,
|
Access privileges for the database itself are also restored,
|
||||||
unless <option>--no-acl</option> is specified.
|
unless <option>--no-acl</option> is specified.
|
||||||
|
<option>--create</option> is required when restoring multiple databases
|
||||||
|
from a non-plain-text archive made using <application>pg_dumpall</application>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
|
@ -247,6 +277,28 @@ PostgreSQL documentation
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>-g</option></term>
|
||||||
|
<term><option>--globals-only</option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Restore only global objects (roles and tablespaces), no databases.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This option is only relevant when restoring from a non-plain-text archive made using <application>pg_dumpall</application>.
|
||||||
|
Note: <option>--globals-only</option> cannot be used with
|
||||||
|
<option>--data-only</option>,
|
||||||
|
<option>--schema-only</option>,
|
||||||
|
<option>--statistics-only</option>,
|
||||||
|
<option>--statistics</option>,
|
||||||
|
<option>--exit-on-error</option>,
|
||||||
|
<option>--single-transaction</option>,
|
||||||
|
<option>--clean</option>, or
|
||||||
|
<option>--transaction-size</option>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>-I <replaceable class="parameter">index</replaceable></option></term>
|
<term><option>-I <replaceable class="parameter">index</replaceable></option></term>
|
||||||
<term><option>--index=<replaceable class="parameter">index</replaceable></option></term>
|
<term><option>--index=<replaceable class="parameter">index</replaceable></option></term>
|
||||||
|
|
@ -581,6 +633,28 @@ PostgreSQL documentation
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--exclude-database=<replaceable class="parameter">pattern</replaceable></option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Do not restore databases whose name matches
|
||||||
|
<replaceable class="parameter">pattern</replaceable>.
|
||||||
|
Multiple patterns can be excluded by writing multiple
|
||||||
|
<option>--exclude-database</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 databases can also be excluded by writing wildcard
|
||||||
|
characters in the pattern. When using wildcards, be careful to
|
||||||
|
quote the pattern if needed to prevent shell wildcard expansion.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This option is only relevant when restoring from a non-plain-text archive made using <application>pg_dumpall</application>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
|
<term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
|
@ -669,7 +743,9 @@ PostgreSQL documentation
|
||||||
in <option>--clean</option> mode. This suppresses <quote>does not
|
in <option>--clean</option> mode. This suppresses <quote>does not
|
||||||
exist</quote> errors that might otherwise be reported. This
|
exist</quote> errors that might otherwise be reported. This
|
||||||
option is not valid unless <option>--clean</option> is also
|
option is not valid unless <option>--clean</option> is also
|
||||||
specified.
|
specified. This option is implied when restoring a
|
||||||
|
<application>pg_dumpall</application> archive with
|
||||||
|
<option>--clean</option>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
@ -1125,6 +1201,27 @@ CREATE DATABASE foo WITH TEMPLATE template0;
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
When restoring from a non-plain-text archive made using
|
||||||
|
<application>pg_dumpall</application>, the <option>--section</option>
|
||||||
|
option may be used, but must include <option>pre-data</option>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The following options cannot be used when restoring from a non-plain-text
|
||||||
|
archive made using <application>pg_dumpall</application>:
|
||||||
|
<option>-a/--data-only</option>,
|
||||||
|
<option>-l/--list</option>,
|
||||||
|
<option>-L/--use-list</option>,
|
||||||
|
<option>--no-schema</option>,
|
||||||
|
<option>--statistics-only</option>, and
|
||||||
|
<option>--strict-names</option>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ tests += {
|
||||||
't/004_pg_dump_parallel.pl',
|
't/004_pg_dump_parallel.pl',
|
||||||
't/005_pg_dump_filterfile.pl',
|
't/005_pg_dump_filterfile.pl',
|
||||||
't/006_pg_dump_compress.pl',
|
't/006_pg_dump_compress.pl',
|
||||||
|
't/007_pg_dumpall.pl',
|
||||||
't/010_dump_connstr.pl',
|
't/010_dump_connstr.pl',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,20 @@ on_exit_close_archive(Archive *AHX)
|
||||||
on_exit_nicely(archive_close_connection, &shutdown_info);
|
on_exit_nicely(archive_close_connection, &shutdown_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update the archive handle in the on_exit callback registered by
|
||||||
|
* on_exit_close_archive(). When pg_restore processes a pg_dumpall archive
|
||||||
|
* containing multiple databases, each database is restored from a separate
|
||||||
|
* archive. After closing one archive and opening the next, we update the
|
||||||
|
* shutdown_info to reference the new archive handle so the cleanup callback
|
||||||
|
* will close the correct archive on exit.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
replace_on_exit_close_archive(Archive *AHX)
|
||||||
|
{
|
||||||
|
shutdown_info.AHX = AHX;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* on_exit_nicely handler for shutting down database connections and
|
* on_exit_nicely handler for shutting down database connections and
|
||||||
* worker processes cleanly.
|
* worker processes cleanly.
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ extern void SetArchiveOptions(Archive *AH, DumpOptions *dopt, RestoreOptions *ro
|
||||||
|
|
||||||
extern void ProcessArchiveRestoreOptions(Archive *AHX);
|
extern void ProcessArchiveRestoreOptions(Archive *AHX);
|
||||||
|
|
||||||
extern void RestoreArchive(Archive *AHX);
|
extern void RestoreArchive(Archive *AHX, bool append_data);
|
||||||
|
|
||||||
/* Open an existing archive */
|
/* Open an existing archive */
|
||||||
extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
|
extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ static int RestoringToDB(ArchiveHandle *AH);
|
||||||
static void dump_lo_buf(ArchiveHandle *AH);
|
static void dump_lo_buf(ArchiveHandle *AH);
|
||||||
static void dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim);
|
static void dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim);
|
||||||
static void SetOutput(ArchiveHandle *AH, const char *filename,
|
static void SetOutput(ArchiveHandle *AH, const char *filename,
|
||||||
const pg_compress_specification compression_spec);
|
const pg_compress_specification compression_spec, bool append_data);
|
||||||
static CompressFileHandle *SaveOutput(ArchiveHandle *AH);
|
static CompressFileHandle *SaveOutput(ArchiveHandle *AH);
|
||||||
static void RestoreOutput(ArchiveHandle *AH, CompressFileHandle *savedOutput);
|
static void RestoreOutput(ArchiveHandle *AH, CompressFileHandle *savedOutput);
|
||||||
|
|
||||||
|
|
@ -339,9 +339,14 @@ ProcessArchiveRestoreOptions(Archive *AHX)
|
||||||
StrictNamesCheck(ropt);
|
StrictNamesCheck(ropt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Public */
|
/*
|
||||||
|
* RestoreArchive
|
||||||
|
*
|
||||||
|
* If append_data is set, then append data into file as we are restoring dump
|
||||||
|
* of multiple databases which was taken by pg_dumpall.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
RestoreArchive(Archive *AHX)
|
RestoreArchive(Archive *AHX, bool append_data)
|
||||||
{
|
{
|
||||||
ArchiveHandle *AH = (ArchiveHandle *) AHX;
|
ArchiveHandle *AH = (ArchiveHandle *) AHX;
|
||||||
RestoreOptions *ropt = AH->public.ropt;
|
RestoreOptions *ropt = AH->public.ropt;
|
||||||
|
|
@ -458,7 +463,7 @@ RestoreArchive(Archive *AHX)
|
||||||
*/
|
*/
|
||||||
sav = SaveOutput(AH);
|
sav = SaveOutput(AH);
|
||||||
if (ropt->filename || ropt->compression_spec.algorithm != PG_COMPRESSION_NONE)
|
if (ropt->filename || ropt->compression_spec.algorithm != PG_COMPRESSION_NONE)
|
||||||
SetOutput(AH, ropt->filename, ropt->compression_spec);
|
SetOutput(AH, ropt->filename, ropt->compression_spec, append_data);
|
||||||
|
|
||||||
ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
|
ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
|
||||||
|
|
||||||
|
|
@ -761,6 +766,19 @@ RestoreArchive(Archive *AHX)
|
||||||
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0)
|
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0)
|
||||||
continue; /* ignore if not to be dumped at all */
|
continue; /* ignore if not to be dumped at all */
|
||||||
|
|
||||||
|
/* Skip if no-tablespace is given. */
|
||||||
|
if (ropt->noTablespace && te && te->desc &&
|
||||||
|
(strcmp(te->desc, "TABLESPACE") == 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip DROP DATABASE/ROLES/TABLESPACE if we didn't specify
|
||||||
|
* --clean
|
||||||
|
*/
|
||||||
|
if (!ropt->dropSchema && te && te->desc &&
|
||||||
|
strcmp(te->desc, "DROP_GLOBAL") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
switch (_tocEntryRestorePass(te))
|
switch (_tocEntryRestorePass(te))
|
||||||
{
|
{
|
||||||
case RESTORE_PASS_MAIN:
|
case RESTORE_PASS_MAIN:
|
||||||
|
|
@ -1316,7 +1334,7 @@ PrintTOCSummary(Archive *AHX)
|
||||||
|
|
||||||
sav = SaveOutput(AH);
|
sav = SaveOutput(AH);
|
||||||
if (ropt->filename)
|
if (ropt->filename)
|
||||||
SetOutput(AH, ropt->filename, out_compression_spec);
|
SetOutput(AH, ropt->filename, out_compression_spec, false);
|
||||||
|
|
||||||
if (strftime(stamp_str, sizeof(stamp_str), PGDUMP_STRFTIME_FMT,
|
if (strftime(stamp_str, sizeof(stamp_str), PGDUMP_STRFTIME_FMT,
|
||||||
localtime(&AH->createDate)) == 0)
|
localtime(&AH->createDate)) == 0)
|
||||||
|
|
@ -1691,11 +1709,15 @@ archprintf(Archive *AH, const char *fmt,...)
|
||||||
|
|
||||||
/*******************************
|
/*******************************
|
||||||
* Stuff below here should be 'private' to the archiver routines
|
* Stuff below here should be 'private' to the archiver routines
|
||||||
|
*
|
||||||
|
* If append_data is set, then append data into file as we are restoring dump
|
||||||
|
* of multiple databases which was taken by pg_dumpall.
|
||||||
*******************************/
|
*******************************/
|
||||||
|
|
||||||
static void
|
static void
|
||||||
SetOutput(ArchiveHandle *AH, const char *filename,
|
SetOutput(ArchiveHandle *AH, const char *filename,
|
||||||
const pg_compress_specification compression_spec)
|
const pg_compress_specification compression_spec,
|
||||||
|
bool append_data)
|
||||||
{
|
{
|
||||||
CompressFileHandle *CFH;
|
CompressFileHandle *CFH;
|
||||||
const char *mode;
|
const char *mode;
|
||||||
|
|
@ -1715,7 +1737,7 @@ SetOutput(ArchiveHandle *AH, const char *filename,
|
||||||
else
|
else
|
||||||
fn = fileno(stdout);
|
fn = fileno(stdout);
|
||||||
|
|
||||||
if (AH->mode == archModeAppend)
|
if (append_data || AH->mode == archModeAppend)
|
||||||
mode = PG_BINARY_A;
|
mode = PG_BINARY_A;
|
||||||
else
|
else
|
||||||
mode = PG_BINARY_W;
|
mode = PG_BINARY_W;
|
||||||
|
|
@ -2391,7 +2413,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
|
||||||
|
|
||||||
/* initialize for backwards compatible string processing */
|
/* initialize for backwards compatible string processing */
|
||||||
AH->public.encoding = 0; /* PG_SQL_ASCII */
|
AH->public.encoding = 0; /* PG_SQL_ASCII */
|
||||||
AH->public.std_strings = false;
|
AH->public.std_strings = true;
|
||||||
|
|
||||||
/* sql error handling */
|
/* sql error handling */
|
||||||
AH->public.exit_on_error = true;
|
AH->public.exit_on_error = true;
|
||||||
|
|
@ -3027,6 +3049,16 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Global object TOC entries (e.g., ROLEs or TABLESPACEs) must not be
|
||||||
|
* ignored.
|
||||||
|
*/
|
||||||
|
if (strcmp(te->desc, "ROLE") == 0 ||
|
||||||
|
strcmp(te->desc, "ROLE PROPERTIES") == 0 ||
|
||||||
|
strcmp(te->desc, "TABLESPACE") == 0 ||
|
||||||
|
strcmp(te->desc, "DROP_GLOBAL") == 0)
|
||||||
|
return REQ_SCHEMA;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Process exclusions that affect certain classes of TOC entries.
|
* Process exclusions that affect certain classes of TOC entries.
|
||||||
*/
|
*/
|
||||||
|
|
@ -3062,6 +3094,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
|
||||||
if (ropt->no_subscriptions &&
|
if (ropt->no_subscriptions &&
|
||||||
strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0)
|
strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Comments on global objects (ROLEs or TABLESPACEs) should not be
|
||||||
|
* skipped, since global objects themselves are never skipped.
|
||||||
|
*/
|
||||||
|
if (strncmp(te->tag, "ROLE", strlen("ROLE")) == 0 ||
|
||||||
|
strncmp(te->tag, "TABLESPACE", strlen("TABLESPACE")) == 0)
|
||||||
|
return REQ_SCHEMA;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -3091,6 +3131,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
|
||||||
if (ropt->no_subscriptions &&
|
if (ropt->no_subscriptions &&
|
||||||
strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0)
|
strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Security labels on global objects (ROLEs or TABLESPACEs) should not
|
||||||
|
* be skipped, since global objects themselves are never skipped.
|
||||||
|
*/
|
||||||
|
if (strncmp(te->tag, "ROLE", strlen("ROLE")) == 0 ||
|
||||||
|
strncmp(te->tag, "TABLESPACE", strlen("TABLESPACE")) == 0)
|
||||||
|
return REQ_SCHEMA;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If it's a subscription, maybe ignore it */
|
/* If it's a subscription, maybe ignore it */
|
||||||
|
|
@ -3865,6 +3913,9 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
|
||||||
else if (strcmp(type, "CAST") == 0 ||
|
else if (strcmp(type, "CAST") == 0 ||
|
||||||
strcmp(type, "CHECK CONSTRAINT") == 0 ||
|
strcmp(type, "CHECK CONSTRAINT") == 0 ||
|
||||||
strcmp(type, "CONSTRAINT") == 0 ||
|
strcmp(type, "CONSTRAINT") == 0 ||
|
||||||
|
strcmp(type, "DROP_GLOBAL") == 0 ||
|
||||||
|
strcmp(type, "ROLE PROPERTIES") == 0 ||
|
||||||
|
strcmp(type, "ROLE") == 0 ||
|
||||||
strcmp(type, "DATABASE PROPERTIES") == 0 ||
|
strcmp(type, "DATABASE PROPERTIES") == 0 ||
|
||||||
strcmp(type, "DEFAULT") == 0 ||
|
strcmp(type, "DEFAULT") == 0 ||
|
||||||
strcmp(type, "FK CONSTRAINT") == 0 ||
|
strcmp(type, "FK CONSTRAINT") == 0 ||
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,7 @@ struct _tocEntry
|
||||||
|
|
||||||
extern int parallel_restore(ArchiveHandle *AH, TocEntry *te);
|
extern int parallel_restore(ArchiveHandle *AH, TocEntry *te);
|
||||||
extern void on_exit_close_archive(Archive *AHX);
|
extern void on_exit_close_archive(Archive *AHX);
|
||||||
|
extern void replace_on_exit_close_archive(Archive *AHX);
|
||||||
|
|
||||||
extern void warn_or_exit_horribly(ArchiveHandle *AH, const char *fmt,...) pg_attribute_printf(2, 3);
|
extern void warn_or_exit_horribly(ArchiveHandle *AH, const char *fmt,...) pg_attribute_printf(2, 3);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -826,7 +826,7 @@ _CloseArchive(ArchiveHandle *AH)
|
||||||
savVerbose = AH->public.verbose;
|
savVerbose = AH->public.verbose;
|
||||||
AH->public.verbose = 0;
|
AH->public.verbose = 0;
|
||||||
|
|
||||||
RestoreArchive((Archive *) AH);
|
RestoreArchive((Archive *) AH, false);
|
||||||
|
|
||||||
SetArchiveOptions((Archive *) AH, savDopt, savRopt);
|
SetArchiveOptions((Archive *) AH, savDopt, savRopt);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1292,7 +1292,7 @@ main(int argc, char **argv)
|
||||||
* right now.
|
* right now.
|
||||||
*/
|
*/
|
||||||
if (plainText)
|
if (plainText)
|
||||||
RestoreArchive(fout);
|
RestoreArchive(fout, false);
|
||||||
|
|
||||||
CloseArchive(fout);
|
CloseArchive(fout);
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@
|
||||||
*
|
*
|
||||||
* pg_restore.c
|
* pg_restore.c
|
||||||
* pg_restore is an utility extracting postgres database definitions
|
* pg_restore is an utility extracting postgres database definitions
|
||||||
* from a backup archive created by pg_dump using the archiver
|
* from a backup archive created by pg_dump/pg_dumpall using the archiver
|
||||||
* interface.
|
* interface.
|
||||||
*
|
*
|
||||||
* pg_restore will read the backup archive and
|
* pg_restore will read the backup archive and
|
||||||
|
|
@ -41,12 +41,16 @@
|
||||||
#include "postgres_fe.h"
|
#include "postgres_fe.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#ifdef HAVE_TERMIOS_H
|
#ifdef HAVE_TERMIOS_H
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "common/string.h"
|
||||||
|
#include "connectdb.h"
|
||||||
#include "dumputils.h"
|
#include "dumputils.h"
|
||||||
#include "fe_utils/option_utils.h"
|
#include "fe_utils/option_utils.h"
|
||||||
|
#include "fe_utils/string_utils.h"
|
||||||
#include "filter.h"
|
#include "filter.h"
|
||||||
#include "getopt_long.h"
|
#include "getopt_long.h"
|
||||||
#include "parallel.h"
|
#include "parallel.h"
|
||||||
|
|
@ -54,18 +58,41 @@
|
||||||
|
|
||||||
static void usage(const char *progname);
|
static void usage(const char *progname);
|
||||||
static void read_restore_filters(const char *filename, RestoreOptions *opts);
|
static void read_restore_filters(const char *filename, RestoreOptions *opts);
|
||||||
|
static bool file_exists_in_directory(const char *dir, const char *filename);
|
||||||
|
static int restore_one_database(const char *inputFileSpec, RestoreOptions *opts,
|
||||||
|
int numWorkers, bool append_data);
|
||||||
|
static int restore_global_objects(const char *inputFileSpec, RestoreOptions *opts);
|
||||||
|
|
||||||
|
static int restore_all_databases(const char *inputFileSpec,
|
||||||
|
SimpleStringList db_exclude_patterns, RestoreOptions *opts, int numWorkers);
|
||||||
|
static int get_dbnames_list_to_restore(PGconn *conn,
|
||||||
|
SimplePtrList *dbname_oid_list,
|
||||||
|
SimpleStringList db_exclude_patterns);
|
||||||
|
static int get_dbname_oid_list_from_mfile(char *dumpdirpath,
|
||||||
|
SimplePtrList *dbname_oid_list);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stores a database OID and the corresponding name.
|
||||||
|
*/
|
||||||
|
typedef struct DbOidName
|
||||||
|
{
|
||||||
|
Oid oid;
|
||||||
|
char str[FLEXIBLE_ARRAY_MEMBER]; /* null-terminated string here */
|
||||||
|
} DbOidName;
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
RestoreOptions *opts;
|
RestoreOptions *opts;
|
||||||
int c;
|
int c;
|
||||||
int exit_code;
|
|
||||||
int numWorkers = 1;
|
int numWorkers = 1;
|
||||||
Archive *AH;
|
|
||||||
char *inputFileSpec;
|
char *inputFileSpec;
|
||||||
bool data_only = false;
|
bool data_only = false;
|
||||||
bool schema_only = false;
|
bool schema_only = false;
|
||||||
|
int n_errors = 0;
|
||||||
|
bool globals_only = false;
|
||||||
|
SimpleStringList db_exclude_patterns = {NULL, NULL};
|
||||||
static int disable_triggers = 0;
|
static int disable_triggers = 0;
|
||||||
static int enable_row_security = 0;
|
static int enable_row_security = 0;
|
||||||
static int if_exists = 0;
|
static int if_exists = 0;
|
||||||
|
|
@ -89,6 +116,7 @@ main(int argc, char **argv)
|
||||||
{"clean", 0, NULL, 'c'},
|
{"clean", 0, NULL, 'c'},
|
||||||
{"create", 0, NULL, 'C'},
|
{"create", 0, NULL, 'C'},
|
||||||
{"data-only", 0, NULL, 'a'},
|
{"data-only", 0, NULL, 'a'},
|
||||||
|
{"globals-only", 0, NULL, 'g'},
|
||||||
{"dbname", 1, NULL, 'd'},
|
{"dbname", 1, NULL, 'd'},
|
||||||
{"exit-on-error", 0, NULL, 'e'},
|
{"exit-on-error", 0, NULL, 'e'},
|
||||||
{"exclude-schema", 1, NULL, 'N'},
|
{"exclude-schema", 1, NULL, 'N'},
|
||||||
|
|
@ -142,6 +170,7 @@ main(int argc, char **argv)
|
||||||
{"statistics-only", no_argument, &statistics_only, 1},
|
{"statistics-only", no_argument, &statistics_only, 1},
|
||||||
{"filter", required_argument, NULL, 4},
|
{"filter", required_argument, NULL, 4},
|
||||||
{"restrict-key", required_argument, NULL, 6},
|
{"restrict-key", required_argument, NULL, 6},
|
||||||
|
{"exclude-database", required_argument, NULL, 7},
|
||||||
|
|
||||||
{NULL, 0, NULL, 0}
|
{NULL, 0, NULL, 0}
|
||||||
};
|
};
|
||||||
|
|
@ -170,7 +199,7 @@ main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
|
while ((c = getopt_long(argc, argv, "acCd:ef:F:gh:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1",
|
||||||
cmdopts, NULL)) != -1)
|
cmdopts, NULL)) != -1)
|
||||||
{
|
{
|
||||||
switch (c)
|
switch (c)
|
||||||
|
|
@ -197,11 +226,14 @@ main(int argc, char **argv)
|
||||||
if (strlen(optarg) != 0)
|
if (strlen(optarg) != 0)
|
||||||
opts->formatName = pg_strdup(optarg);
|
opts->formatName = pg_strdup(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'g':
|
||||||
|
/* restore only global sql commands. */
|
||||||
|
globals_only = true;
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
if (strlen(optarg) != 0)
|
if (strlen(optarg) != 0)
|
||||||
opts->cparams.pghost = pg_strdup(optarg);
|
opts->cparams.pghost = pg_strdup(optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'j': /* number of restore jobs */
|
case 'j': /* number of restore jobs */
|
||||||
if (!option_parse_int(optarg, "-j/--jobs", 1,
|
if (!option_parse_int(optarg, "-j/--jobs", 1,
|
||||||
PG_MAX_JOBS,
|
PG_MAX_JOBS,
|
||||||
|
|
@ -321,6 +353,10 @@ main(int argc, char **argv)
|
||||||
opts->restrict_key = pg_strdup(optarg);
|
opts->restrict_key = pg_strdup(optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 7: /* database patterns to skip */
|
||||||
|
simple_string_list_append(&db_exclude_patterns, optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
/* getopt_long already emitted a complaint */
|
/* getopt_long already emitted a complaint */
|
||||||
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
||||||
|
|
@ -347,6 +383,14 @@ main(int argc, char **argv)
|
||||||
if (!opts->cparams.dbname && !opts->filename && !opts->tocSummary)
|
if (!opts->cparams.dbname && !opts->filename && !opts->tocSummary)
|
||||||
pg_fatal("one of -d/--dbname and -f/--file must be specified");
|
pg_fatal("one of -d/--dbname and -f/--file must be specified");
|
||||||
|
|
||||||
|
if (db_exclude_patterns.head != NULL && globals_only)
|
||||||
|
{
|
||||||
|
pg_log_error("option %s cannot be used together with %s",
|
||||||
|
"--exclude-database", "-g/--globals-only");
|
||||||
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
||||||
|
exit_nicely(1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Should get at most one of -d and -f, else user is confused */
|
/* Should get at most one of -d and -f, else user is confused */
|
||||||
if (opts->cparams.dbname)
|
if (opts->cparams.dbname)
|
||||||
{
|
{
|
||||||
|
|
@ -420,6 +464,31 @@ main(int argc, char **argv)
|
||||||
pg_fatal("options %s and %s cannot be used together",
|
pg_fatal("options %s and %s cannot be used together",
|
||||||
"-1/--single-transaction", "--transaction-size");
|
"-1/--single-transaction", "--transaction-size");
|
||||||
|
|
||||||
|
if (opts->single_txn && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together when restoring an archive created by pg_dumpall",
|
||||||
|
"--single-transaction", "-g/--globals-only");
|
||||||
|
|
||||||
|
if (opts->txn_size && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together when restoring an archive created by pg_dumpall",
|
||||||
|
"--transaction-size", "-g/--globals-only");
|
||||||
|
|
||||||
|
if (opts->exit_on_error && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together when restoring an archive created by pg_dumpall",
|
||||||
|
"--exit-on-error", "-g/--globals-only");
|
||||||
|
|
||||||
|
if (data_only && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together",
|
||||||
|
"-a/--data-only", "-g/--globals-only");
|
||||||
|
if (schema_only && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together",
|
||||||
|
"-s/--schema-only", "-g/--globals-only");
|
||||||
|
if (statistics_only && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together",
|
||||||
|
"--statistics-only", "-g/--globals-only");
|
||||||
|
if (with_statistics && globals_only)
|
||||||
|
pg_fatal("options %s and %s cannot be used together",
|
||||||
|
"--statistics", "-g/--globals-only");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -C is not compatible with -1, because we can't create a database inside
|
* -C is not compatible with -1, because we can't create a database inside
|
||||||
* a transaction block.
|
* a transaction block.
|
||||||
|
|
@ -485,6 +554,183 @@ main(int argc, char **argv)
|
||||||
opts->formatName);
|
opts->formatName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If toc.glo file is present, then restore all the databases from
|
||||||
|
* map.dat, but skip restoring those matching --exclude-database patterns.
|
||||||
|
*/
|
||||||
|
if (inputFileSpec != NULL &&
|
||||||
|
(file_exists_in_directory(inputFileSpec, "toc.glo")))
|
||||||
|
{
|
||||||
|
char global_path[MAXPGPATH];
|
||||||
|
RestoreOptions *tmpopts = pg_malloc0_object(RestoreOptions);
|
||||||
|
|
||||||
|
opts->format = archUnknown;
|
||||||
|
|
||||||
|
memcpy(tmpopts, opts, sizeof(RestoreOptions));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Can only use --list or --use-list options with a single database
|
||||||
|
* dump.
|
||||||
|
*/
|
||||||
|
if (opts->tocSummary)
|
||||||
|
pg_fatal("option %s cannot be used when restoring an archive created by pg_dumpall",
|
||||||
|
"-l/--list");
|
||||||
|
if (opts->tocFile)
|
||||||
|
pg_fatal("option %s cannot be used when restoring an archive created by pg_dumpall",
|
||||||
|
"-L/--use-list");
|
||||||
|
|
||||||
|
if (opts->strict_names)
|
||||||
|
pg_fatal("option %s cannot be used when restoring an archive created by pg_dumpall",
|
||||||
|
"--strict-names");
|
||||||
|
if (globals_only && opts->dropSchema)
|
||||||
|
pg_fatal("options %s and %s cannot be used together when restoring an archive created by pg_dumpall",
|
||||||
|
"--clean", "-g/--globals-only");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For pg_dumpall archives, --clean implies --if-exists since global
|
||||||
|
* objects may not exist in the target cluster.
|
||||||
|
*/
|
||||||
|
if (opts->dropSchema && !opts->if_exists)
|
||||||
|
{
|
||||||
|
opts->if_exists = 1;
|
||||||
|
pg_log_info("--if-exists is implied by --clean for pg_dumpall archives");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (no_schema)
|
||||||
|
pg_fatal("option %s cannot be used when restoring an archive created by pg_dumpall",
|
||||||
|
"--no-schema");
|
||||||
|
|
||||||
|
if (data_only)
|
||||||
|
pg_fatal("option %s cannot be used when restoring an archive created by pg_dumpall",
|
||||||
|
"-a/--data-only");
|
||||||
|
|
||||||
|
if (statistics_only)
|
||||||
|
pg_fatal("option %s cannot be used when restoring an archive created by pg_dumpall",
|
||||||
|
"--statistics-only");
|
||||||
|
|
||||||
|
if (!(opts->dumpSections & DUMP_PRE_DATA))
|
||||||
|
pg_fatal("option %s cannot exclude %s when restoring a pg_dumpall archive",
|
||||||
|
"--section", "--pre-data");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To restore from a pg_dumpall archive, -C (create database) option
|
||||||
|
* must be specified unless we are only restoring globals.
|
||||||
|
*/
|
||||||
|
if (!globals_only && opts->createDB != 1)
|
||||||
|
{
|
||||||
|
pg_log_error("option %s must be specified when restoring an archive created by pg_dumpall",
|
||||||
|
"-C/--create");
|
||||||
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
||||||
|
pg_log_error_hint("Individual databases can be restored using their specific archives.");
|
||||||
|
exit_nicely(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Always restore global objects, even if --exclude-database results
|
||||||
|
* in zero databases to process. If 'globals-only' is set, exit
|
||||||
|
* immediately.
|
||||||
|
*/
|
||||||
|
snprintf(global_path, MAXPGPATH, "%s/toc.glo", inputFileSpec);
|
||||||
|
|
||||||
|
n_errors = restore_global_objects(global_path, tmpopts);
|
||||||
|
|
||||||
|
if (globals_only)
|
||||||
|
pg_log_info("database restoring skipped because option %s was specified",
|
||||||
|
"-g/--globals-only");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Now restore all the databases from map.dat */
|
||||||
|
n_errors = n_errors + restore_all_databases(inputFileSpec, db_exclude_patterns,
|
||||||
|
opts, numWorkers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free db pattern list. */
|
||||||
|
simple_string_list_destroy(&db_exclude_patterns);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (db_exclude_patterns.head != NULL)
|
||||||
|
{
|
||||||
|
simple_string_list_destroy(&db_exclude_patterns);
|
||||||
|
pg_fatal("option %s can be used only when restoring an archive created by pg_dumpall",
|
||||||
|
"--exclude-database");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globals_only)
|
||||||
|
pg_fatal("option %s can be used only when restoring an archive created by pg_dumpall",
|
||||||
|
"-g/--globals-only");
|
||||||
|
|
||||||
|
/* Process if toc.glo file does not exist. */
|
||||||
|
n_errors = restore_one_database(inputFileSpec, opts, numWorkers, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Done, print a summary of ignored errors during restore. */
|
||||||
|
if (n_errors)
|
||||||
|
{
|
||||||
|
pg_log_warning("errors ignored on restore: %d", n_errors);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* restore_global_objects
|
||||||
|
*
|
||||||
|
* This restore all global objects.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
restore_global_objects(const char *inputFileSpec, RestoreOptions *opts)
|
||||||
|
{
|
||||||
|
Archive *AH;
|
||||||
|
int nerror = 0;
|
||||||
|
|
||||||
|
/* Set format as custom so that toc.glo file can be read. */
|
||||||
|
opts->format = archCustom;
|
||||||
|
opts->txn_size = 0;
|
||||||
|
|
||||||
|
AH = OpenArchive(inputFileSpec, opts->format);
|
||||||
|
|
||||||
|
SetArchiveOptions(AH, NULL, opts);
|
||||||
|
|
||||||
|
on_exit_close_archive(AH);
|
||||||
|
|
||||||
|
/* Let the archiver know how noisy to be */
|
||||||
|
AH->verbose = opts->verbose;
|
||||||
|
|
||||||
|
/* Don't output TOC entry comments when restoring globals */
|
||||||
|
((ArchiveHandle *) AH)->noTocComments = 1;
|
||||||
|
|
||||||
|
AH->exit_on_error = false;
|
||||||
|
|
||||||
|
/* Parallel execution is not supported for global object restoration. */
|
||||||
|
AH->numWorkers = 1;
|
||||||
|
|
||||||
|
ProcessArchiveRestoreOptions(AH);
|
||||||
|
RestoreArchive(AH, false);
|
||||||
|
|
||||||
|
nerror = AH->n_errors;
|
||||||
|
|
||||||
|
/* AH may be freed in CloseArchive? */
|
||||||
|
CloseArchive(AH);
|
||||||
|
|
||||||
|
return nerror;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* restore_one_database
|
||||||
|
*
|
||||||
|
* This will restore one database using toc.dat file.
|
||||||
|
*
|
||||||
|
* returns the number of errors while doing restore.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
restore_one_database(const char *inputFileSpec, RestoreOptions *opts,
|
||||||
|
int numWorkers, bool append_data)
|
||||||
|
{
|
||||||
|
Archive *AH;
|
||||||
|
int n_errors;
|
||||||
|
|
||||||
AH = OpenArchive(inputFileSpec, opts->format);
|
AH = OpenArchive(inputFileSpec, opts->format);
|
||||||
|
|
||||||
SetArchiveOptions(AH, NULL, opts);
|
SetArchiveOptions(AH, NULL, opts);
|
||||||
|
|
@ -492,9 +738,15 @@ main(int argc, char **argv)
|
||||||
/*
|
/*
|
||||||
* We don't have a connection yet but that doesn't matter. The connection
|
* We don't have a connection yet but that doesn't matter. The connection
|
||||||
* is initialized to NULL and if we terminate through exit_nicely() while
|
* is initialized to NULL and if we terminate through exit_nicely() while
|
||||||
* it's still NULL, the cleanup function will just be a no-op.
|
* it's still NULL, the cleanup function will just be a no-op. If we are
|
||||||
|
* restoring multiple databases, then only update AX handle for cleanup as
|
||||||
|
* the previous entry was already in the array and we had closed previous
|
||||||
|
* connection, so we can use the same array slot.
|
||||||
*/
|
*/
|
||||||
on_exit_close_archive(AH);
|
if (!append_data)
|
||||||
|
on_exit_close_archive(AH);
|
||||||
|
else
|
||||||
|
replace_on_exit_close_archive(AH);
|
||||||
|
|
||||||
/* Let the archiver know how noisy to be */
|
/* Let the archiver know how noisy to be */
|
||||||
AH->verbose = opts->verbose;
|
AH->verbose = opts->verbose;
|
||||||
|
|
@ -514,25 +766,21 @@ main(int argc, char **argv)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ProcessArchiveRestoreOptions(AH);
|
ProcessArchiveRestoreOptions(AH);
|
||||||
RestoreArchive(AH);
|
RestoreArchive(AH, append_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* done, print a summary of ignored errors */
|
n_errors = AH->n_errors;
|
||||||
if (AH->n_errors)
|
|
||||||
pg_log_warning("errors ignored on restore: %d", AH->n_errors);
|
|
||||||
|
|
||||||
/* AH may be freed in CloseArchive? */
|
/* AH may be freed in CloseArchive? */
|
||||||
exit_code = AH->n_errors ? 1 : 0;
|
|
||||||
|
|
||||||
CloseArchive(AH);
|
CloseArchive(AH);
|
||||||
|
|
||||||
return exit_code;
|
return n_errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
usage(const char *progname)
|
usage(const char *progname)
|
||||||
{
|
{
|
||||||
printf(_("%s restores a PostgreSQL database from an archive created by pg_dump.\n\n"), progname);
|
printf(_("%s restores PostgreSQL databases from archives created by pg_dump or pg_dumpall.\n\n"), progname);
|
||||||
printf(_("Usage:\n"));
|
printf(_("Usage:\n"));
|
||||||
printf(_(" %s [OPTION]... [FILE]\n"), progname);
|
printf(_(" %s [OPTION]... [FILE]\n"), progname);
|
||||||
|
|
||||||
|
|
@ -550,6 +798,7 @@ usage(const char *progname)
|
||||||
printf(_(" -c, --clean clean (drop) database objects before recreating\n"));
|
printf(_(" -c, --clean clean (drop) database objects before recreating\n"));
|
||||||
printf(_(" -C, --create create the target database\n"));
|
printf(_(" -C, --create create the target database\n"));
|
||||||
printf(_(" -e, --exit-on-error exit on error, default is to continue\n"));
|
printf(_(" -e, --exit-on-error exit on error, default is to continue\n"));
|
||||||
|
printf(_(" -g, --globals-only restore only global objects, no databases\n"));
|
||||||
printf(_(" -I, --index=NAME restore named index\n"));
|
printf(_(" -I, --index=NAME restore named index\n"));
|
||||||
printf(_(" -j, --jobs=NUM use this many parallel jobs to restore\n"));
|
printf(_(" -j, --jobs=NUM use this many parallel jobs to restore\n"));
|
||||||
printf(_(" -L, --use-list=FILENAME use table of contents from this file for\n"
|
printf(_(" -L, --use-list=FILENAME use table of contents from this file for\n"
|
||||||
|
|
@ -566,6 +815,7 @@ usage(const char *progname)
|
||||||
printf(_(" -1, --single-transaction restore as a single transaction\n"));
|
printf(_(" -1, --single-transaction restore as a single transaction\n"));
|
||||||
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
|
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
|
||||||
printf(_(" --enable-row-security enable row security\n"));
|
printf(_(" --enable-row-security enable row security\n"));
|
||||||
|
printf(_(" --exclude-database=PATTERN do not restore the specified database(s)\n"));
|
||||||
printf(_(" --filter=FILENAME restore or skip objects based on expressions\n"
|
printf(_(" --filter=FILENAME restore or skip objects based on expressions\n"
|
||||||
" in FILENAME\n"));
|
" in FILENAME\n"));
|
||||||
printf(_(" --if-exists use IF EXISTS when dropping objects\n"));
|
printf(_(" --if-exists use IF EXISTS when dropping objects\n"));
|
||||||
|
|
@ -601,8 +851,8 @@ usage(const char *progname)
|
||||||
printf(_(" --role=ROLENAME do SET ROLE before restore\n"));
|
printf(_(" --role=ROLENAME do SET ROLE before restore\n"));
|
||||||
|
|
||||||
printf(_("\n"
|
printf(_("\n"
|
||||||
"The options -I, -n, -N, -P, -t, -T, and --section can be combined and specified\n"
|
"The options -I, -n, -N, -P, -t, -T, --section, and --exclude-database can be\n"
|
||||||
"multiple times to select multiple objects.\n"));
|
"combined and specified multiple times to select multiple objects.\n"));
|
||||||
printf(_("\nIf no input file name is supplied, then standard input is used.\n\n"));
|
printf(_("\nIf no input file name is supplied, then standard input is used.\n\n"));
|
||||||
printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
||||||
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
|
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
|
||||||
|
|
@ -707,3 +957,422 @@ read_restore_filters(const char *filename, RestoreOptions *opts)
|
||||||
|
|
||||||
filter_free(&fstate);
|
filter_free(&fstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* file_exists_in_directory
|
||||||
|
*
|
||||||
|
* Returns true if the file exists in the given directory.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
file_exists_in_directory(const char *dir, const char *filename)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
char buf[MAXPGPATH];
|
||||||
|
|
||||||
|
if (snprintf(buf, MAXPGPATH, "%s/%s", dir, filename) >= MAXPGPATH)
|
||||||
|
pg_fatal("directory name too long: \"%s\"", dir);
|
||||||
|
|
||||||
|
return (stat(buf, &st) == 0 && S_ISREG(st.st_mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_dbnames_list_to_restore
|
||||||
|
*
|
||||||
|
* This will mark for skipping any entries from dbname_oid_list that pattern match an
|
||||||
|
* entry in the db_exclude_patterns list.
|
||||||
|
*
|
||||||
|
* Returns the number of database to be restored.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
get_dbnames_list_to_restore(PGconn *conn,
|
||||||
|
SimplePtrList *dbname_oid_list,
|
||||||
|
SimpleStringList db_exclude_patterns)
|
||||||
|
{
|
||||||
|
int count_db = 0;
|
||||||
|
PQExpBuffer query;
|
||||||
|
PQExpBuffer db_lit;
|
||||||
|
PGresult *res;
|
||||||
|
|
||||||
|
query = createPQExpBuffer();
|
||||||
|
db_lit = createPQExpBuffer();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process one by one all dbnames and if specified to skip restoring, then
|
||||||
|
* remove dbname from list.
|
||||||
|
*/
|
||||||
|
for (SimplePtrListCell *db_cell = dbname_oid_list->head;
|
||||||
|
db_cell; db_cell = db_cell->next)
|
||||||
|
{
|
||||||
|
DbOidName *dbidname = (DbOidName *) db_cell->ptr;
|
||||||
|
bool skip_db_restore = false;
|
||||||
|
|
||||||
|
resetPQExpBuffer(query);
|
||||||
|
resetPQExpBuffer(db_lit);
|
||||||
|
|
||||||
|
appendStringLiteralConn(db_lit, dbidname->str, conn);
|
||||||
|
|
||||||
|
for (SimpleStringListCell *pat_cell = db_exclude_patterns.head; pat_cell; pat_cell = pat_cell->next)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If there is an exact match then we don't need to try a pattern
|
||||||
|
* match
|
||||||
|
*/
|
||||||
|
if (pg_strcasecmp(dbidname->str, pat_cell->val) == 0)
|
||||||
|
skip_db_restore = true;
|
||||||
|
/* Otherwise, try a pattern match if there is a connection */
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int dotcnt;
|
||||||
|
|
||||||
|
appendPQExpBufferStr(query, "SELECT 1 ");
|
||||||
|
processSQLNamePattern(conn, query, pat_cell->val, false,
|
||||||
|
false, NULL, db_lit->data,
|
||||||
|
NULL, NULL, NULL, &dotcnt);
|
||||||
|
|
||||||
|
if (dotcnt > 0)
|
||||||
|
{
|
||||||
|
pg_log_error("improper qualified name (too many dotted names): %s",
|
||||||
|
dbidname->str);
|
||||||
|
PQfinish(conn);
|
||||||
|
exit_nicely(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = executeQuery(conn, query->data);
|
||||||
|
|
||||||
|
if (PQntuples(res))
|
||||||
|
{
|
||||||
|
skip_db_restore = true;
|
||||||
|
pg_log_info("database name \"%s\" matches --exclude-database pattern \"%s\"", dbidname->str, pat_cell->val);
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(res);
|
||||||
|
resetPQExpBuffer(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip_db_restore)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mark db to be skipped or increment the counter of dbs to be
|
||||||
|
* restored
|
||||||
|
*/
|
||||||
|
if (skip_db_restore)
|
||||||
|
{
|
||||||
|
pg_log_info("excluding database \"%s\"", dbidname->str);
|
||||||
|
dbidname->oid = InvalidOid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
count_db++;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyPQExpBuffer(query);
|
||||||
|
destroyPQExpBuffer(db_lit);
|
||||||
|
|
||||||
|
return count_db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_dbname_oid_list_from_mfile
|
||||||
|
*
|
||||||
|
* Open map.dat file and read line by line and then prepare a list of database
|
||||||
|
* names and corresponding db_oid.
|
||||||
|
*
|
||||||
|
* Returns, total number of database names in map.dat file.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
get_dbname_oid_list_from_mfile(char *dumpdirpath, SimplePtrList *dbname_oid_list)
|
||||||
|
{
|
||||||
|
StringInfoData linebuf;
|
||||||
|
FILE *pfile;
|
||||||
|
char map_file_path[MAXPGPATH];
|
||||||
|
int count = 0;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there is no map.dat file in dump, then return from here as there is
|
||||||
|
* no database to restore.
|
||||||
|
*/
|
||||||
|
if (!file_exists_in_directory(dumpdirpath, "map.dat"))
|
||||||
|
{
|
||||||
|
pg_log_info("database restoring is skipped because file \"%s\" does not exist in directory \"%s\"", "map.dat", dumpdirpath);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = strlen(dumpdirpath);
|
||||||
|
|
||||||
|
/* Trim slash from directory name. */
|
||||||
|
while (len > 1 && dumpdirpath[len - 1] == '/')
|
||||||
|
{
|
||||||
|
dumpdirpath[len - 1] = '\0';
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(map_file_path, MAXPGPATH, "%s/map.dat", dumpdirpath);
|
||||||
|
|
||||||
|
/* Open map.dat file. */
|
||||||
|
pfile = fopen(map_file_path, PG_BINARY_R);
|
||||||
|
|
||||||
|
if (pfile == NULL)
|
||||||
|
pg_fatal("could not open file \"%s\": %m", map_file_path);
|
||||||
|
|
||||||
|
initStringInfo(&linebuf);
|
||||||
|
|
||||||
|
/* Append all the dbname/db_oid combinations to the list. */
|
||||||
|
while (pg_get_line_buf(pfile, &linebuf))
|
||||||
|
{
|
||||||
|
Oid db_oid = InvalidOid;
|
||||||
|
char *dbname;
|
||||||
|
DbOidName *dbidname;
|
||||||
|
int namelen;
|
||||||
|
char *p = linebuf.data;
|
||||||
|
|
||||||
|
/* look for the dboid. */
|
||||||
|
while (isdigit((unsigned char) *p))
|
||||||
|
p++;
|
||||||
|
|
||||||
|
/* ignore lines that don't begin with a digit */
|
||||||
|
if (p == linebuf.data)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (*p == ' ')
|
||||||
|
{
|
||||||
|
sscanf(linebuf.data, "%u", &db_oid);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dbname is the rest of the line */
|
||||||
|
dbname = p;
|
||||||
|
namelen = strlen(dbname);
|
||||||
|
|
||||||
|
/* Strip trailing newline */
|
||||||
|
if (namelen > 0 && dbname[namelen - 1] == '\n')
|
||||||
|
dbname[--namelen] = '\0';
|
||||||
|
|
||||||
|
/* Report error and exit if the file has any corrupted data. */
|
||||||
|
if (!OidIsValid(db_oid) || namelen < 1)
|
||||||
|
pg_fatal("invalid entry in file \"%s\" on line %d", map_file_path,
|
||||||
|
count + 1);
|
||||||
|
|
||||||
|
dbidname = pg_malloc(offsetof(DbOidName, str) + namelen + 1);
|
||||||
|
dbidname->oid = db_oid;
|
||||||
|
strlcpy(dbidname->str, dbname, namelen + 1);
|
||||||
|
|
||||||
|
pg_log_info("found database \"%s\" (OID: %u) in file \"%s\"",
|
||||||
|
dbidname->str, db_oid, map_file_path);
|
||||||
|
|
||||||
|
simple_ptr_list_append(dbname_oid_list, dbidname);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close map.dat file. */
|
||||||
|
fclose(pfile);
|
||||||
|
|
||||||
|
pfree(linebuf.data);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* restore_all_databases
|
||||||
|
*
|
||||||
|
* This will restore databases those dumps are present in
|
||||||
|
* directory based on map.dat file mapping.
|
||||||
|
*
|
||||||
|
* This will skip restoring for databases that are specified with
|
||||||
|
* exclude-database option.
|
||||||
|
*
|
||||||
|
* returns, number of errors while doing restore.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
restore_all_databases(const char *inputFileSpec,
|
||||||
|
SimpleStringList db_exclude_patterns, RestoreOptions *opts,
|
||||||
|
int numWorkers)
|
||||||
|
{
|
||||||
|
SimplePtrList dbname_oid_list = {NULL, NULL};
|
||||||
|
int num_db_restore = 0;
|
||||||
|
int num_total_db;
|
||||||
|
int n_errors_total = 0;
|
||||||
|
char *connected_db = NULL;
|
||||||
|
PGconn *conn = NULL;
|
||||||
|
RestoreOptions *original_opts = pg_malloc0_object(RestoreOptions);
|
||||||
|
RestoreOptions *tmpopts = pg_malloc0_object(RestoreOptions);
|
||||||
|
|
||||||
|
memcpy(original_opts, opts, sizeof(RestoreOptions));
|
||||||
|
|
||||||
|
/* Save db name to reuse it for all the database. */
|
||||||
|
if (opts->cparams.dbname)
|
||||||
|
connected_db = opts->cparams.dbname;
|
||||||
|
|
||||||
|
num_total_db = get_dbname_oid_list_from_mfile((char *) inputFileSpec, &dbname_oid_list);
|
||||||
|
|
||||||
|
pg_log_info(ngettext("found %d database name in \"%s\"",
|
||||||
|
"found %d database names in \"%s\"",
|
||||||
|
num_total_db),
|
||||||
|
num_total_db, "map.dat");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If exclude-patterns is given, connect to the database to process them.
|
||||||
|
*/
|
||||||
|
if (db_exclude_patterns.head != NULL)
|
||||||
|
{
|
||||||
|
if (opts->cparams.dbname)
|
||||||
|
{
|
||||||
|
conn = ConnectDatabase(opts->cparams.dbname, NULL, opts->cparams.pghost,
|
||||||
|
opts->cparams.pgport, opts->cparams.username, TRI_DEFAULT,
|
||||||
|
false, progname, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
pg_fatal("could not connect to database \"%s\"", opts->cparams.dbname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
{
|
||||||
|
pg_log_info("trying to connect to database \"%s\"", "postgres");
|
||||||
|
|
||||||
|
conn = ConnectDatabase("postgres", NULL, opts->cparams.pghost,
|
||||||
|
opts->cparams.pgport, opts->cparams.username, TRI_DEFAULT,
|
||||||
|
false, progname, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
|
/* Try with template1. */
|
||||||
|
if (!conn)
|
||||||
|
{
|
||||||
|
pg_log_info("trying to connect to database \"%s\"", "template1");
|
||||||
|
|
||||||
|
conn = ConnectDatabase("template1", NULL, opts->cparams.pghost,
|
||||||
|
opts->cparams.pgport, opts->cparams.username, TRI_DEFAULT,
|
||||||
|
false, progname, NULL, NULL, NULL, NULL);
|
||||||
|
if (!conn)
|
||||||
|
{
|
||||||
|
pg_log_error("could not connect to databases \"postgres\" or \"template1\"\n"
|
||||||
|
"Please specify an alternative database.");
|
||||||
|
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
|
||||||
|
exit_nicely(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter the db list according to the exclude patterns. */
|
||||||
|
num_db_restore = get_dbnames_list_to_restore(conn, &dbname_oid_list,
|
||||||
|
db_exclude_patterns);
|
||||||
|
PQfinish(conn);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
num_db_restore = num_total_db;
|
||||||
|
|
||||||
|
/* Exit if no db needs to be restored. */
|
||||||
|
if (num_db_restore == 0)
|
||||||
|
{
|
||||||
|
pg_log_info(ngettext("no database needs restoring out of %d database",
|
||||||
|
"no database needs restoring out of %d databases", num_total_db),
|
||||||
|
num_total_db);
|
||||||
|
pg_free(original_opts);
|
||||||
|
pg_free(tmpopts);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pg_log_info("need to restore %d databases out of %d databases", num_db_restore, num_total_db);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have a list of databases to restore after processing the
|
||||||
|
* exclude-database switch(es). Now we can restore them one by one.
|
||||||
|
*/
|
||||||
|
for (SimplePtrListCell *db_cell = dbname_oid_list.head;
|
||||||
|
db_cell; db_cell = db_cell->next)
|
||||||
|
{
|
||||||
|
DbOidName *dbidname = (DbOidName *) db_cell->ptr;
|
||||||
|
char subdirpath[MAXPGPATH];
|
||||||
|
char subdirdbpath[MAXPGPATH];
|
||||||
|
char dbfilename[MAXPGPATH];
|
||||||
|
int n_errors;
|
||||||
|
|
||||||
|
/* ignore dbs marked for skipping */
|
||||||
|
if (dbidname->oid == InvalidOid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since pg_backup_archiver.c may modify RestoreOptions during the
|
||||||
|
* previous restore, we must provide a fresh copy of the original
|
||||||
|
* "opts" for each call to restore_one_database.
|
||||||
|
*/
|
||||||
|
memcpy(tmpopts, original_opts, sizeof(RestoreOptions));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to reset override_dbname so that objects can be restored
|
||||||
|
* into an already created database. (used with -d/--dbname option)
|
||||||
|
*/
|
||||||
|
if (tmpopts->cparams.override_dbname)
|
||||||
|
{
|
||||||
|
pfree(tmpopts->cparams.override_dbname);
|
||||||
|
tmpopts->cparams.override_dbname = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(subdirdbpath, MAXPGPATH, "%s/databases", inputFileSpec);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look for the database dump file/dir. If there is an {oid}.tar or
|
||||||
|
* {oid}.dmp file, use it. Otherwise try to use a directory called
|
||||||
|
* {oid}
|
||||||
|
*/
|
||||||
|
snprintf(dbfilename, MAXPGPATH, "%u.tar", dbidname->oid);
|
||||||
|
if (file_exists_in_directory(subdirdbpath, dbfilename))
|
||||||
|
snprintf(subdirpath, MAXPGPATH, "%s/databases/%u.tar", inputFileSpec, dbidname->oid);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(dbfilename, MAXPGPATH, "%u.dmp", dbidname->oid);
|
||||||
|
|
||||||
|
if (file_exists_in_directory(subdirdbpath, dbfilename))
|
||||||
|
snprintf(subdirpath, MAXPGPATH, "%s/databases/%u.dmp", inputFileSpec, dbidname->oid);
|
||||||
|
else
|
||||||
|
snprintf(subdirpath, MAXPGPATH, "%s/databases/%u", inputFileSpec, dbidname->oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
pg_log_info("restoring database \"%s\"", dbidname->str);
|
||||||
|
|
||||||
|
/* If database is already created, then don't set createDB flag. */
|
||||||
|
if (tmpopts->cparams.dbname)
|
||||||
|
{
|
||||||
|
PGconn *test_conn;
|
||||||
|
|
||||||
|
test_conn = ConnectDatabase(dbidname->str, NULL, tmpopts->cparams.pghost,
|
||||||
|
tmpopts->cparams.pgport, tmpopts->cparams.username, TRI_DEFAULT,
|
||||||
|
false, progname, NULL, NULL, NULL, NULL);
|
||||||
|
if (test_conn)
|
||||||
|
{
|
||||||
|
PQfinish(test_conn);
|
||||||
|
|
||||||
|
/* Use already created database for connection. */
|
||||||
|
tmpopts->createDB = 0;
|
||||||
|
tmpopts->cparams.dbname = dbidname->str;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* We'll have to create it */
|
||||||
|
tmpopts->createDB = 1;
|
||||||
|
tmpopts->cparams.dbname = connected_db;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Restore the single database. */
|
||||||
|
n_errors = restore_one_database(subdirpath, tmpopts, numWorkers, true);
|
||||||
|
|
||||||
|
n_errors_total += n_errors;
|
||||||
|
|
||||||
|
/* Print a summary of ignored errors during single database restore. */
|
||||||
|
if (n_errors)
|
||||||
|
pg_log_warning("errors ignored on database \"%s\" restore: %d", dbidname->str, n_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log number of processed databases. */
|
||||||
|
pg_log_info("number of restored databases is %d", num_db_restore);
|
||||||
|
|
||||||
|
/* Free dbname and dboid list. */
|
||||||
|
simple_ptr_list_destroy(&dbname_oid_list);
|
||||||
|
|
||||||
|
pg_free(original_opts);
|
||||||
|
pg_free(tmpopts);
|
||||||
|
|
||||||
|
return n_errors_total;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -244,4 +244,59 @@ command_fails_like(
|
||||||
'pg_dumpall: option --exclude-database cannot be used together with -g/--globals-only'
|
'pg_dumpall: option --exclude-database cannot be used together with -g/--globals-only'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_dumpall', '--format', 'x' ],
|
||||||
|
qr/\Qpg_dumpall: error: unrecognized output format "x";\E/,
|
||||||
|
'pg_dumpall: unrecognized output format');
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_dumpall', '--format', 'd', '--restrict-key=uu', '-f dumpfile' ],
|
||||||
|
qr/\Qpg_dumpall: error: option --restrict-key can only be used with --format=plain\E/,
|
||||||
|
'pg_dumpall: --restrict-key can only be used with plain dump format');
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_dumpall', '--format', 'd', '--globals-only', '--clean', '-f', 'dumpfile' ],
|
||||||
|
qr/\Qpg_dumpall: error: options --clean and -g\/--globals-only cannot be used together in non-text dump\E/,
|
||||||
|
'pg_dumpall: --clean and -g/--globals-only cannot be used together in non-text dump');
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_dumpall', '--format', 'd' ],
|
||||||
|
qr/\Qpg_dumpall: error: option -F\/--format=d|c|t requires option -f\/--file\E/,
|
||||||
|
'pg_dumpall: non-plain format requires --file option');
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_restore', '--exclude-database=foo', '--globals-only', '-d', 'xxx' ],
|
||||||
|
qr/\Qpg_restore: error: option --exclude-database cannot be used together with -g\/--globals-only\E/,
|
||||||
|
'pg_restore: option --exclude-database cannot be used together with -g/--globals-only'
|
||||||
|
);
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_restore', '--data-only', '--globals-only', '-d', 'xxx' ],
|
||||||
|
qr/\Qpg_restore: error: options -a\/--data-only and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'pg_restore: error: options -a/--data-only and -g/--globals-only cannot be used together'
|
||||||
|
);
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_restore', '--schema-only', '--globals-only', '-d', 'xxx' ],
|
||||||
|
qr/\Qpg_restore: error: options -s\/--schema-only and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'pg_restore: error: options -s/--schema-only and -g/--globals-only cannot be used together'
|
||||||
|
);
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_restore', '--statistics-only', '--globals-only', '-d', 'xxx' ],
|
||||||
|
qr/\Qpg_restore: error: options --statistics-only and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'pg_restore: error: options --statistics-only and -g/--globals-only cannot be used together'
|
||||||
|
);
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_restore', '--exclude-database=foo', '-d', 'xxx', 'dumpdir' ],
|
||||||
|
qr/\Qpg_restore: error: option --exclude-database can be used only when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When option --exclude-database is used in pg_restore with dump of pg_dump'
|
||||||
|
);
|
||||||
|
|
||||||
|
command_fails_like(
|
||||||
|
[ 'pg_restore', '--globals-only', '-d', 'xxx', 'dumpdir' ],
|
||||||
|
qr/\Qpg_restore: error: option -g\/--globals-only can be used only when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When option --globals-only is used in pg_restore with the dump of pg_dump'
|
||||||
|
);
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|
|
||||||
639
src/bin/pg_dump/t/007_pg_dumpall.pl
Normal file
639
src/bin/pg_dump/t/007_pg_dumpall.pl
Normal file
|
|
@ -0,0 +1,639 @@
|
||||||
|
# Copyright (c) 2021-2026, 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 $!";
|
||||||
|
|
||||||
|
# escape tablespace locations on Windows.
|
||||||
|
my $tablespace2_orig = $tablespace2;
|
||||||
|
$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 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 tbl2 OWNER tap LOCATION \E(?:E)?\Q'$tablespace2_orig';\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 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
|
||||||
|
# report an error 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: option -C\/--create must be specified when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When -C is not used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when \l/--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');
|
||||||
|
|
||||||
|
# report an error when -L/--use-list option is used with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--use-list' => 'use',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: option -L\/--use-list cannot be used when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When -L/--use-list is used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when --strict-names option is used with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--strict-names',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: option --strict-names cannot be used when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When --strict-names is used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when --clean and -g/--globals-only are used in pg_restore with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--clean',
|
||||||
|
'--globals-only',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options --clean and -g\/--globals-only cannot be used together when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When --clean and -g/--globals-only are used in pg_restore with dump of pg_dumpall'
|
||||||
|
);
|
||||||
|
|
||||||
|
# report an error when non-exist database is given with -d option
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'-d' => 'dbpq',
|
||||||
|
],
|
||||||
|
qr/\QFATAL: database "dbpq" does not exist\E/,
|
||||||
|
'When non-existent database is given with -d option in pg_restore with dump of pg_dumpall'
|
||||||
|
);
|
||||||
|
|
||||||
|
# report an error when --no-schema is used with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--no-schema',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: option --no-schema cannot be used when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When --no-schema is used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when --data-only is used with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--data-only',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: option -a\/--data-only cannot be used when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When --data-only is used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when --statistics-only is used with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--statistics-only',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: option --statistics-only cannot be used when restoring an archive created by pg_dumpall\E/,
|
||||||
|
'When --statistics-only is used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when --section excludes pre-data with dump of pg_dumpall
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--section' => 'post-data',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: option --section cannot exclude --pre-data when restoring a pg_dumpall archive\E/,
|
||||||
|
'When --section=post-data is used in pg_restore with dump of pg_dumpall');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --data-only are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--data-only',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options -a\/--data-only and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --data-only are used together');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --schema-only are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--schema-only',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options -s\/--schema-only and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --schema-only are used together');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --statistics-only are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--statistics-only',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options --statistics-only and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --statistics-only are used together');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --statistics are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--statistics',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options --statistics and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --statistics are used together');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --exit-on-error are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--exit-on-error',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options --exit-on-error and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --exit-on-error are used together');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --single-transaction are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--single-transaction',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options --single-transaction and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --single-transaction are used together');
|
||||||
|
|
||||||
|
# report an error when --globals-only and --transaction-size are used together
|
||||||
|
$node->command_fails_like(
|
||||||
|
[
|
||||||
|
'pg_restore',
|
||||||
|
"$tempdir/format_custom", '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--globals-only',
|
||||||
|
'--transaction-size' => '100',
|
||||||
|
'--file' => "$tempdir/error_test.sql",
|
||||||
|
],
|
||||||
|
qr/\Qpg_restore: error: options --transaction-size and -g\/--globals-only cannot be used together\E/,
|
||||||
|
'When --globals-only and --transaction-size are used together');
|
||||||
|
|
||||||
|
# verify map.dat preamble exists
|
||||||
|
my $map_dat_content = slurp_file("$tempdir/format_directory/map.dat");
|
||||||
|
like(
|
||||||
|
$map_dat_content,
|
||||||
|
qr/^# map\.dat\n.*# This file maps oids to database names/ms,
|
||||||
|
'map.dat contains expected preamble');
|
||||||
|
|
||||||
|
# verify commenting out a line in map.dat skips that database
|
||||||
|
$node->safe_psql($run_db, 'CREATE DATABASE comment_test_db;
|
||||||
|
\c comment_test_db
|
||||||
|
CREATE TABLE comment_test_table (id int);');
|
||||||
|
|
||||||
|
$node->command_ok(
|
||||||
|
[
|
||||||
|
'pg_dumpall',
|
||||||
|
'--format' => 'directory',
|
||||||
|
'--file' => "$tempdir/comment_test",
|
||||||
|
],
|
||||||
|
'pg_dumpall for comment test');
|
||||||
|
|
||||||
|
# Modify map.dat to comment out the comment_test_db entry
|
||||||
|
my $map_content = slurp_file("$tempdir/comment_test/map.dat");
|
||||||
|
$map_content =~ s/^(\d+ comment_test_db)$/# $1/m;
|
||||||
|
open(my $fh, '>', "$tempdir/comment_test/map.dat")
|
||||||
|
or die "Cannot open map.dat: $!";
|
||||||
|
print $fh $map_content;
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
# Create a target node and restore - commented db should be skipped
|
||||||
|
my $target_comment = PostgreSQL::Test::Cluster->new("target_comment");
|
||||||
|
$target_comment->init;
|
||||||
|
$target_comment->start;
|
||||||
|
|
||||||
|
$node->command_ok(
|
||||||
|
[
|
||||||
|
'pg_restore', '-C',
|
||||||
|
'--format' => 'directory',
|
||||||
|
'--file' => "$tempdir/comment_test_restore.sql",
|
||||||
|
'--host', $target_comment->host,
|
||||||
|
'--port', $target_comment->port,
|
||||||
|
"$tempdir/comment_test",
|
||||||
|
],
|
||||||
|
'pg_restore with commented out database in map.dat');
|
||||||
|
|
||||||
|
my $restore_output = slurp_file("$tempdir/comment_test_restore.sql");
|
||||||
|
unlike(
|
||||||
|
$restore_output,
|
||||||
|
qr/CREATE DATABASE comment_test_db/,
|
||||||
|
'commented out database in map.dat is not restored');
|
||||||
|
|
||||||
|
# Test that --clean implies --if-exists for pg_dumpall archives
|
||||||
|
$node->command_ok(
|
||||||
|
[
|
||||||
|
'pg_restore', '-C',
|
||||||
|
'--format' => 'custom',
|
||||||
|
'--clean',
|
||||||
|
'--file' => "$tempdir/clean_test.sql",
|
||||||
|
"$tempdir/format_custom",
|
||||||
|
],
|
||||||
|
'pg_restore with --clean on pg_dumpall archive');
|
||||||
|
|
||||||
|
my $clean_output = slurp_file("$tempdir/clean_test.sql");
|
||||||
|
like(
|
||||||
|
$clean_output,
|
||||||
|
qr/DROP ROLE IF EXISTS/,
|
||||||
|
'--clean implies --if-exists: DROP ROLE IF EXISTS in output');
|
||||||
|
|
||||||
|
$node->stop('fast');
|
||||||
|
|
||||||
|
done_testing();
|
||||||
|
|
@ -600,6 +600,7 @@ CustomScanMethods
|
||||||
CustomScanState
|
CustomScanState
|
||||||
CycleCtr
|
CycleCtr
|
||||||
DBState
|
DBState
|
||||||
|
DbOidName
|
||||||
DCHCacheEntry
|
DCHCacheEntry
|
||||||
DEADLOCK_INFO
|
DEADLOCK_INFO
|
||||||
DECountItem
|
DECountItem
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue