vacuumdb: Don't let an unconnectable database abort --all
vacuumdb --all connects to each database in turn, and a failed
connection was fatal, so a single database that refuses connections
prevented every remaining database from being processed. Some managed
PostgreSQL services (CloudSQL in particular) reserve a database for the
provider that ordinary users cannot connect to, which defeats --all
entirely on those systems.
Report such a database as a warning and continue with the rest.
Databases named explicitly are unaffected: failing to connect to those
is still an error.
So that the warning can carry the reason the connection failed,
connectDatabase() now hands a failed connection back to the caller when
fail_ok is set, rather than closing it and returning NULL; its only
existing fail_ok caller, connectMaintenanceDatabase(), is adjusted to
match.
---
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 508c8dfa146..b96b1aa84cf 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -128,6 +128,13 @@ PostgreSQL documentation
Vacuum all databases.
+
+ Databases that cannot be connected to are skipped with a warning, and
+ the remaining databases are still processed. This is chiefly of use
+ with managed PostgreSQL services, which
+ commonly include a database reserved for the provider that other
+ users are not permitted to connect to.
+
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index 58e38971b3d..3559d125cd1 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -13,7 +13,7 @@ program_version_ok('vacuumdb');
program_options_handling_ok('vacuumdb');
my $node = PostgreSQL::Test::Cluster->new('main');
-$node->init;
+$node->init(auth_extra => [ '--create-role' => 'regress_vacuumdb_user' ]);
$node->start;
$node->issues_sql_like(
@@ -373,4 +373,21 @@ $node->issues_sql_unlike(
qr/statement:\ VACUUM/sx,
'--analyze-only does not run vacuum');
+# A database that the user cannot connect to must not abandon --all: it is
+# skipped with a warning, and the other databases are still processed. Run as
+# an unprivileged role, since a superuser is exempt from the CONNECT check.
+$node->safe_psql('postgres', 'CREATE ROLE regress_vacuumdb_user LOGIN');
+$node->safe_psql('postgres', 'CREATE DATABASE regress_vacuumdb_noconn');
+$node->safe_psql('postgres',
+ 'REVOKE CONNECT ON DATABASE regress_vacuumdb_noconn FROM PUBLIC');
+$node->command_checks_all(
+ [
+ 'vacuumdb', '--all',
+ '--analyze-only', '--username' => 'regress_vacuumdb_user'
+ ],
+ 0,
+ [qr/^$/],
+ [qr/warning: skipping database "regress_vacuumdb_noconn": /],
+ '--all skips databases that cannot be connected to');
+
done_testing();
diff --git a/src/bin/scripts/vacuuming.c b/src/bin/scripts/vacuuming.c
index 855a5754c98..fb6db77f761 100644
--- a/src/bin/scripts/vacuuming.c
+++ b/src/bin/scripts/vacuuming.c
@@ -192,7 +192,25 @@ vacuum_one_database(ConnParams *cparams,
Assert(stage == ANALYZE_NO_STAGE ||
(stage >= 0 && stage < ANALYZE_NUM_STAGES));
- conn = connectDatabase(cparams, progname, vacopts->echo, false, true);
+ /*
+ * When processing every database, one we cannot connect to is not a fatal
+ * condition: managed environments routinely include a database reserved
+ * for the hosting provider that ordinary users are refused entry to, and
+ * treating that as fatal would abandon the run before reaching the
+ * databases the user actually cares about. Warn and let the caller move
+ * on to the next one. Any other database was named explicitly, so
+ * failing to connect to it remains an error.
+ */
+ conn = connectDatabase(cparams, progname, vacopts->echo,
+ (vacopts->objfilter & OBJFILTER_ALL_DBS) != 0,
+ true);
+ if (PQstatus(conn) == CONNECTION_BAD)
+ {
+ pg_log_warning("skipping database \"%s\": %s",
+ PQdb(conn), PQerrorMessage(conn));
+ PQfinish(conn);
+ return EXIT_SUCCESS;
+ }
if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
{
diff --git a/src/fe_utils/connect_utils.c b/src/fe_utils/connect_utils.c
index b117718513c..6fa81ce404a 100644
--- a/src/fe_utils/connect_utils.c
+++ b/src/fe_utils/connect_utils.c
@@ -27,6 +27,11 @@
* given during previous calls to this routine. (Callers should not pass
* allow_password_reuse=true unless reconnecting to the same host+port+user
* as before, else we might create password exposure hazards.)
+ *
+ * If fail_ok is true, a connection that could not be established is handed
+ * back to the caller with status CONNECTION_BAD, just as PQconnectdb would
+ * do; the caller must then report PQerrorMessage() as it sees fit and call
+ * PQfinish() on it. Otherwise, a failure is reported and we exit.
*/
PGconn *
connectDatabase(const ConnParams *cparams, const char *progname,
@@ -109,10 +114,7 @@ connectDatabase(const ConnParams *cparams, const char *progname,
if (PQstatus(conn) == CONNECTION_BAD)
{
if (fail_ok)
- {
- PQfinish(conn);
- return NULL;
- }
+ return conn; /* caller reports the error and closes it */
pg_fatal("%s", PQerrorMessage(conn));
}
@@ -143,8 +145,9 @@ connectMaintenanceDatabase(ConnParams *cparams,
/* Otherwise, try postgres first and then template1. */
cparams->dbname = "postgres";
conn = connectDatabase(cparams, progname, echo, true, false);
- if (!conn)
+ if (PQstatus(conn) == CONNECTION_BAD)
{
+ PQfinish(conn);
cparams->dbname = "template1";
conn = connectDatabase(cparams, progname, echo, false, false);
}