All of the other CLI tools seems to support connection strings via the
PGDATABASE environment variable and the --maintenance-db command line
option, but this functionality is missing from createuser and dropuser.
Attached is a patch to bring them up to par with the other CLI tools.
There is an argument to be made for renaming or aliasing the
--maintenance-db parameter as --connection-string or similar for all of
these tools of course, but all of the others (except createdb and dropdb
afaics) accepts a "raw" connection string on the command line so it
might not matter much for them.
--
Anders Åstrand
Percona
From 1cc657cbfef2bd8ac482e88fe99a45a00e7392be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anders=20=C3=85strand?= <[email protected]>
Date: Fri, 16 Jan 2026 14:53:17 +0100
Subject: [PATCH] Add support for connection strings in user CLI tools
The other CLI tools support specifying connection strings either via the
PGDATABASE environment variable or the --maintenance-db command line
parameter.
This functionality was missing from createuser and dropuser, so this
commit adds support for it using either method.
---
doc/src/sgml/ref/createuser.sgml | 15 ++++++++++++++
doc/src/sgml/ref/dropuser.sgml | 15 ++++++++++++++
src/bin/scripts/createuser.c | 11 +++++++++-
src/bin/scripts/dropuser.c | 11 +++++++++-
src/bin/scripts/t/040_createuser.pl | 28 ++++++++++++++++++++++++++
src/bin/scripts/t/070_dropuser.pl | 31 +++++++++++++++++++++++++++++
6 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/ref/createuser.sgml b/doc/src/sgml/ref/createuser.sgml
index 0c061428514..bc2b460afa0 100644
--- a/doc/src/sgml/ref/createuser.sgml
+++ b/doc/src/sgml/ref/createuser.sgml
@@ -442,6 +442,21 @@ PostgreSQL documentation
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><option>--maintenance-db=<replaceable class="parameter">dbname</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the name of the database to connect to when creating the
+ new user. If not specified, the <literal>postgres</literal>
+ database will be used; if that does not exist,
+ <literal>template1</literal> will be used.
+ This can be a <link linkend="libpq-connstring">connection
+ string</link>. If so, connection string parameters will override any
+ conflicting command line options.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/dropuser.sgml b/doc/src/sgml/ref/dropuser.sgml
index b6be26d5b0a..dcb425a97bc 100644
--- a/doc/src/sgml/ref/dropuser.sgml
+++ b/doc/src/sgml/ref/dropuser.sgml
@@ -201,6 +201,21 @@ PostgreSQL documentation
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><option>--maintenance-db=<replaceable class="parameter">dbname</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the name of the database to connect to when removing the
+ user. If not specified, the <literal>postgres</literal> database will
+ be used; if that does not exist, <literal>template1</literal> will be
+ used.
+ This can be a <link linkend="libpq-connstring">connection
+ string</link>. If so, connection string parameters will override any
+ conflicting command line options.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
</refsect1>
diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c
index b3ab607afa7..070ef675454 100644
--- a/src/bin/scripts/createuser.c
+++ b/src/bin/scripts/createuser.c
@@ -57,6 +57,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 3},
{"bypassrls", no_argument, NULL, 4},
{"no-bypassrls", no_argument, NULL, 5},
+ {"maintenance-db", required_argument, NULL, 6},
{NULL, 0, NULL, 0}
};
@@ -67,6 +68,7 @@ main(int argc, char *argv[])
char *host = NULL;
char *port = NULL;
char *username = NULL;
+ const char *maintenance_db = NULL;
SimpleStringList roles = {NULL, NULL};
SimpleStringList members = {NULL, NULL};
SimpleStringList admins = {NULL, NULL};
@@ -190,6 +192,9 @@ main(int argc, char *argv[])
case 5:
bypassrls = TRI_NO;
break;
+ case 6:
+ maintenance_db = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -283,7 +288,10 @@ main(int argc, char *argv[])
if (login == TRI_DEFAULT)
login = TRI_YES;
- cparams.dbname = NULL; /* this program lacks any dbname option... */
+ if (maintenance_db == NULL)
+ maintenance_db = getenv("PGDATABASE");
+
+ cparams.dbname = maintenance_db;
cparams.pghost = host;
cparams.pgport = port;
cparams.pguser = username;
@@ -453,6 +461,7 @@ help(const char *progname)
printf(_(" -U, --username=USERNAME user name to connect as (not the one to create)\n"));
printf(_(" -w, --no-password never prompt for password\n"));
printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
diff --git a/src/bin/scripts/dropuser.c b/src/bin/scripts/dropuser.c
index 50952b46c45..5630dbe6875 100644
--- a/src/bin/scripts/dropuser.c
+++ b/src/bin/scripts/dropuser.c
@@ -35,6 +35,7 @@ main(int argc, char *argv[])
{"echo", no_argument, NULL, 'e'},
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
+ {"maintenance-db", required_argument, NULL, 2},
{NULL, 0, NULL, 0}
};
@@ -46,6 +47,7 @@ main(int argc, char *argv[])
char *host = NULL;
char *port = NULL;
char *username = NULL;
+ const char *maintenance_db = NULL;
enum trivalue prompt_password = TRI_DEFAULT;
ConnParams cparams;
bool echo = false;
@@ -90,6 +92,9 @@ main(int argc, char *argv[])
case 0:
/* this covers the long options */
break;
+ case 2:
+ maintenance_db = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -132,7 +137,10 @@ main(int argc, char *argv[])
exit(0);
}
- cparams.dbname = NULL; /* this program lacks any dbname option... */
+ if (maintenance_db == NULL)
+ maintenance_db = getenv("PGDATABASE");
+
+ cparams.dbname = maintenance_db;
cparams.pghost = host;
cparams.pgport = port;
cparams.pguser = username;
@@ -183,6 +191,7 @@ help(const char *progname)
printf(_(" -U, --username=USERNAME user name to connect as (not the one to drop)\n"));
printf(_(" -w, --no-password never prompt for password\n"));
printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
diff --git a/src/bin/scripts/t/040_createuser.pl b/src/bin/scripts/t/040_createuser.pl
index 0fbb91ce330..a2bee7c623d 100644
--- a/src/bin/scripts/t/040_createuser.pl
+++ b/src/bin/scripts/t/040_createuser.pl
@@ -90,4 +90,32 @@ $node->command_fails(
],
'fails for too many non-options');
+# These tests needs to run last as we have to break the cluster somewhat to run them.
+
+$node->safe_psql('postgres', 'CREATE DATABASE maintenance');
+$node->safe_psql('maintenance', 'DROP DATABASE postgres');
+$node->safe_psql('maintenance',
+ "UPDATE pg_database SET datistemplate=false WHERE datname='template1'");
+$node->safe_psql('maintenance', 'DROP DATABASE template1');
+
+$node->command_fails(
+ [ 'createuser', 'regress_user13' ],
+ 'fails when default databases do not exist');
+
+$node->command_ok(
+ [
+ 'createuser',
+ '--maintenance-db' => 'maintenance',
+ 'regress_user14',
+ ],
+ 'succeeds when maintenance database name is specified');
+
+$node->command_ok(
+ [
+ 'createuser',
+ '--maintenance-db' => 'postgresql:///maintenance',
+ 'regress_user15',
+ ],
+ 'succeeds when connection string is specified');
+
done_testing();
diff --git a/src/bin/scripts/t/070_dropuser.pl b/src/bin/scripts/t/070_dropuser.pl
index 6b2e8711d7c..43e777a68a0 100644
--- a/src/bin/scripts/t/070_dropuser.pl
+++ b/src/bin/scripts/t/070_dropuser.pl
@@ -27,4 +27,35 @@ $node->command_fails_like(
qr/role "regress_nonexistent" does not exist/,
'fails with nonexistent user');
+# These tests needs to run last as we have to break the cluster somewhat to run them.
+
+$node->safe_psql('postgres', 'CREATE ROLE regress_foobar2');
+$node->safe_psql('postgres', 'CREATE ROLE regress_foobar3');
+$node->safe_psql('postgres', 'CREATE ROLE regress_foobar4');
+
+$node->safe_psql('postgres', 'CREATE DATABASE maintenance');
+$node->safe_psql('maintenance', 'DROP DATABASE postgres');
+$node->safe_psql('maintenance',
+ "UPDATE pg_database SET datistemplate=false WHERE datname='template1'");
+$node->safe_psql('maintenance', 'DROP DATABASE template1');
+
+$node->command_fails([ 'dropuser', 'regress_foobar2' ],
+ 'fails when default databases do not exist');
+
+$node->command_ok(
+ [
+ 'dropuser',
+ '--maintenance-db' => 'maintenance',
+ 'regress_foobar3',
+ ],
+ 'succeeds when maintenance database name is specified');
+
+$node->command_ok(
+ [
+ 'dropuser',
+ '--maintenance-db' => 'postgresql:///maintenance',
+ 'regress_foobar4',
+ ],
+ 'succeeds when connection string is specified');
+
done_testing();
--
2.43.0