On Thu, Aug 27, 2026 at 2:23 PM Mahendra Singh Thalor
<[email protected]> wrote:
>
> Thanks Noah and Andrew for the feedback. Based on review comments, I
> re-implemented these patches.
>
at [1], Noah mentioned that
"""
There should be one appendPQExpBuffer(delQry, "DROP ROLE ..."), not one for
plain format and another for non-plain formats. Having two creates excess
risk of format-specific bugs, something pg_dump.c has long avoided well. (I'm
echoing my postgr.es/m/[email protected] review. I wrote,
"The strength of the archiver architecture shows in how rarely new features
need format-specific logic and how rarely format-specific bugs get reported."
That holds for the way pg_dump.c uses the archiver, but it doesn't hold for
the way pg_dumpall.c now uses the archiver.)
"""
That means we cannot use
if (archDumpFormat == archNull)
fprintf(OPF, "DROP ROLE"....)
else
ArchiveEntry(fout, ....)
All of the below is wrong.
+static void dropDBsText(PGconn *conn);
+static void dropTablespacesText(PGconn *conn);
+static void dropRolesText(PGconn *conn)
Therefore, we should unify it using ArchiveEntry, because we cannot
use fprintf for non-plain text formats.
With that, plain-text format also needs CreateArchive(). We must be
careful with fprintf(OPF): both RestoreArchive() and fprintf(OPF)
write to the same output, so their writes must be ordered correctly.
I also refactored check_for_invalid_global_names. if one database name
contained \r\n, call pg_fatal.
[1]
https://www.postgresql.org/message-id/20260607000218.96.noahmisch%40microsoft.com
--
jian
https://www.enterprisedb.com/
From 53e18987a274b262ee0ef764469d70be3074a081 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Mon, 5 Oct 2026 13:34:06 +0800
Subject: [PATCH v2 1/2] refactor check_for_invalid_global_names and pgindent
---
src/bin/pg_dump/pg_dumpall.c | 110 ++++++++++++++++++-----------------
1 file changed, 57 insertions(+), 53 deletions(-)
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index d1714cb7447..f1152f46a42 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -776,12 +776,12 @@ main(int argc, char *argv[])
if (!data_only && !statistics_only && !no_schema)
{
/*
- * For plain-text output with --clean, print the Drop
- * databases/Drop tablespaces/Drop roles sections first, ahead
of
- * any creates (see dropDBsText() for why the order matters).
For
- * non-plain formats,
dumpRoles()/dumpTablespaces()/dumpDatabases()
- * instead attach a drop statement to each archive entry, and
the
- * archiver decides at restore time whether to use it.
+ * For plain-text output with --clean, print the Drop
databases/Drop
+ * tablespaces/Drop roles sections first, ahead of any creates
(see
+ * dropDBsText() for why the order matters). For non-plain
formats,
+ * dumpRoles()/dumpTablespaces()/dumpDatabases() instead attach
a drop
+ * statement to each archive entry, and the archiver decides at
+ * restore time whether to use it.
*
* For non-text formats, pg_dumpall unconditionally process
--clean
* option. In contrast, pg_restore only applies it if the user
@@ -1762,7 +1762,7 @@ dumpTablespaces(PGconn *conn)
ArchiveEntry(fout,
nilCatalogId, /* catalog ID */
- spcDumpId, /* dump
ID */
+ spcDumpId, /* dump ID */
ARCHIVE_OPTS(.tag = tag,
.description = "TABLESPACE",
.section = SECTION_PRE_DATA,
@@ -2231,58 +2231,62 @@ static void
check_for_invalid_global_names(PGconn *conn,
SimpleStringList
*excluded_names)
{
+ PQExpBuffer query = createPQExpBuffer();
+ PQExpBuffer escaped;
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;
+ char *objtype;
+
+ appendPQExpBufferStr(query,
+ "(SELECT datname AS objname,
'database' AS objtype "
+ "FROM pg_catalog.pg_database "
+ "WHERE datallowconn AND
datconnlimit != -2 "
+ "AND datname ~ E'[\\n\\r]'");
+
+ for (SimpleStringListCell *cell = excluded_names->head; cell; cell =
cell->next)
{
- 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(excluded_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++;
- }
+ appendPQExpBufferStr(query, " AND datname <> ");
+ appendStringLiteralConn(query, cell->val, conn);
}
- PQclear(res);
+ appendPQExpBufferStr(query,
+ " LIMIT 1) "
+ "UNION ALL "
+ "(SELECT rolname, 'role' "
+ "FROM pg_catalog.pg_roles "
+ "WHERE rolname ~ E'[\\n\\r]'
LIMIT 1) "
+ "UNION ALL "
+ "(SELECT spcname, 'tablespace'
"
+ "FROM pg_catalog.pg_tablespace
"
+ "WHERE spcname ~ E'[\\n\\r]'
LIMIT 1) "
+ "LIMIT 1");
+
+ res = executeQuery(conn, query->data);
+ destroyPQExpBuffer(query);
+
+ if (PQntuples(res) == 0)
+ {
+ PQclear(res);
+ return;
+ }
- 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);
+ objname = PQgetvalue(res, 0, 0);
+ objtype = PQgetvalue(res, 0, 1);
+
+ /* Show newline and carriage return as \n and \r */
+ escaped = createPQExpBuffer();
+ for (char *p = objname; *p; p++)
+ {
+ if (*p == '\n')
+ appendPQExpBufferStr(escaped, "\\n");
+ else if (*p == '\r')
+ appendPQExpBufferStr(escaped, "\\r");
+ else
+ appendPQExpBufferChar(escaped, *p);
+ }
- destroyPQExpBuffer(names);
+ pg_fatal("%s name \"%s\" contains a newline or carriage return
character, which is not supported in non-plain-text dumps",
+ objtype, escaped->data);
}
--
2.34.1
From dc2d24282a2fefb990d1e1e0a75e5a27ebce3d45 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Thu, 8 Oct 2026 15:39:49 +0800
Subject: [PATCH v2 2/2] using ArchiveEntry not using fprintf for global
objects
* Run pgindent on pg_backup_archiver.c and pg_dumpall.c.
* To keep the diff small, some former if/else branches are kept as bare
{ ... } blocks instead of being re-indented; they can be flattened later.
* Keep OPF open in append mode and fflush() it before the archiver or
pg_dump writes to the file, instead of repeatedly closing and reopening it.
* As requested in [1], pg_dumpall.c should not write global objects with
fprintf(OPF); they should be emitted through ArchiveEntry() in all output
formats, in a uniform way.
[1]
https://www.postgresql.org/message-id/20260607000218.96.noahmisch%40microsoft.com
---
src/bin/pg_dump/pg_backup_archiver.c | 26 +-
src/bin/pg_dump/pg_dumpall.c | 478 ++++++++-------------------
src/bin/pg_dump/t/007_pg_dumpall.pl | 7 +-
3 files changed, 167 insertions(+), 344 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup_archiver.c
b/src/bin/pg_dump/pg_backup_archiver.c
index 0a755126838..c24c8bbc399 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -644,7 +644,9 @@ RestoreArchive(Archive *AHX, bool append_data)
* object type. Most
of the time this matches
* te->desc, so search
for that; however for the
* different kinds of
CONSTRAINTs, we know to
- * search for hardcoded
"DROP CONSTRAINT" instead.
+ * search for hardcoded
"DROP CONSTRAINT" instead,
+ * and for pg_dumpall's
DROP_GLOBAL entries, "DROP
+ * DATABASE".
*/
if (strcmp(te->desc,
"DEFAULT") == 0 ||
strcmp(te->desc, "DATABASE PROPERTIES") == 0 ||
@@ -659,6 +661,8 @@ RestoreArchive(Archive *AHX, bool append_data)
strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
strcmp(te->desc, "FK CONSTRAINT") == 0)
strcpy(buffer, "DROP CONSTRAINT");
+ else if
(strcmp(te->desc, "DROP_GLOBAL") == 0)
+
strcpy(buffer, "DROP DATABASE");
else
snprintf(buffer, sizeof(buffer), "DROP %s",
te->desc);
@@ -1670,7 +1674,7 @@ archputs(const char *s, Archive *AH)
/* Public */
int
-archprintf(Archive *AH, const char *fmt, ...)
+archprintf(Archive *AH, const char *fmt,...)
{
int save_errno = errno;
char *p;
@@ -1774,7 +1778,7 @@ RestoreOutput(ArchiveHandle *AH, CompressFileHandle
*savedOutput)
* Print formatted text to the output file (usually stdout).
*/
int
-ahprintf(ArchiveHandle *AH, const char *fmt, ...)
+ahprintf(ArchiveHandle *AH, const char *fmt,...)
{
int save_errno = errno;
char *p;
@@ -1912,7 +1916,7 @@ ahwrite(const void *ptr, size_t size, size_t nmemb,
ArchiveHandle *AH)
/* on some error, we may decide to go on... */
void
-warn_or_exit_horribly(ArchiveHandle *AH, const char *fmt, ...)
+warn_or_exit_horribly(ArchiveHandle *AH, const char *fmt,...)
{
/* Stay quiet if this is a result of our own cancellation. */
if (!is_cancel_in_progress())
@@ -3020,7 +3024,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection,
ArchiveHandle *AH)
/* These items are treated specially */
if (strcmp(te->desc, "ENCODING") == 0 ||
strcmp(te->desc, "STDSTRINGS") == 0 ||
- strcmp(te->desc, "SEARCHPATH") == 0)
+ strcmp(te->desc, "SEARCHPATH") == 0 ||
+ strcmp(te->desc, "DEFAULT_TRANSACTION_READ_ONLY") == 0)
return REQ_SPECIAL;
if ((strcmp(te->desc, "STATISTICS DATA") == 0) ||
@@ -3054,9 +3059,9 @@ _tocEntryRequired(TocEntry *te, teSection curSection,
ArchiveHandle *AH)
* Global object TOC entries (e.g., ROLEs or TABLESPACEs) must not be
* ignored. They have no te->namespace, so without this, the
* --schema/--exclude-schema checks below would exclude them entirely.
- * DROP_GLOBAL (pg_dumpall's cross-database drops) is included here
- * rather than above with DATABASE/DATABASE PROPERTIES because it must
- * be gated on dropSchema, not createDB.
+ * DROP_GLOBAL (pg_dumpall's cross-database drops) is included here
rather
+ * than above with DATABASE/DATABASE PROPERTIES because it must be gated
+ * on dropSchema, not createDB.
*/
if (strcmp(te->desc, "ROLE") == 0 ||
strcmp(te->desc, "ROLE PROPERTIES") == 0 ||
@@ -3484,6 +3489,11 @@ _doSetFixedOutputState(ArchiveHandle *AH)
{
RestoreOptions *ropt = AH->public.ropt;
+ /* pg_dumpall archives: the restore must be able to write */
+ for (TocEntry *te = AH->toc->next; te != AH->toc; te = te->next)
+ if (strcmp(te->desc, "DEFAULT_TRANSACTION_READ_ONLY") == 0)
+ ahprintf(AH, "%s", te->defn);
+
/*
* Disable timeouts to allow for slow commands, idle parallel workers,
etc
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index f1152f46a42..38d3c8ca3e4 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -66,14 +66,12 @@ typedef struct
static void help(void);
-static void dropDBsText(PGconn *conn);
-static void dropTablespacesText(PGconn *conn);
-static void dropRolesText(PGconn *conn);
static void dumpRoles(PGconn *conn);
static void dumpRoleMembership(PGconn *conn);
static void dumpRoleGUCPrivs(PGconn *conn);
static void dumpTablespaces(PGconn *conn);
static void dumpUserConfig(PGconn *conn, const char *username);
+static void dropDatabases(PGconn *conn);
static void dumpDatabases(PGconn *conn);
static void dumpTimestamp(const char *msg);
static int runPgDump(const char *dbname, const char *create_opts, char
*dbfile);
@@ -567,6 +565,17 @@ main(int argc, char *argv[])
if (!OPF)
pg_fatal("could not open output file \"%s\": %m",
filename);
+ fclose(OPF);
+
+ /*
+ * Reopen in append mode, so that our writes always go to the
end of
+ * the file even after the archiver or pg_dump has appended to
it.
+ * Flushing OPF is then enough; we needn't close and reopen it
around
+ * those writes.
+ */
+ OPF = fopen(filename, PG_BINARY_A);
+ if (!OPF)
+ pg_fatal("could not open output file \"%s\": %m",
filename);
}
else
OPF = stdout;
@@ -658,40 +667,60 @@ main(int argc, char *argv[])
executeCommand(conn, "SET quote_all_identifiers = true");
/* Create an archive file for global commands. */
- if (archDumpFormat != archNull)
+ if (archDumpFormat == archNull)
+ {
+ fprintf(OPF, "--\n-- PostgreSQL database cluster dump\n--\n\n");
+
+ if (verbose)
+ dumpTimestamp("Started on");
+
+ fflush(OPF);
+ }
+
+ /*
+ * 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 && archDumpFormat != archNull)
+ check_for_invalid_global_names(conn, &database_exclude_names);
+
{
PQExpBuffer qry = createPQExpBuffer();
- char global_path[MAXPGPATH];
const char *encname;
pg_compress_specification compression_spec = {0};
- /*
- * 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);
-
- /* 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);
+ if (archDumpFormat != archNull)
+ {
+ char global_path[MAXPGPATH];
+
+ /* Set file path for global sql commands. */
+ snprintf(global_path, MAXPGPATH, "%s/toc.glo",
filename);
+
+ fout = CreateArchive(global_path, archCustom,
compression_spec,
+ dosync,
archModeWrite, NULL, DATA_DIR_SYNC_METHOD_FSYNC);
+ }
+ else
+ fout = CreateArchive(filename, archNull,
compression_spec,
+ dosync,
archModeWrite, NULL, DATA_DIR_SYNC_METHOD_FSYNC);
+
+ fout->std_strings = true;
+ fout->encoding = encoding;
/* Make dump options accessible right away */
SetArchiveOptions(fout, &dopt, NULL);
- ((ArchiveHandle *) fout)->connection = conn;
((ArchiveHandle *) fout)->public.numWorkers = 1;
/* Register the cleanup hook */
on_exit_close_archive(fout);
- /* Let the archiver know how noisy to be */
- fout->verbose = verbose;
+ /*
+ * Don't output TOC entry comments when dumping globals; they're
+ * purely informational and just add noise ahead of the SQL.
+ */
+ ((ArchiveHandle *) fout)->noTocComments = 1;
/*
* We allow the server to be back to 9.2, and up to any minor
release
@@ -703,12 +732,12 @@ main(int argc, char *argv[])
fout->numWorkers = 1;
/* Dump default_transaction_read_only. */
- appendPQExpBufferStr(qry, "SET default_transaction_read_only =
off;\n\n");
+ appendPQExpBufferStr(qry, "SET default_transaction_read_only =
off;\n");
ArchiveEntry(fout,
nilCatalogId, /* catalog ID */
createDumpId(), /* dump ID */
- ARCHIVE_OPTS(.tag =
"default_transaction_read_only",
- .description
= "default_transaction_read_only",
+ ARCHIVE_OPTS(.tag =
"DEFAULT_TRANSACTION_READ_ONLY",
+ .description
= "DEFAULT_TRANSACTION_READ_ONLY",
.section =
SECTION_PRE_DATA,
.createStmt =
qry->data));
resetPQExpBuffer(qry);
@@ -739,68 +768,9 @@ main(int argc, char *argv[])
.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)
{
- /*
- * For plain-text output with --clean, print the Drop
databases/Drop
- * tablespaces/Drop roles sections first, ahead of any creates
(see
- * dropDBsText() for why the order matters). For non-plain
formats,
- * dumpRoles()/dumpTablespaces()/dumpDatabases() instead attach
a drop
- * statement to each archive entry, and the archiver decides at
- * restore time whether to use it.
- *
- * 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 (archDumpFormat == archNull && output_clean)
- {
- if (!globals_only && !roles_only && !tablespaces_only)
- dropDBsText(conn);
-
- if (!roles_only && !no_tablespaces)
- dropTablespacesText(conn);
-
- if (!tablespaces_only)
- dropRolesText(conn);
- }
-
if (!tablespaces_only)
{
/* Dump roles (users) */
@@ -817,24 +787,34 @@ main(int argc, char *argv[])
/* Dump tablespaces */
if (!roles_only && !no_tablespaces)
dumpTablespaces(conn);
+
+ if (!globals_only && !roles_only && !tablespaces_only)
+ dropDatabases(conn);
}
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);
+ RestoreOptions *ropt = NewRestoreOptions();
+
+ ropt->filename = filename;
+ ropt->dropSchema = output_clean;
+ ropt->if_exists = if_exists;
+ ropt->restrict_key = restrict_key;
+ SetArchiveOptions(fout, &dopt, ropt);
+ ProcessArchiveRestoreOptions(fout);
+
+ /* write out all global objects before any database */
+ RestoreArchive(fout, true);
+ CloseArchive(fout);
}
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");
@@ -941,125 +921,6 @@ help(void)
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
-
-/*
- * Print the "Drop databases"/"Drop tablespaces"/"Drop roles" plain-text
- * sections, in that order, ahead of any "create" sections. The order
- * matters: a database may depend on a tablespace or role, so those must
- * go first. Non-plain formats need no such helper; the archiver's
- * restore-time drop pass already runs all drops before any creates,
- * regardless of registration order.
- */
-static void
-dropDBsText(PGconn *conn)
-{
- PGresult *res;
- int i;
-
- Assert(archDumpFormat == archNull);
-
- res = executeQuery(conn,
- "SELECT datname "
- "FROM pg_database d "
- "WHERE datallowconn AND datconnlimit
!= -2 "
- "ORDER BY datname");
-
- if (PQntuples(res) > 0)
- 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);
-
- if (strcmp(dbname, "template1") != 0 &&
- strcmp(dbname, "template0") != 0 &&
- strcmp(dbname, "postgres") != 0)
- fprintf(OPF, "DROP DATABASE %s%s;\n",
- if_exists ? "IF EXISTS " : "",
- fmtId(dbname));
- }
-
- PQclear(res);
-
- if (PQntuples(res) > 0)
- fprintf(OPF, "\n\n");
-}
-
-
-/*
- * Drop tablespaces (see dropDBsText() for why this is plain-text only).
- */
-static void
-dropTablespacesText(PGconn *conn)
-{
- PGresult *res;
- int i;
-
- Assert(archDumpFormat == archNull);
-
- res = executeQuery(conn, "SELECT spcname "
- "FROM pg_catalog.pg_tablespace "
- "WHERE spcname !~ '^pg_' "
- "ORDER BY 1");
-
- if (PQntuples(res) > 0)
- fprintf(OPF, "--\n-- Drop tablespaces\n--\n\n");
-
- for (i = 0; i < PQntuples(res); i++)
- {
- char *spcname = PQgetvalue(res, i, 0);
-
- fprintf(OPF, "DROP TABLESPACE %s%s;\n",
- if_exists ? "IF EXISTS " : "",
- fmtId(spcname));
- }
-
- PQclear(res);
-
- if (PQntuples(res) > 0)
- fprintf(OPF, "\n\n");
-}
-
-
-/*
- * Drop roles (see dropDBsText() for why this is plain-text only).
- */
-static void
-dropRolesText(PGconn *conn)
-{
- PQExpBuffer buf = createPQExpBuffer();
- PGresult *res;
- int i;
-
- Assert(archDumpFormat == archNull);
-
- printfPQExpBuffer(buf,
- "SELECT rolname "
- "FROM %s "
- "WHERE rolname !~ '^pg_' "
- "ORDER BY 1", role_catalog);
-
- res = executeQuery(conn, buf->data);
- destroyPQExpBuffer(buf);
-
- if (PQntuples(res) > 0)
- fprintf(OPF, "--\n-- Drop roles\n--\n\n");
-
- for (i = 0; i < PQntuples(res); i++)
- {
- char *rolename = PQgetvalue(res, i, 0);
-
- fprintf(OPF, "DROP ROLE %s%s;\n",
- if_exists ? "IF EXISTS " : "",
- fmtId(rolename));
- }
-
- PQclear(res);
-
- if (PQntuples(res) > 0)
- fprintf(OPF, "\n\n");
-}
-
/*
* Drop and dump roles.
*/
@@ -1119,9 +980,6 @@ dumpRoles(PGconn *conn)
i_rolcomment = PQfnumber(res, "rolcomment");
i_is_current_user = PQfnumber(res, "is_current_user");
- if (PQntuples(res) > 0 && archDumpFormat == archNull)
- fprintf(OPF, "--\n-- Roles\n--\n\n");
-
for (i = 0; i < PQntuples(res); i++)
{
const char *rolename;
@@ -1229,15 +1087,6 @@ dumpRoles(PGconn *conn)
"ROLE", rolename,
seclabel_buf);
- if (archDumpFormat == archNull)
- {
- fprintf(OPF, "%s", buf->data);
- fprintf(OPF, "%s", comment_buf->data);
-
- if (seclabel_buf->data[0] != '\0')
- fprintf(OPF, "%s", seclabel_buf->data);
- }
- else
{
char *tag = psprintf("ROLE %s", fmtId(rolename));
DumpId roleDumpId = createDumpId();
@@ -1279,17 +1128,10 @@ dumpRoles(PGconn *conn)
* We do it this way because config settings for roles could mention the
* names of other roles.
*/
- if (PQntuples(res) > 0 && archDumpFormat == archNull)
- fprintf(OPF, "\n--\n-- User Configurations\n--\n");
-
for (i = 0; i < PQntuples(res); i++)
dumpUserConfig(conn, PQgetvalue(res, i, i_rolname));
PQclear(res);
-
- if (archDumpFormat == archNull)
- fprintf(OPF, "\n\n");
-
destroyPQExpBuffer(buf);
destroyPQExpBuffer(delQry);
destroyPQExpBuffer(comment_buf);
@@ -1370,9 +1212,6 @@ dumpRoleMembership(PGconn *conn)
i_inherit_option = PQfnumber(res, "inherit_option");
i_set_option = PQfnumber(res, "set_option");
- if (PQntuples(res) > 0 && archDumpFormat == archNull)
- fprintf(OPF, "--\n-- Role memberships\n--\n\n");
-
/*
* We can't dump these GRANT commands in arbitrary order, because a role
* that is named as a grantor must already have ADMIN OPTION on the role
@@ -1545,16 +1384,13 @@ dumpRoleMembership(PGconn *conn)
appendPQExpBuffer(querybuf, " GRANTED
BY %s", fmtId(grantor));
appendPQExpBufferStr(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));
+ 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));
}
}
@@ -1567,9 +1403,6 @@ dumpRoleMembership(PGconn *conn)
destroyPQExpBuffer(buf);
destroyPQExpBuffer(querybuf);
destroyPQExpBuffer(optbuf);
-
- if (archDumpFormat == archNull)
- fprintf(OPF, "\n\n");
}
@@ -1596,9 +1429,6 @@ dumpRoleGUCPrivs(PGconn *conn)
"FROM pg_catalog.pg_parameter_acl "
"ORDER BY 1");
- if (PQntuples(res) > 0 && archDumpFormat == archNull)
- fprintf(OPF, "--\n-- Role privileges on configuration
parameters\n--\n\n");
-
for (i = 0; i < PQntuples(res); i++)
{
PQExpBuffer buf = createPQExpBuffer();
@@ -1621,25 +1451,19 @@ dumpRoleGUCPrivs(PGconn *conn)
exit_nicely(1);
}
- 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));
+ 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));
pg_free(fparname);
destroyPQExpBuffer(buf);
}
PQclear(res);
-
- if (archDumpFormat == archNull)
- fprintf(OPF, "\n\n");
}
@@ -1669,9 +1493,6 @@ dumpTablespaces(PGconn *conn)
"WHERE spcname !~ '^pg_' "
"ORDER BY 1");
- if (PQntuples(res) > 0 && archDumpFormat == archNull)
- fprintf(OPF, "--\n-- Tablespaces\n--\n\n");
-
for (i = 0; i < PQntuples(res); i++)
{
PQExpBuffer buf = createPQExpBuffer();
@@ -1745,17 +1566,6 @@ dumpTablespaces(PGconn *conn)
"TABLESPACE", spcname,
seclabel_buf);
- if (archDumpFormat == archNull)
- {
- 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));
DumpId spcDumpId = createDumpId();
@@ -1800,9 +1610,6 @@ dumpTablespaces(PGconn *conn)
destroyPQExpBuffer(delQry);
destroyPQExpBuffer(comment_buf);
destroyPQExpBuffer(seclabel_buf);
-
- if (archDumpFormat == archNull)
- fprintf(OPF, "\n\n");
}
@@ -1824,15 +1631,6 @@ dumpUserConfig(PGconn *conn, const char *username)
res = executeQuery(conn, buf->data);
- if (PQntuples(res) > 0 && archDumpFormat == archNull)
- {
- char *sanitized;
-
- sanitized = sanitize_line(username, true);
- fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", sanitized);
- free(sanitized);
- }
-
for (int i = 0; i < PQntuples(res); i++)
{
resetPQExpBuffer(buf);
@@ -1840,16 +1638,13 @@ dumpUserConfig(PGconn *conn, const char *username)
"ROLE", username,
NULL, NULL,
buf);
- 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));
+ 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);
@@ -1911,6 +1706,55 @@ expand_dbname_patterns(PGconn *conn,
destroyPQExpBuffer(query);
}
+/*
+ * Register a drop-only archive entry for each database that dumpDatabases()
+ * will recreate. A database may depend on a tablespace or role, so it must
+ * be dropped before them. The archiver's drop pass processes entries in
+ * reverse registration order, so this must be called after dumpRoles() and
+ * dumpTablespaces().
+ *
+ * template1 and postgres are skipped: they are assumed to exist in the target
+ * cluster, and with --clean dumpDatabases() has pg_dump drop and recreate
+ * them instead. IF EXISTS, if requested, is added by the archiver.
+ */
+static void
+dropDatabases(PGconn *conn)
+{
+ PQExpBuffer delQry = createPQExpBuffer();
+ PGresult *res;
+
+ /* This must agree with dumpDatabases() */
+ res = executeQuery(conn,
+ "SELECT datname "
+ "FROM pg_catalog.pg_database "
+ "WHERE datallowconn AND datconnlimit
!= -2 "
+ "AND datname NOT IN ('template0',
'template1', 'postgres') "
+ "ORDER BY datname");
+
+ for (int i = 0; i < PQntuples(res); i++)
+ {
+ char *dbname = PQgetvalue(res, i, 0);
+
+ /* Excluded databases are not recreated, so don't drop them
either */
+ if (simple_string_list_member(&database_exclude_names, dbname))
+ continue;
+
+ resetPQExpBuffer(delQry);
+ appendPQExpBuffer(delQry, "DROP DATABASE %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,
+ .dropStmt =
delQry->data));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(delQry);
+}
+
/*
* Dump contents of databases.
*/
@@ -1918,7 +1762,6 @@ static void
dumpDatabases(PGconn *conn)
{
PGresult *res;
- PQExpBuffer delQry = createPQExpBuffer();
int i;
char db_subdir[MAXPGPATH];
char dbfilepath[MAXPGPATH];
@@ -2009,11 +1852,11 @@ dumpDatabases(PGconn *conn)
/*
* We assume that "template1" and "postgres" already exist in
the
- * target installation and are never dropped above, for fear of
- * dropping the DB the restore script is initially connected
to. If
- * --clean was specified, tell pg_dump to drop and recreate
them;
- * otherwise we'll merely restore their contents. Other
databases
- * should simply be created.
+ * target installation and are never dropped by
dropDatabases(), for
+ * fear of dropping the DB the restore script is initially
connected
+ * to. If --clean was specified, tell pg_dump to drop and
recreate
+ * them; otherwise we'll merely restore their contents. Other
+ * databases should simply be created.
*/
if (strcmp(dbname, "template1") == 0 || strcmp(dbname,
"postgres") == 0)
{
@@ -2028,9 +1871,6 @@ dumpDatabases(PGconn *conn)
else
create_opts = "--create";
- if (filename && archDumpFormat == archNull)
- fclose(OPF);
-
if (archDumpFormat != archNull)
{
/* Compute this database's own archive file path. */
@@ -2043,36 +1883,11 @@ dumpDatabases(PGconn *conn)
/* Put one line entry for dboid and dbname in map file.
*/
fprintf(map_file, "%s %s\n", oid, dbname);
-
- /* Let the archiver drop this database first, if
--clean. */
- if (!data_only && !statistics_only && !no_schema &&
- strcmp(dbname, "template1") != 0 &&
strcmp(dbname, "postgres") != 0)
- {
- resetPQExpBuffer(delQry);
- 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,
-
.dropStmt = delQry->data));
- }
}
ret = runPgDump(dbname, create_opts, dbfilepath);
if (ret != 0)
pg_fatal("pg_dump failed on database \"%s\", exiting",
dbname);
-
- if (filename && archDumpFormat == archNull)
- {
- OPF = fopen(filename, PG_BINARY_A);
- if (!OPF)
- pg_fatal("could not re-open the output file
\"%s\": %m",
- filename);
- }
}
/* Close map file */
@@ -2090,7 +1905,6 @@ dumpDatabases(PGconn *conn)
}
PQclear(res);
- destroyPQExpBuffer(delQry);
}
diff --git a/src/bin/pg_dump/t/007_pg_dumpall.pl
b/src/bin/pg_dump/t/007_pg_dumpall.pl
index debaf1e770b..7b261ae4867 100644
--- a/src/bin/pg_dump/t/007_pg_dumpall.pl
+++ b/src/bin/pg_dump/t/007_pg_dumpall.pl
@@ -445,8 +445,7 @@ unlike(
'commented out database in map.dat is not restored');
# Test 12: --clean without --if-exists should not add IF EXISTS (no
-# auto-implication). DROP DATABASE always includes IF EXISTS, unlike
-# ROLE/TABLESPACE.
+# auto-implication).
$node->command_ok(
[
'pg_restore', '-C',
@@ -469,8 +468,8 @@ like(
);
like(
$clean_output,
- qr/DROP DATABASE IF EXISTS/,
- '--clean without --if-exists: DROP DATABASE IF EXISTS in output');
+ qr/DROP DATABASE(?! IF EXISTS)/,
+ '--clean without --if-exists: DROP DATABASE without IF EXISTS in
output');
# Test 13: --clean --if-exists adds IF EXISTS to all object types.
$node->command_ok(
--
2.34.1