]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add non-text output formats to pg_dumpall
authorAndrew Dunstan <andrew@dunslane.net>
Mon, 23 Feb 2026 20:08:55 +0000 (15:08 -0500)
committerAndrew Dunstan <andrew@dunslane.net>
Thu, 26 Feb 2026 13:29:56 +0000 (08:29 -0500)
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

14 files changed:
doc/src/sgml/ref/pg_dumpall.sgml
doc/src/sgml/ref/pg_restore.sgml
src/bin/pg_dump/meson.build
src/bin/pg_dump/parallel.c
src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_backup_archiver.h
src/bin/pg_dump/pg_backup_tar.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dumpall.c
src/bin/pg_dump/pg_restore.c
src/bin/pg_dump/t/001_basic.pl
src/bin/pg_dump/t/007_pg_dumpall.pl [new file with mode: 0644]
src/tools/pgindent/typedefs.list

index 8834b7ec141ea120180d52345bab318baefda2a8..49e5c99b09eb158959874533fdb1a21fe541fe9b 100644 (file)
@@ -16,7 +16,10 @@ PostgreSQL documentation
 
  <refnamediv>
   <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>
 
  <refsynopsisdiv>
@@ -33,7 +36,7 @@ PostgreSQL documentation
   <para>
    <application>pg_dumpall</application> is a utility for writing out
    (<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
    linkend="app-psql"/> to restore the databases.  It does this by
    calling <xref linkend="app-pgdump"/> for each database in the cluster.
@@ -52,11 +55,16 @@ PostgreSQL documentation
   </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
    redirect it into a file.
   </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>
   <application>pg_dumpall</application> needs to connect several
   times to the <productname>PostgreSQL</productname> server (once per
@@ -131,16 +139,93 @@ PostgreSQL documentation
        <para>
         Send output to the specified file.  If this is omitted, the
         standard output is used.
+        This option can only be omitted when <option>--format</option> is plain.
        </para>
       </listitem>
      </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>
       <term><option>-g</option></term>
       <term><option>--globals-only</option></term>
       <listitem>
        <para>
         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>
       </listitem>
      </varlistentry>
@@ -936,13 +1021,21 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
  <refsect1 id="app-pg-dumpall-ex">
   <title>Examples</title>
   <para>
-   To dump all databases:
-
+   To dump all databases in plain text format (the default):
 <screen>
 <prompt>$</prompt> <userinput>pg_dumpall &gt; db.out</userinput>
 </screen>
   </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>
    To restore database(s) from this file, you can use:
 <screen>
@@ -956,6 +1049,16 @@ exclude database <replaceable class="parameter">PATTERN</replaceable>
    the script will attempt to drop other databases immediately, and that
    will fail for the database you are connected to.
   </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>
index 420a308a7c7d9bfcf63c0f69250e0b38fbd9853c..4a21a08984092fe39e969ab0f4a2a024582bc006 100644 (file)
@@ -18,8 +18,9 @@ PostgreSQL documentation
   <refname>pg_restore</refname>
 
   <refpurpose>
-   restore a <productname>PostgreSQL</productname> database from an
-   archive file created by <application>pg_dump</application>
+   restore <productname>PostgreSQL</productname> databases from archives
+   created by <application>pg_dump</application> or
+   <application>pg_dumpall</application>
   </refpurpose>
  </refnamediv>
 
@@ -38,13 +39,14 @@ PostgreSQL documentation
 
   <para>
    <application>pg_restore</application> is a utility for restoring a
-   <productname>PostgreSQL</productname> database from an archive
-   created by <xref linkend="app-pgdump"/> in one of the non-plain-text
+   <productname>PostgreSQL</productname> database or cluster from an archive
+   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
-   database to the state it was in at the time it was saved.  The
-   archive files also allow <application>pg_restore</application> to
+   database or cluster to the state it was in at the time it was saved. The
+   archives also allow <application>pg_restore</application> to
    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.
   </para>
 
@@ -52,14 +54,34 @@ PostgreSQL documentation
    <application>pg_restore</application> can operate in two modes.
    If a database name is specified, <application>pg_restore</application>
    connects to that database and restores archive contents directly into
-   the database.  Otherwise, a script containing the SQL
-   commands necessary to rebuild the database is created and written
+   the database.
+   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
-   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
    <application>pg_dump</application> options.
   </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>
    Obviously, <application>pg_restore</application> cannot restore information
    that is not present in the archive file.  For instance, if the
@@ -130,6 +152,12 @@ PostgreSQL documentation
         ignorable error messages will be reported,
         unless <option>--if-exists</option> is also specified.
        </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>
      </varlistentry>
 
@@ -152,6 +180,8 @@ PostgreSQL documentation
         commands that mention this database.
         Access privileges for the database itself are also restored,
         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>
@@ -247,6 +277,28 @@ PostgreSQL documentation
       </listitem>
      </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>
       <term><option>-I <replaceable class="parameter">index</replaceable></option></term>
       <term><option>--index=<replaceable class="parameter">index</replaceable></option></term>
@@ -581,6 +633,28 @@ PostgreSQL documentation
       </listitem>
      </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>
       <term><option>--filter=<replaceable class="parameter">filename</replaceable></option></term>
       <listitem>
@@ -669,7 +743,9 @@ PostgreSQL documentation
         in <option>--clean</option> mode.  This suppresses <quote>does not
         exist</quote> errors that might otherwise be reported.  This
         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>
       </listitem>
      </varlistentry>
@@ -1125,6 +1201,27 @@ CREATE DATABASE foo WITH TEMPLATE template0;
      </para>
     </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>
   </para>
 
index 79bd5036841e06ffccf37c0e84ed6acd5dc7a262..7c9a475963b5ca83ed8558729f88301b7ada8233 100644 (file)
@@ -103,6 +103,7 @@ tests += {
       't/004_pg_dump_parallel.pl',
       't/005_pg_dump_filterfile.pl',
       't/006_pg_dump_compress.pl',
+         't/007_pg_dumpall.pl',
       't/010_dump_connstr.pl',
     ],
   },
index 56cb2c1f32dce1af5990c722e431970a2b23e9e1..a28561fbd84e919e41b90a7ecf1920a3a1628888 100644 (file)
@@ -333,6 +333,20 @@ on_exit_close_archive(Archive *AHX)
        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
  * worker processes cleanly.
index 2f8d9799c30c09fad7cdfceb4f3df5d87b7caa7d..fda912ba0a91fe846bea74578bf14a3d9bbf9f5a 100644 (file)
@@ -313,7 +313,7 @@ extern void SetArchiveOptions(Archive *AH, DumpOptions *dopt, RestoreOptions *ro
 
 extern void ProcessArchiveRestoreOptions(Archive *AHX);
 
-extern void RestoreArchive(Archive *AHX);
+extern void RestoreArchive(Archive *AHX, bool append_data);
 
 /* Open an existing archive */
 extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
index 7afcc0859c868ca480527e7668cbd078c8a46746..df8a69d3b79d8979b0c548be77245b665d15beb6 100644 (file)
@@ -86,7 +86,7 @@ static int    RestoringToDB(ArchiveHandle *AH);
 static void dump_lo_buf(ArchiveHandle *AH);
 static void dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim);
 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 void RestoreOutput(ArchiveHandle *AH, CompressFileHandle *savedOutput);
 
@@ -339,9 +339,14 @@ ProcessArchiveRestoreOptions(Archive *AHX)
                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
-RestoreArchive(Archive *AHX)
+RestoreArchive(Archive *AHX, bool append_data)
 {
        ArchiveHandle *AH = (ArchiveHandle *) AHX;
        RestoreOptions *ropt = AH->public.ropt;
@@ -458,7 +463,7 @@ RestoreArchive(Archive *AHX)
         */
        sav = SaveOutput(AH);
        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");
 
@@ -761,6 +766,19 @@ RestoreArchive(Archive *AHX)
                        if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0)
                                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))
                        {
                                case RESTORE_PASS_MAIN:
@@ -1316,7 +1334,7 @@ PrintTOCSummary(Archive *AHX)
 
        sav = SaveOutput(AH);
        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,
                                 localtime(&AH->createDate)) == 0)
@@ -1691,11 +1709,15 @@ archprintf(Archive *AH, const char *fmt,...)
 
 /*******************************
  * 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
 SetOutput(ArchiveHandle *AH, const char *filename,
-                 const pg_compress_specification compression_spec)
+                 const pg_compress_specification compression_spec,
+                 bool append_data)
 {
        CompressFileHandle *CFH;
        const char *mode;
@@ -1715,7 +1737,7 @@ SetOutput(ArchiveHandle *AH, const char *filename,
        else
                fn = fileno(stdout);
 
-       if (AH->mode == archModeAppend)
+       if (append_data || AH->mode == archModeAppend)
                mode = PG_BINARY_A;
        else
                mode = PG_BINARY_W;
@@ -2391,7 +2413,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 
        /* initialize for backwards compatible string processing */
        AH->public.encoding = 0;        /* PG_SQL_ASCII */
-       AH->public.std_strings = false;
+       AH->public.std_strings = true;
 
        /* sql error handling */
        AH->public.exit_on_error = true;
@@ -3027,6 +3049,16 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
                        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.
         */
@@ -3062,6 +3094,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
                if (ropt->no_subscriptions &&
                        strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 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 &&
                        strncmp(te->tag, "SUBSCRIPTION", strlen("SUBSCRIPTION")) == 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 */
@@ -3865,6 +3913,9 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
        else if (strcmp(type, "CAST") == 0 ||
                         strcmp(type, "CHECK 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, "DEFAULT") == 0 ||
                         strcmp(type, "FK CONSTRAINT") == 0 ||
index 325b53fc9bd4bb0514259d467a666e0c93696142..365073b3eae459008733541adced3924b9d5cbd5 100644 (file)
@@ -394,6 +394,7 @@ struct _tocEntry
 
 extern int     parallel_restore(ArchiveHandle *AH, TocEntry *te);
 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);
 
index b5ba3b46dd999f5578caed6d2237388d2078c72c..d94d0de2a5d177829134115047c970523ea6b97b 100644 (file)
@@ -826,7 +826,7 @@ _CloseArchive(ArchiveHandle *AH)
                savVerbose = AH->public.verbose;
                AH->public.verbose = 0;
 
-               RestoreArchive((Archive *) AH);
+               RestoreArchive((Archive *) AH, false);
 
                SetArchiveOptions((Archive *) AH, savDopt, savRopt);
 
index 46a6794a57fc6abe414a2147411fa9b3f23f98f5..dd8adef0a3e4905626b6e31264235a6154b0f882 100644 (file)
@@ -1292,7 +1292,7 @@ main(int argc, char **argv)
         * right now.
         */
        if (plainText)
-               RestoreArchive(fout);
+               RestoreArchive(fout, false);
 
        CloseArchive(fout);
 
index 98389d2034cdbdaf69b5a6458486f33b1303ca91..65f8e3a41f14037ecbdfa8b29dcc27606ffad91b 100644 (file)
@@ -1,13 +1,20 @@
 /*-------------------------------------------------------------------------
  *
  * pg_dumpall.c
+ *       pg_dumpall dumps all databases and global objects (roles and
+ *       tablespaces) from a PostgreSQL cluster.
+ *
+ *       For text format output, globals are written directly and pg_dump is
+ *       invoked for each database, with all output going to stdout or a file.
+ *
+ *       For non-text formats (custom, directory, tar), a directory is created
+ *       containing a toc.glo file with global objects, a map.dat file mapping
+ *       database OIDs to names, and a databases/ subdirectory with individual
+ *       pg_dump archives for each database.
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * pg_dumpall forces all pg_dump output to be text, since it also outputs
- * text into the same output stream.
- *
  * src/bin/pg_dump/pg_dumpall.c
  *
  *-------------------------------------------------------------------------
@@ -30,6 +37,7 @@
 #include "fe_utils/string_utils.h"
 #include "filter.h"
 #include "getopt_long.h"
+#include "pg_backup_archiver.h"
 
 /* version string we expect back from pg_dump */
 #define PGDUMP_VERSIONSTR "pg_dump (PostgreSQL) " PG_VERSION "\n"
@@ -67,15 +75,19 @@ static void dropDBs(PGconn *conn);
 static void dumpUserConfig(PGconn *conn, const char *username);
 static void dumpDatabases(PGconn *conn);
 static void dumpTimestamp(const char *msg);
-static int     runPgDump(const char *dbname, const char *create_opts);
+static int     runPgDump(const char *dbname, const char *create_opts, char *dbfile);
 static void buildShSecLabels(PGconn *conn,
                                                         const char *catalog_name, Oid objectId,
                                                         const char *objtype, const char *objname,
                                                         PQExpBuffer buffer);
 static void executeCommand(PGconn *conn, const char *query);
+static void check_for_invalid_global_names(PGconn *conn,
+                                                                                  SimpleStringList *database_exclude_names);
 static void expand_dbname_patterns(PGconn *conn, SimpleStringList *patterns,
                                                                   SimpleStringList *names);
 static void read_dumpall_filters(const char *filename, SimpleStringList *pattern);
+static ArchiveFormat parseDumpFormat(const char *format);
+static int     createDumpId(void);
 
 static char pg_dump_bin[MAXPGPATH];
 static PQExpBuffer pgdumpopts;
@@ -123,6 +135,10 @@ static SimpleStringList database_exclude_patterns = {NULL, NULL};
 static SimpleStringList database_exclude_names = {NULL, NULL};
 
 static char *restrict_key;
+static Archive *fout = NULL;
+static int     dumpIdVal = 0;
+static ArchiveFormat archDumpFormat = archNull;
+static const CatalogId nilCatalogId = {0, 0};
 
 int
 main(int argc, char *argv[])
@@ -148,6 +164,7 @@ main(int argc, char *argv[])
                {"password", no_argument, NULL, 'W'},
                {"no-privileges", no_argument, NULL, 'x'},
                {"no-acl", no_argument, NULL, 'x'},
+               {"format", required_argument, NULL, 'F'},
 
                /*
                 * the following options don't have an equivalent short option letter
@@ -197,6 +214,7 @@ main(int argc, char *argv[])
        char       *pgdb = NULL;
        char       *use_role = NULL;
        const char *dumpencoding = NULL;
+       const char *format_name = "p";
        trivalue        prompt_password = TRI_DEFAULT;
        bool            data_only = false;
        bool            globals_only = false;
@@ -207,6 +225,7 @@ main(int argc, char *argv[])
        int                     c,
                                ret;
        int                     optindex;
+       DumpOptions dopt;
 
        pg_logging_init(argv[0]);
        pg_logging_set_level(PG_LOG_WARNING);
@@ -244,8 +263,9 @@ main(int argc, char *argv[])
        }
 
        pgdumpopts = createPQExpBuffer();
+       InitDumpOptions(&dopt);
 
-       while ((c = getopt_long(argc, argv, "acd:E:f:gh:l:Op:rsS:tU:vwWx", long_options, &optindex)) != -1)
+       while ((c = getopt_long(argc, argv, "acd:E:f:F:gh:l:Op:rsS:tU:vwWx", long_options, &optindex)) != -1)
        {
                switch (c)
                {
@@ -273,7 +293,9 @@ main(int argc, char *argv[])
                                appendPQExpBufferStr(pgdumpopts, " -f ");
                                appendShellString(pgdumpopts, filename);
                                break;
-
+                       case 'F':
+                               format_name = pg_strdup(optarg);
+                               break;
                        case 'g':
                                globals_only = true;
                                break;
@@ -313,6 +335,7 @@ main(int argc, char *argv[])
 
                        case 'U':
                                pguser = pg_strdup(optarg);
+                               dopt.cparams.username = pg_strdup(optarg);
                                break;
 
                        case 'v':
@@ -434,6 +457,32 @@ main(int argc, char *argv[])
                exit_nicely(1);
        }
 
+       /* Get format for dump. */
+       archDumpFormat = parseDumpFormat(format_name);
+
+       /*
+        * If a non-plain format is specified, a file name is also required as the
+        * path to the main directory.
+        */
+       if (archDumpFormat != archNull &&
+               (!filename || strcmp(filename, "") == 0))
+       {
+               pg_log_error("option %s=d|c|t requires option %s",
+                                        "-F/--format", "-f/--file");
+               pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+               exit_nicely(1);
+       }
+
+       /* restrict-key is only supported with --format=plain */
+       if (archDumpFormat != archNull && restrict_key)
+               pg_fatal("option %s can only be used with %s=plain",
+                                "--restrict-key", "--format");
+
+       /* --clean and -g/--globals-only cannot be used together in non-text dump */
+       if (archDumpFormat != archNull && output_clean && globals_only)
+               pg_fatal("options %s and %s cannot be used together in non-text dump",
+                                "--clean", "-g/--globals-only");
+
        /*
         * If password values are not required in the dump, switch to using
         * pg_roles which is equally useful, just more likely to have unrestricted
@@ -494,6 +543,27 @@ main(int argc, char *argv[])
        if (sequence_data)
                appendPQExpBufferStr(pgdumpopts, " --sequence-data");
 
+       /*
+        * Open the output file if required, otherwise use stdout.  If required,
+        * then create new directory.
+        */
+       if (archDumpFormat != archNull)
+       {
+               Assert(filename);
+
+               /* Create new directory or accept the empty existing directory. */
+               create_or_open_dir(filename);
+       }
+       else if (filename)
+       {
+               OPF = fopen(filename, PG_BINARY_W);
+               if (!OPF)
+                       pg_fatal("could not open output file \"%s\": %m",
+                                        filename);
+       }
+       else
+               OPF = stdout;
+
        /*
         * If you don't provide a restrict key, one will be appointed for you.
         */
@@ -543,19 +613,6 @@ main(int argc, char *argv[])
        expand_dbname_patterns(conn, &database_exclude_patterns,
                                                   &database_exclude_names);
 
-       /*
-        * Open the output file if required, otherwise use stdout
-        */
-       if (filename)
-       {
-               OPF = fopen(filename, PG_BINARY_W);
-               if (!OPF)
-                       pg_fatal("could not open output file \"%s\": %m",
-                                        filename);
-       }
-       else
-               OPF = stdout;
-
        /*
         * Set the client encoding if requested.
         */
@@ -593,35 +650,121 @@ main(int argc, char *argv[])
        if (quote_all_identifiers)
                executeCommand(conn, "SET quote_all_identifiers = true");
 
-       fprintf(OPF, "--\n-- PostgreSQL database cluster dump\n--\n\n");
-       if (verbose)
-               dumpTimestamp("Started on");
+       /* create a archive file for global commands. */
+       if (archDumpFormat != archNull)
+       {
+               PQExpBuffer qry = createPQExpBuffer();
+               char            global_path[MAXPGPATH];
+               const char *encname;
+               pg_compress_specification compression_spec = {0};
 
-       /*
-        * Enter restricted mode to block any unexpected psql meta-commands.  A
-        * malicious source might try to inject a variety of things via bogus
-        * responses to queries.  While we cannot prevent such sources from
-        * affecting the destination at restore time, we can block psql
-        * meta-commands so that the client machine that runs psql with the dump
-        * output remains unaffected.
-        */
-       fprintf(OPF, "\\restrict %s\n\n", restrict_key);
+               /*
+                * Check that no global object names contain newlines or carriage
+                * returns, which would break the map.dat file format.  This is only
+                * needed for servers older than v19, which started prohibiting such
+                * names.
+                */
+               if (server_version < 190000)
+                       check_for_invalid_global_names(conn, &database_exclude_names);
 
-       /*
-        * We used to emit \connect postgres here, but that served no purpose
-        * other than to break things for installations without a postgres
-        * database.  Everything we're restoring here is a global, so whichever
-        * database we're connected to at the moment is fine.
-        */
+               /* Set file path for global sql commands. */
+               snprintf(global_path, MAXPGPATH, "%s/toc.glo", filename);
+
+               /* Open the output file */
+               fout = CreateArchive(global_path, archCustom, compression_spec,
+                                                        dosync, archModeWrite, NULL, DATA_DIR_SYNC_METHOD_FSYNC);
+
+               /* Make dump options accessible right away */
+               SetArchiveOptions(fout, &dopt, NULL);
 
-       /* Restore will need to write to the target cluster */
-       fprintf(OPF, "SET default_transaction_read_only = off;\n\n");
+               ((ArchiveHandle *) fout)->connection = conn;
+               ((ArchiveHandle *) fout)->public.numWorkers = 1;
 
-       /* Replicate encoding and standard_conforming_strings in output */
-       fprintf(OPF, "SET client_encoding = '%s';\n",
-                       pg_encoding_to_char(encoding));
-       fprintf(OPF, "SET standard_conforming_strings = on;\n");
-       fprintf(OPF, "\n");
+               /* Register the cleanup hook */
+               on_exit_close_archive(fout);
+
+               /* Let the archiver know how noisy to be */
+               fout->verbose = verbose;
+
+               /*
+                * We allow the server to be back to 9.2, and up to any minor release
+                * of our own major version.  (See also version check in
+                * pg_dumpall.c.)
+                */
+               fout->minRemoteVersion = 90200;
+               fout->maxRemoteVersion = (PG_VERSION_NUM / 100) * 100 + 99;
+               fout->numWorkers = 1;
+
+               /* Dump default_transaction_read_only. */
+               appendPQExpBufferStr(qry, "SET default_transaction_read_only = off;\n\n");
+               ArchiveEntry(fout,
+                                        nilCatalogId,  /* catalog ID */
+                                        createDumpId(),        /* dump ID */
+                                        ARCHIVE_OPTS(.tag = "default_transaction_read_only",
+                                                                 .description = "default_transaction_read_only",
+                                                                 .section = SECTION_PRE_DATA,
+                                                                 .createStmt = qry->data));
+               resetPQExpBuffer(qry);
+
+               /* Put the correct encoding into the archive */
+               encname = pg_encoding_to_char(encoding);
+
+               appendPQExpBufferStr(qry, "SET client_encoding = ");
+               appendStringLiteralAH(qry, encname, fout);
+               appendPQExpBufferStr(qry, ";\n");
+               ArchiveEntry(fout,
+                                        nilCatalogId,  /* catalog ID */
+                                        createDumpId(),        /* dump ID */
+                                        ARCHIVE_OPTS(.tag = "client_encoding",
+                                                                 .description = "client_encoding",
+                                                                 .section = SECTION_PRE_DATA,
+                                                                 .createStmt = qry->data));
+               resetPQExpBuffer(qry);
+
+               /* Put the correct escape string behavior into the archive. */
+               appendPQExpBuffer(qry, "SET standard_conforming_strings = 'on';\n");
+               ArchiveEntry(fout,
+                                        nilCatalogId,  /* catalog ID */
+                                        createDumpId(),        /* dump ID */
+                                        ARCHIVE_OPTS(.tag = "standard_conforming_strings",
+                                                                 .description = "standard_conforming_strings",
+                                                                 .section = SECTION_PRE_DATA,
+                                                                 .createStmt = qry->data));
+               destroyPQExpBuffer(qry);
+       }
+       else
+       {
+               fprintf(OPF, "--\n-- PostgreSQL database cluster dump\n--\n\n");
+
+               if (verbose)
+                       dumpTimestamp("Started on");
+
+               /*
+                * Enter restricted mode to block any unexpected psql meta-commands. A
+                * malicious source might try to inject a variety of things via bogus
+                * responses to queries.  While we cannot prevent such sources from
+                * affecting the destination at restore time, we can block psql
+                * meta-commands so that the client machine that runs psql with the
+                * dump output remains unaffected.
+                */
+               fprintf(OPF, "\\restrict %s\n\n", restrict_key);
+
+               /*
+                * We used to emit \connect postgres here, but that served no purpose
+                * other than to break things for installations without a postgres
+                * database.  Everything we're restoring here is a global, so
+                * whichever database we're connected to at the moment is fine.
+                */
+
+               /* Restore will need to write to the target cluster */
+               fprintf(OPF, "SET default_transaction_read_only = off;\n\n");
+
+               /* Replicate encoding and standard_conforming_strings in output */
+               fprintf(OPF, "SET client_encoding = '%s';\n",
+                               pg_encoding_to_char(encoding));
+               fprintf(OPF, "SET standard_conforming_strings = on;\n");
+               fprintf(OPF, "\n");
+       }
 
        if (!data_only && !statistics_only && !no_schema)
        {
@@ -630,8 +773,14 @@ main(int argc, char *argv[])
                 * dependency analysis because databases never depend on each other,
                 * and tablespaces never depend on each other.  Roles could have
                 * grants to each other, but DROP ROLE will clean those up silently.
+                *
+                * For non-text formats, pg_dumpall unconditionally process --clean
+                * option. In contrast, pg_restore only applies it if the user
+                * explicitly provides the flag.  This discrepancy resolves corner
+                * cases where pg_restore requires cleanup instructions that may be
+                * missing from a standard pg_dumpall output.
                 */
-               if (output_clean)
+               if (output_clean || archDumpFormat != archNull)
                {
                        if (!globals_only && !roles_only && !tablespaces_only)
                                dropDBs(conn);
@@ -665,28 +814,45 @@ main(int argc, char *argv[])
                        dumpTablespaces(conn);
        }
 
-       /*
-        * Exit restricted mode just before dumping the databases.  pg_dump will
-        * handle entering restricted mode again as appropriate.
-        */
-       fprintf(OPF, "\\unrestrict %s\n\n", restrict_key);
+       if (archDumpFormat == archNull)
+       {
+               /*
+                * Exit restricted mode just before dumping the databases.  pg_dump
+                * will handle entering restricted mode again as appropriate.
+                */
+               fprintf(OPF, "\\unrestrict %s\n\n", restrict_key);
+       }
 
        if (!globals_only && !roles_only && !tablespaces_only)
                dumpDatabases(conn);
 
-       PQfinish(conn);
+       if (archDumpFormat == archNull)
+       {
+               PQfinish(conn);
 
-       if (verbose)
-               dumpTimestamp("Completed on");
-       fprintf(OPF, "--\n-- PostgreSQL database cluster dump complete\n--\n\n");
+               if (verbose)
+                       dumpTimestamp("Completed on");
+               fprintf(OPF, "--\n-- PostgreSQL database cluster dump complete\n--\n\n");
 
-       if (filename)
+               if (filename)
+               {
+                       fclose(OPF);
+
+                       /* sync the resulting file, errors are not fatal */
+                       if (dosync)
+                               (void) fsync_fname(filename, false);
+               }
+       }
+       else
        {
-               fclose(OPF);
+               RestoreOptions *ropt;
+
+               ropt = NewRestoreOptions();
+               SetArchiveOptions(fout, &dopt, ropt);
 
-               /* sync the resulting file, errors are not fatal */
-               if (dosync)
-                       (void) fsync_fname(filename, false);
+               /* Mark which entries should be output */
+               ProcessArchiveRestoreOptions(fout);
+               CloseArchive(fout);
        }
 
        exit_nicely(0);
@@ -696,12 +862,14 @@ main(int argc, char *argv[])
 static void
 help(void)
 {
-       printf(_("%s exports a PostgreSQL database cluster as an SQL script.\n\n"), progname);
+       printf(_("%s exports a PostgreSQL database cluster as an SQL script or to other formats.\n\n"), progname);
        printf(_("Usage:\n"));
        printf(_("  %s [OPTION]...\n"), progname);
 
        printf(_("\nGeneral options:\n"));
        printf(_("  -f, --file=FILENAME          output file name\n"));
+       printf(_("  -F, --format=c|d|t|p         output file format (custom, directory, tar,\n"
+                        "                               plain text (default))\n"));
        printf(_("  -v, --verbose                verbose mode\n"));
        printf(_("  -V, --version                output version information, then exit\n"));
        printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
@@ -796,24 +964,45 @@ dropRoles(PGconn *conn)
 
        i_rolname = PQfnumber(res, "rolname");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Drop roles\n--\n\n");
 
        for (i = 0; i < PQntuples(res); i++)
        {
                const char *rolename;
+               PQExpBuffer delQry = createPQExpBuffer();
 
                rolename = PQgetvalue(res, i, i_rolname);
 
-               fprintf(OPF, "DROP ROLE %s%s;\n",
-                               if_exists ? "IF EXISTS " : "",
-                               fmtId(rolename));
+               if (archDumpFormat == archNull)
+               {
+                       appendPQExpBuffer(delQry, "DROP ROLE %s%s;\n",
+                                                         if_exists ? "IF EXISTS " : "",
+                                                         fmtId(rolename));
+                       fprintf(OPF, "%s", delQry->data);
+               }
+               else
+               {
+                       appendPQExpBuffer(delQry, "DROP ROLE IF EXISTS %s;\n",
+                                                         fmtId(rolename));
+
+                       ArchiveEntry(fout,
+                                                nilCatalogId,  /* catalog ID */
+                                                createDumpId(),        /* dump ID */
+                                                ARCHIVE_OPTS(.tag = psprintf("ROLE %s", fmtId(rolename)),
+                                                                         .description = "DROP_GLOBAL",
+                                                                         .section = SECTION_PRE_DATA,
+                                                                         .createStmt = delQry->data));
+               }
+
+               destroyPQExpBuffer(delQry);
        }
 
        PQclear(res);
        destroyPQExpBuffer(buf);
 
-       fprintf(OPF, "\n\n");
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 }
 
 /*
@@ -823,6 +1012,8 @@ static void
 dumpRoles(PGconn *conn)
 {
        PQExpBuffer buf = createPQExpBuffer();
+       PQExpBuffer comment_buf = createPQExpBuffer();
+       PQExpBuffer seclabel_buf = createPQExpBuffer();
        PGresult   *res;
        int                     i_oid,
                                i_rolname,
@@ -894,7 +1085,7 @@ dumpRoles(PGconn *conn)
        i_rolcomment = PQfnumber(res, "rolcomment");
        i_is_current_user = PQfnumber(res, "is_current_user");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Roles\n--\n\n");
 
        for (i = 0; i < PQntuples(res); i++)
@@ -913,6 +1104,8 @@ dumpRoles(PGconn *conn)
                }
 
                resetPQExpBuffer(buf);
+               resetPQExpBuffer(comment_buf);
+               resetPQExpBuffer(seclabel_buf);
 
                if (binary_upgrade)
                {
@@ -989,17 +1182,53 @@ dumpRoles(PGconn *conn)
 
                if (!no_comments && !PQgetisnull(res, i, i_rolcomment))
                {
-                       appendPQExpBuffer(buf, "COMMENT ON ROLE %s IS ", fmtId(rolename));
-                       appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolcomment), conn);
-                       appendPQExpBufferStr(buf, ";\n");
+                       appendPQExpBuffer(comment_buf, "COMMENT ON ROLE %s IS ", fmtId(rolename));
+                       appendStringLiteralConn(comment_buf, PQgetvalue(res, i, i_rolcomment), conn);
+                       appendPQExpBufferStr(comment_buf, ";\n");
                }
 
                if (!no_security_labels)
                        buildShSecLabels(conn, "pg_authid", auth_oid,
                                                         "ROLE", rolename,
-                                                        buf);
+                                                        seclabel_buf);
+
+               if (archDumpFormat == archNull)
+               {
+                       fprintf(OPF, "%s", buf->data);
+                       fprintf(OPF, "%s", comment_buf->data);
 
-               fprintf(OPF, "%s", buf->data);
+                       if (seclabel_buf->data[0] != '\0')
+                               fprintf(OPF, "%s", seclabel_buf->data);
+               }
+               else
+               {
+                       char       *tag = psprintf("ROLE %s", fmtId(rolename));
+
+                       ArchiveEntry(fout,
+                                                nilCatalogId,  /* catalog ID */
+                                                createDumpId(),        /* dump ID */
+                                                ARCHIVE_OPTS(.tag = tag,
+                                                                         .description = "ROLE",
+                                                                         .section = SECTION_PRE_DATA,
+                                                                         .createStmt = buf->data));
+                       if (comment_buf->data[0] != '\0')
+                               ArchiveEntry(fout,
+                                                        nilCatalogId,  /* catalog ID */
+                                                        createDumpId(),        /* dump ID */
+                                                        ARCHIVE_OPTS(.tag = tag,
+                                                                                 .description = "COMMENT",
+                                                                                 .section = SECTION_PRE_DATA,
+                                                                                 .createStmt = comment_buf->data));
+
+                       if (seclabel_buf->data[0] != '\0')
+                               ArchiveEntry(fout,
+                                                        nilCatalogId,  /* catalog ID */
+                                                        createDumpId(),        /* dump ID */
+                                                        ARCHIVE_OPTS(.tag = tag,
+                                                                                 .description = "SECURITY LABEL",
+                                                                                 .section = SECTION_PRE_DATA,
+                                                                                 .createStmt = seclabel_buf->data));
+               }
        }
 
        /*
@@ -1007,7 +1236,7 @@ dumpRoles(PGconn *conn)
         * We do it this way because config settings for roles could mention the
         * names of other roles.
         */
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "\n--\n-- User Configurations\n--\n");
 
        for (i = 0; i < PQntuples(res); i++)
@@ -1015,9 +1244,12 @@ dumpRoles(PGconn *conn)
 
        PQclear(res);
 
-       fprintf(OPF, "\n\n");
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 
        destroyPQExpBuffer(buf);
+       destroyPQExpBuffer(comment_buf);
+       destroyPQExpBuffer(seclabel_buf);
 }
 
 
@@ -1031,6 +1263,7 @@ static void
 dumpRoleMembership(PGconn *conn)
 {
        PQExpBuffer buf = createPQExpBuffer();
+       PQExpBuffer querybuf = createPQExpBuffer();
        PQExpBuffer optbuf = createPQExpBuffer();
        PGresult   *res;
        int                     start = 0,
@@ -1093,7 +1326,7 @@ dumpRoleMembership(PGconn *conn)
        i_inherit_option = PQfnumber(res, "inherit_option");
        i_set_option = PQfnumber(res, "set_option");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Role memberships\n--\n\n");
 
        /*
@@ -1229,8 +1462,9 @@ dumpRoleMembership(PGconn *conn)
 
                                /* Generate the actual GRANT statement. */
                                resetPQExpBuffer(optbuf);
-                               fprintf(OPF, "GRANT %s", fmtId(role));
-                               fprintf(OPF, " TO %s", fmtId(member));
+                               resetPQExpBuffer(querybuf);
+                               appendPQExpBuffer(querybuf, "GRANT %s", fmtId(role));
+                               appendPQExpBuffer(querybuf, " TO %s", fmtId(member));
                                if (*admin_option == 't')
                                        appendPQExpBufferStr(optbuf, "ADMIN OPTION");
                                if (dump_grant_options)
@@ -1251,10 +1485,21 @@ dumpRoleMembership(PGconn *conn)
                                        appendPQExpBufferStr(optbuf, "SET FALSE");
                                }
                                if (optbuf->data[0] != '\0')
-                                       fprintf(OPF, " WITH %s", optbuf->data);
+                                       appendPQExpBuffer(querybuf, " WITH %s", optbuf->data);
                                if (dump_grantors)
-                                       fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
-                               fprintf(OPF, ";\n");
+                                       appendPQExpBuffer(querybuf, " GRANTED BY %s", fmtId(grantor));
+                               appendPQExpBuffer(querybuf, ";\n");
+
+                               if (archDumpFormat == archNull)
+                                       fprintf(OPF, "%s", querybuf->data);
+                               else
+                                       ArchiveEntry(fout,
+                                                                nilCatalogId,  /* catalog ID */
+                                                                createDumpId(),        /* dump ID */
+                                                                ARCHIVE_OPTS(.tag = psprintf("ROLE %s", fmtId(role)),
+                                                                                         .description = "ROLE PROPERTIES",
+                                                                                         .section = SECTION_PRE_DATA,
+                                                                                         .createStmt = querybuf->data));
                        }
                }
 
@@ -1265,8 +1510,11 @@ dumpRoleMembership(PGconn *conn)
 
        PQclear(res);
        destroyPQExpBuffer(buf);
+       destroyPQExpBuffer(querybuf);
+       destroyPQExpBuffer(optbuf);
 
-       fprintf(OPF, "\n\n");
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 }
 
 
@@ -1293,7 +1541,7 @@ dumpRoleGUCPrivs(PGconn *conn)
                                           "FROM pg_catalog.pg_parameter_acl "
                                           "ORDER BY 1");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n");
 
        for (i = 0; i < PQntuples(res); i++)
@@ -1318,14 +1566,25 @@ dumpRoleGUCPrivs(PGconn *conn)
                        exit_nicely(1);
                }
 
-               fprintf(OPF, "%s", buf->data);
+               if (archDumpFormat == archNull)
+                       fprintf(OPF, "%s", buf->data);
+               else
+                       ArchiveEntry(fout,
+                                                nilCatalogId,  /* catalog ID */
+                                                createDumpId(),        /* dump ID */
+                                                ARCHIVE_OPTS(.tag = psprintf("ROLE %s", fmtId(parowner)),
+                                                                         .description = "ROLE PROPERTIES",
+                                                                         .section = SECTION_PRE_DATA,
+                                                                         .createStmt = buf->data));
 
                free(fparname);
                destroyPQExpBuffer(buf);
        }
 
        PQclear(res);
-       fprintf(OPF, "\n\n");
+
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 }
 
 
@@ -1347,21 +1606,41 @@ dropTablespaces(PGconn *conn)
                                           "WHERE spcname !~ '^pg_' "
                                           "ORDER BY 1");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Drop tablespaces\n--\n\n");
 
        for (i = 0; i < PQntuples(res); i++)
        {
                char       *spcname = PQgetvalue(res, i, 0);
+               PQExpBuffer delQry = createPQExpBuffer();
+
+               if (archDumpFormat == archNull)
+               {
+                       appendPQExpBuffer(delQry, "DROP TABLESPACE %s%s;\n",
+                                                         if_exists ? "IF EXISTS " : "",
+                                                         fmtId(spcname));
+                       fprintf(OPF, "%s", delQry->data);
+               }
+               else
+               {
+                       appendPQExpBuffer(delQry, "DROP TABLESPACE IF EXISTS %s;\n",
+                                                         fmtId(spcname));
+                       ArchiveEntry(fout,
+                                                nilCatalogId,  /* catalog ID */
+                                                createDumpId(),        /* dump ID */
+                                                ARCHIVE_OPTS(.tag = psprintf("TABLESPACE %s", fmtId(spcname)),
+                                                                         .description = "DROP_GLOBAL",
+                                                                         .section = SECTION_PRE_DATA,
+                                                                         .createStmt = delQry->data));
+               }
 
-               fprintf(OPF, "DROP TABLESPACE %s%s;\n",
-                               if_exists ? "IF EXISTS " : "",
-                               fmtId(spcname));
+               destroyPQExpBuffer(delQry);
        }
 
        PQclear(res);
 
-       fprintf(OPF, "\n\n");
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 }
 
 /*
@@ -1371,6 +1650,8 @@ static void
 dumpTablespaces(PGconn *conn)
 {
        PGresult   *res;
+       PQExpBuffer comment_buf = createPQExpBuffer();
+       PQExpBuffer seclabel_buf = createPQExpBuffer();
        int                     i;
 
        /*
@@ -1387,7 +1668,7 @@ dumpTablespaces(PGconn *conn)
                                           "WHERE spcname !~ '^pg_' "
                                           "ORDER BY 1");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Tablespaces\n--\n\n");
 
        for (i = 0; i < PQntuples(res); i++)
@@ -1406,6 +1687,9 @@ dumpTablespaces(PGconn *conn)
                /* needed for buildACLCommands() */
                fspcname = pg_strdup(fmtId(spcname));
 
+               resetPQExpBuffer(comment_buf);
+               resetPQExpBuffer(seclabel_buf);
+
                if (binary_upgrade)
                {
                        appendPQExpBufferStr(buf, "\n-- For binary upgrade, must preserve pg_tablespace oid\n");
@@ -1447,24 +1731,67 @@ dumpTablespaces(PGconn *conn)
 
                if (!no_comments && spccomment && spccomment[0] != '\0')
                {
-                       appendPQExpBuffer(buf, "COMMENT ON TABLESPACE %s IS ", fspcname);
-                       appendStringLiteralConn(buf, spccomment, conn);
-                       appendPQExpBufferStr(buf, ";\n");
+                       appendPQExpBuffer(comment_buf, "COMMENT ON TABLESPACE %s IS ", fspcname);
+                       appendStringLiteralConn(comment_buf, spccomment, conn);
+                       appendPQExpBufferStr(comment_buf, ";\n");
                }
 
                if (!no_security_labels)
                        buildShSecLabels(conn, "pg_tablespace", spcoid,
                                                         "TABLESPACE", spcname,
-                                                        buf);
+                                                        seclabel_buf);
+
+               if (archDumpFormat == archNull)
+               {
+                       fprintf(OPF, "%s", buf->data);
 
-               fprintf(OPF, "%s", buf->data);
+                       if (comment_buf->data[0] != '\0')
+                               fprintf(OPF, "%s", comment_buf->data);
+
+                       if (seclabel_buf->data[0] != '\0')
+                               fprintf(OPF, "%s", seclabel_buf->data);
+               }
+               else
+               {
+                       char       *tag = psprintf("TABLESPACE %s", fmtId(fspcname));
+
+                       ArchiveEntry(fout,
+                                                nilCatalogId,  /* catalog ID */
+                                                createDumpId(),        /* dump ID */
+                                                ARCHIVE_OPTS(.tag = tag,
+                                                                         .description = "TABLESPACE",
+                                                                         .section = SECTION_PRE_DATA,
+                                                                         .createStmt = buf->data));
+
+                       if (comment_buf->data[0] != '\0')
+                               ArchiveEntry(fout,
+                                                        nilCatalogId,  /* catalog ID */
+                                                        createDumpId(),        /* dump ID */
+                                                        ARCHIVE_OPTS(.tag = tag,
+                                                                                 .description = "COMMENT",
+                                                                                 .section = SECTION_PRE_DATA,
+                                                                                 .createStmt = comment_buf->data));
+
+                       if (seclabel_buf->data[0] != '\0')
+                               ArchiveEntry(fout,
+                                                        nilCatalogId,  /* catalog ID */
+                                                        createDumpId(),        /* dump ID */
+                                                        ARCHIVE_OPTS(.tag = tag,
+                                                                                 .description = "SECURITY LABEL",
+                                                                                 .section = SECTION_PRE_DATA,
+                                                                                 .createStmt = seclabel_buf->data));
+               }
 
                free(fspcname);
                destroyPQExpBuffer(buf);
        }
 
        PQclear(res);
-       fprintf(OPF, "\n\n");
+       destroyPQExpBuffer(comment_buf);
+       destroyPQExpBuffer(seclabel_buf);
+
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 }
 
 
@@ -1487,12 +1814,13 @@ dropDBs(PGconn *conn)
                                           "WHERE datallowconn AND datconnlimit != -2 "
                                           "ORDER BY datname");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Drop databases (except postgres and template1)\n--\n\n");
 
        for (i = 0; i < PQntuples(res); i++)
        {
                char       *dbname = PQgetvalue(res, i, 0);
+               PQExpBuffer delQry = createPQExpBuffer();
 
                /*
                 * Skip "postgres" and "template1"; dumpDatabases() will deal with
@@ -1503,15 +1831,35 @@ dropDBs(PGconn *conn)
                        strcmp(dbname, "template0") != 0 &&
                        strcmp(dbname, "postgres") != 0)
                {
-                       fprintf(OPF, "DROP DATABASE %s%s;\n",
-                                       if_exists ? "IF EXISTS " : "",
-                                       fmtId(dbname));
+                       if (archDumpFormat == archNull)
+                       {
+                               appendPQExpBuffer(delQry, "DROP DATABASE %s%s;\n",
+                                                                 if_exists ? "IF EXISTS " : "",
+                                                                 fmtId(dbname));
+                               fprintf(OPF, "%s", delQry->data);
+                       }
+                       else
+                       {
+                               appendPQExpBuffer(delQry, "DROP DATABASE IF EXISTS %s;\n",
+                                                                 fmtId(dbname));
+
+                               ArchiveEntry(fout,
+                                                        nilCatalogId,  /* catalog ID */
+                                                        createDumpId(),        /* dump ID */
+                                                        ARCHIVE_OPTS(.tag = psprintf("DATABASE %s", fmtId(dbname)),
+                                                                                 .description = "DROP_GLOBAL",
+                                                                                 .section = SECTION_PRE_DATA,
+                                                                                 .createStmt = delQry->data));
+                       }
+
+                       destroyPQExpBuffer(delQry);
                }
        }
 
        PQclear(res);
 
-       fprintf(OPF, "\n\n");
+       if (archDumpFormat == archNull)
+               fprintf(OPF, "\n\n");
 }
 
 
@@ -1533,7 +1881,7 @@ dumpUserConfig(PGconn *conn, const char *username)
 
        res = executeQuery(conn, buf->data);
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
        {
                char       *sanitized;
 
@@ -1548,7 +1896,17 @@ dumpUserConfig(PGconn *conn, const char *username)
                makeAlterConfigCommand(conn, PQgetvalue(res, i, 0),
                                                           "ROLE", username, NULL, NULL,
                                                           buf);
-               fprintf(OPF, "%s", buf->data);
+
+               if (archDumpFormat == archNull)
+                       fprintf(OPF, "%s", buf->data);
+               else
+                       ArchiveEntry(fout,
+                                                nilCatalogId,  /* catalog ID */
+                                                createDumpId(),        /* dump ID */
+                                                ARCHIVE_OPTS(.tag = psprintf("ROLE %s", fmtId(username)),
+                                                                         .description = "ROLE PROPERTIES",
+                                                                         .section = SECTION_PRE_DATA,
+                                                                         .createStmt = buf->data));
        }
 
        PQclear(res);
@@ -1618,6 +1976,9 @@ dumpDatabases(PGconn *conn)
 {
        PGresult   *res;
        int                     i;
+       char            db_subdir[MAXPGPATH];
+       char            dbfilepath[MAXPGPATH];
+       FILE       *map_file = NULL;
 
        /*
         * Skip databases marked not datallowconn, since we'd be unable to connect
@@ -1631,19 +1992,59 @@ dumpDatabases(PGconn *conn)
         * doesn't have some failure mode with --clean.
         */
        res = executeQuery(conn,
-                                          "SELECT datname "
+                                          "SELECT datname, oid "
                                           "FROM pg_database d "
                                           "WHERE datallowconn AND datconnlimit != -2 "
                                           "ORDER BY (datname <> 'template1'), datname");
 
-       if (PQntuples(res) > 0)
+       if (PQntuples(res) > 0 && archDumpFormat == archNull)
                fprintf(OPF, "--\n-- Databases\n--\n\n");
 
+       /*
+        * If directory/tar/custom format is specified, create a subdirectory
+        * under the main directory and each database dump file or subdirectory
+        * will be created in that subdirectory by pg_dump.
+        */
+       if (archDumpFormat != archNull)
+       {
+               char            map_file_path[MAXPGPATH];
+               char       *map_preamble[] = {
+                       "#################################################################",
+                       "# map.dat",
+                       "#",
+                       "# This file maps oids to database names",
+                       "#",
+                       "# pg_restore will restore all the databases listed here, unless",
+                       "# otherwise excluded. You can also inhibit restoration of a",
+                       "# database by removing the line or commenting out the line with"
+                       "# a # mark.",
+                       "#################################################################",
+                       NULL
+               };
+
+               snprintf(db_subdir, MAXPGPATH, "%s/databases", filename);
+
+               /* Create a subdirectory with 'databases' name under main directory. */
+               if (mkdir(db_subdir, pg_dir_create_mode) != 0)
+                       pg_fatal("could not create directory \"%s\": %m", db_subdir);
+
+               snprintf(map_file_path, MAXPGPATH, "%s/map.dat", filename);
+
+               /* Create a map file (to store dboid and dbname) */
+               map_file = fopen(map_file_path, PG_BINARY_W);
+               if (!map_file)
+                       pg_fatal("could not open file \"%s\": %m", map_file_path);
+
+               for (char **line = map_preamble; *line; line++)
+                       fprintf(map_file, "%s\n", *line);
+       }
+
        for (i = 0; i < PQntuples(res); i++)
        {
                char       *dbname = PQgetvalue(res, i, 0);
                char       *sanitized;
-               const char *create_opts;
+               char       *oid = PQgetvalue(res, i, 1);
+               const char *create_opts = "";
                int                     ret;
 
                /* Skip template0, even if it's not marked !datallowconn. */
@@ -1660,7 +2061,10 @@ dumpDatabases(PGconn *conn)
                pg_log_info("dumping database \"%s\"", dbname);
 
                sanitized = sanitize_line(dbname, true);
-               fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized);
+
+               if (archDumpFormat == archNull)
+                       fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", sanitized);
+
                free(sanitized);
 
                /*
@@ -1675,24 +2079,40 @@ dumpDatabases(PGconn *conn)
                {
                        if (output_clean)
                                create_opts = "--clean --create";
+                       /* Since pg_dump won't emit a \connect command, we must */
+                       else if (archDumpFormat == archNull)
+                               fprintf(OPF, "\\connect %s\n\n", dbname);
                        else
-                       {
                                create_opts = "";
-                               /* Since pg_dump won't emit a \connect command, we must */
-                               fprintf(OPF, "\\connect %s\n\n", dbname);
-                       }
                }
                else
                        create_opts = "--create";
 
-               if (filename)
+               if (filename && archDumpFormat == archNull)
                        fclose(OPF);
 
-               ret = runPgDump(dbname, create_opts);
+               /*
+                * If this is not a plain format dump, then append dboid and dbname to
+                * the map.dat file.
+                */
+               if (archDumpFormat != archNull)
+               {
+                       if (archDumpFormat == archCustom)
+                               snprintf(dbfilepath, MAXPGPATH, "\"%s\"/\"%s\".dmp", db_subdir, oid);
+                       else if (archDumpFormat == archTar)
+                               snprintf(dbfilepath, MAXPGPATH, "\"%s\"/\"%s\".tar", db_subdir, oid);
+                       else
+                               snprintf(dbfilepath, MAXPGPATH, "\"%s\"/\"%s\"", db_subdir, oid);
+
+                       /* Put one line entry for dboid and dbname in map file. */
+                       fprintf(map_file, "%s %s\n", oid, dbname);
+               }
+
+               ret = runPgDump(dbname, create_opts, dbfilepath);
                if (ret != 0)
                        pg_fatal("pg_dump failed on database \"%s\", exiting", dbname);
 
-               if (filename)
+               if (filename && archDumpFormat == archNull)
                {
                        OPF = fopen(filename, PG_BINARY_A);
                        if (!OPF)
@@ -1701,6 +2121,10 @@ dumpDatabases(PGconn *conn)
                }
        }
 
+       /* Close map file */
+       if (archDumpFormat != archNull)
+               fclose(map_file);
+
        PQclear(res);
 }
 
@@ -1710,7 +2134,7 @@ dumpDatabases(PGconn *conn)
  * Run pg_dump on dbname, with specified options.
  */
 static int
-runPgDump(const char *dbname, const char *create_opts)
+runPgDump(const char *dbname, const char *create_opts, char *dbfile)
 {
        PQExpBufferData connstrbuf;
        PQExpBufferData cmd;
@@ -1719,17 +2143,36 @@ runPgDump(const char *dbname, const char *create_opts)
        initPQExpBuffer(&connstrbuf);
        initPQExpBuffer(&cmd);
 
-       printfPQExpBuffer(&cmd, "\"%s\" %s %s", pg_dump_bin,
-                                         pgdumpopts->data, create_opts);
-
        /*
-        * If we have a filename, use the undocumented plain-append pg_dump
-        * format.
+        * If this is not a plain format dump, then append file name and dump
+        * format to the pg_dump command to get archive dump.
         */
-       if (filename)
-               appendPQExpBufferStr(&cmd, " -Fa ");
+       if (archDumpFormat != archNull)
+       {
+               printfPQExpBuffer(&cmd, "\"%s\" %s -f %s %s", pg_dump_bin,
+                                                 pgdumpopts->data, dbfile, create_opts);
+
+               if (archDumpFormat == archDirectory)
+                       appendPQExpBufferStr(&cmd, "  --format=directory ");
+               else if (archDumpFormat == archCustom)
+                       appendPQExpBufferStr(&cmd, "  --format=custom ");
+               else if (archDumpFormat == archTar)
+                       appendPQExpBufferStr(&cmd, "  --format=tar ");
+       }
        else
-               appendPQExpBufferStr(&cmd, " -Fp ");
+       {
+               printfPQExpBuffer(&cmd, "\"%s\" %s %s", pg_dump_bin,
+                                                 pgdumpopts->data, create_opts);
+
+               /*
+                * If we have a filename, use the undocumented plain-append pg_dump
+                * format.
+                */
+               if (filename)
+                       appendPQExpBufferStr(&cmd, " -Fa ");
+               else
+                       appendPQExpBufferStr(&cmd, " -Fp ");
+       }
 
        /*
         * Append the database name to the already-constructed stem of connection
@@ -1803,6 +2246,76 @@ executeCommand(PGconn *conn, const char *query)
 }
 
 
+/*
+ * check_for_invalid_global_names
+ *
+ * Check that no database, role, or tablespace name contains a newline or
+ * carriage return character.  Such characters in database names would break
+ * the map.dat file format used for non-plain-text dumps.  Role and tablespace
+ * names are also checked because such characters were forbidden starting in
+ * v19.
+ *
+ * Excluded databases are skipped since they won't appear in map.dat.
+ */
+static void
+check_for_invalid_global_names(PGconn *conn,
+                                                          SimpleStringList *database_exclude_names)
+{
+       PGresult   *res;
+       int                     i;
+       PQExpBuffer names;
+       int                     count = 0;
+
+       res = executeQuery(conn,
+                                          "SELECT datname AS objname, 'database' AS objtype "
+                                          "FROM pg_catalog.pg_database "
+                                          "WHERE datallowconn AND datconnlimit != -2 "
+                                          "UNION ALL "
+                                          "SELECT rolname AS objname, 'role' AS objtype "
+                                          "FROM pg_catalog.pg_roles "
+                                          "UNION ALL "
+                                          "SELECT spcname AS objname, 'tablespace' AS objtype "
+                                          "FROM pg_catalog.pg_tablespace");
+
+       names = createPQExpBuffer();
+
+       for (i = 0; i < PQntuples(res); i++)
+       {
+               char       *objname = PQgetvalue(res, i, 0);
+               char       *objtype = PQgetvalue(res, i, 1);
+
+               /* Skip excluded databases since they won't be in map.dat */
+               if (strcmp(objtype, "database") == 0 &&
+                       simple_string_list_member(database_exclude_names, objname))
+                       continue;
+
+               if (strpbrk(objname, "\n\r"))
+               {
+                       appendPQExpBuffer(names, "  %s: \"", objtype);
+                       for (char *p = objname; *p; p++)
+                       {
+                               if (*p == '\n')
+                                       appendPQExpBufferStr(names, "\\n");
+                               else if (*p == '\r')
+                                       appendPQExpBufferStr(names, "\\r");
+                               else
+                                       appendPQExpBufferChar(names, *p);
+                       }
+                       appendPQExpBufferStr(names, "\"\n");
+                       count++;
+               }
+       }
+
+       PQclear(res);
+
+       if (count > 0)
+               pg_fatal("database, role, or tablespace names contain a newline or carriage return character, which is not supported in non-plain-text dumps:\n%s",
+                                names->data);
+
+       destroyPQExpBuffer(names);
+}
+
+
 /*
  * dumpTimestamp
  */
@@ -1874,3 +2387,47 @@ read_dumpall_filters(const char *filename, SimpleStringList *pattern)
 
        filter_free(&fstate);
 }
+
+/*
+ * parseDumpFormat
+ *
+ * This will validate dump formats.
+ */
+static ArchiveFormat
+parseDumpFormat(const char *format)
+{
+       ArchiveFormat archDumpFormat;
+
+       if (pg_strcasecmp(format, "c") == 0)
+               archDumpFormat = archCustom;
+       else if (pg_strcasecmp(format, "custom") == 0)
+               archDumpFormat = archCustom;
+       else if (pg_strcasecmp(format, "d") == 0)
+               archDumpFormat = archDirectory;
+       else if (pg_strcasecmp(format, "directory") == 0)
+               archDumpFormat = archDirectory;
+       else if (pg_strcasecmp(format, "p") == 0)
+               archDumpFormat = archNull;
+       else if (pg_strcasecmp(format, "plain") == 0)
+               archDumpFormat = archNull;
+       else if (pg_strcasecmp(format, "t") == 0)
+               archDumpFormat = archTar;
+       else if (pg_strcasecmp(format, "tar") == 0)
+               archDumpFormat = archTar;
+       else
+               pg_fatal("unrecognized output format \"%s\"; please specify \"c\", \"d\", \"p\", or \"t\"",
+                                format);
+
+       return archDumpFormat;
+}
+
+/*
+ * createDumpId
+ *
+ * Return the next dumpId.
+ */
+static int
+createDumpId(void)
+{
+       return ++dumpIdVal;
+}
index 84b8d410c9ef417db50cb499192c715c9c0e037e..14d886fc86e8f6ea70afd50eda40fe4c332ae67f 100644 (file)
@@ -2,7 +2,7 @@
  *
  * pg_restore.c
  *     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.
  *
  *     pg_restore will read the backup archive and
 #include "postgres_fe.h"
 
 #include <ctype.h>
+#include <sys/stat.h>
 #ifdef HAVE_TERMIOS_H
 #include <termios.h>
 #endif
 
+#include "common/string.h"
+#include "connectdb.h"
 #include "dumputils.h"
 #include "fe_utils/option_utils.h"
+#include "fe_utils/string_utils.h"
 #include "filter.h"
 #include "getopt_long.h"
 #include "parallel.h"
 
 static void usage(const char *progname);
 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
 main(int argc, char **argv)
 {
        RestoreOptions *opts;
        int                     c;
-       int                     exit_code;
        int                     numWorkers = 1;
-       Archive    *AH;
        char       *inputFileSpec;
        bool            data_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      enable_row_security = 0;
        static int      if_exists = 0;
@@ -89,6 +116,7 @@ main(int argc, char **argv)
                {"clean", 0, NULL, 'c'},
                {"create", 0, NULL, 'C'},
                {"data-only", 0, NULL, 'a'},
+               {"globals-only", 0, NULL, 'g'},
                {"dbname", 1, NULL, 'd'},
                {"exit-on-error", 0, NULL, 'e'},
                {"exclude-schema", 1, NULL, 'N'},
@@ -142,6 +170,7 @@ main(int argc, char **argv)
                {"statistics-only", no_argument, &statistics_only, 1},
                {"filter", required_argument, NULL, 4},
                {"restrict-key", required_argument, NULL, 6},
+               {"exclude-database", required_argument, NULL, 7},
 
                {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)
        {
                switch (c)
@@ -197,11 +226,14 @@ main(int argc, char **argv)
                                if (strlen(optarg) != 0)
                                        opts->formatName = pg_strdup(optarg);
                                break;
+                       case 'g':
+                               /* restore only global sql commands. */
+                               globals_only = true;
+                               break;
                        case 'h':
                                if (strlen(optarg) != 0)
                                        opts->cparams.pghost = pg_strdup(optarg);
                                break;
-
                        case 'j':                       /* number of restore jobs */
                                if (!option_parse_int(optarg, "-j/--jobs", 1,
                                                                          PG_MAX_JOBS,
@@ -321,6 +353,10 @@ main(int argc, char **argv)
                                opts->restrict_key = pg_strdup(optarg);
                                break;
 
+                       case 7:                         /* database patterns to skip */
+                               simple_string_list_append(&db_exclude_patterns, optarg);
+                               break;
+
                        default:
                                /* getopt_long already emitted a complaint */
                                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)
                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 */
        if (opts->cparams.dbname)
        {
@@ -420,6 +464,31 @@ main(int argc, char **argv)
                pg_fatal("options %s and %s cannot be used together",
                                 "-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
         * a transaction block.
@@ -485,6 +554,183 @@ main(int argc, char **argv)
                                         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);
 
        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
         * 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 */
        AH->verbose = opts->verbose;
@@ -514,25 +766,21 @@ main(int argc, char **argv)
        else
        {
                ProcessArchiveRestoreOptions(AH);
-               RestoreArchive(AH);
+               RestoreArchive(AH, append_data);
        }
 
-       /* done, print a summary of ignored errors */
-       if (AH->n_errors)
-               pg_log_warning("errors ignored on restore: %d", AH->n_errors);
+       n_errors = AH->n_errors;
 
        /* AH may be freed in CloseArchive? */
-       exit_code = AH->n_errors ? 1 : 0;
-
        CloseArchive(AH);
 
-       return exit_code;
+       return n_errors;
 }
 
 static void
 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(_("  %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, --create                 create the target database\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(_("  -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"
@@ -566,6 +815,7 @@ usage(const char *progname)
        printf(_("  -1, --single-transaction     restore as a single transaction\n"));
        printf(_("  --disable-triggers           disable triggers during data-only restore\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"
                         "                               in FILENAME\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(_("\n"
-                        "The options -I, -n, -N, -P, -t, -T, and --section can be combined and specified\n"
-                        "multiple times to select multiple objects.\n"));
+                        "The options -I, -n, -N, -P, -t, -T, --section, and --exclude-database can be\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(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
        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);
 }
+
+/*
+ * 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;
+}
index ab9310eb42b21e6e3e2412f84c0ad906a3e565d7..a895bc314b0044bc090e6e55d1625cc49895b747 100644 (file)
@@ -244,4 +244,59 @@ command_fails_like(
        '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();
diff --git a/src/bin/pg_dump/t/007_pg_dumpall.pl b/src/bin/pg_dump/t/007_pg_dumpall.pl
new file mode 100644 (file)
index 0000000..b228e57
--- /dev/null
@@ -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();
index 241945734ec8663c7e363e92e35b74d07ea00cbf..1a89ef94becdb200e62f5431561fe26d9df084e4 100644 (file)
@@ -600,6 +600,7 @@ CustomScanMethods
 CustomScanState
 CycleCtr
 DBState
+DbOidName
 DCHCacheEntry
 DEADLOCK_INFO
 DECountItem