dropdb --force
Hi,
I propose a simple patch (works-for-me), which adds --force (-f)
option to dropdb utility.
Pros: This seems to be a desired option for many sysadmins, as this
thread proves: https://dba.stackexchange.com/questions/11893/force-drop-db-while-others-may-be-connected
Cons: another possible foot-gun for the unwary.
Obviously this patch needs some more work (see TODO note inside).
Please share opinions if this makes sense at all, and has any chance
going upstream.
The regression tests is simplistic, please help with an example of
multi-session test so I can follow.
Thanks,
Filip
Attachments:
postgresql-dropdb--force.20181218_01.patchtext/x-patch; charset=US-ASCII; name=postgresql-dropdb--force.20181218_01.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..797763c28f 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -79,7 +79,8 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
This command cannot be executed while connected to the target
database. Thus, it might be more convenient to use the program
<xref linkend="app-dropdb"/> instead,
- which is a wrapper around this command.
+ which is a wrapper around this command, and is also able
+ to terminate existing connections.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 38f38f01ce..29fb098e19 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -42,8 +42,9 @@ PostgreSQL documentation
<para>
<application>dropdb</application> is a wrapper around the
<acronym>SQL</acronym> command <xref linkend="sql-dropdatabase"/>.
- There is no effective difference between dropping databases via
- this utility and via other methods for accessing the server.
+ Other than the <option>--force</option> option, there is no effective
+ difference between dropping databases via this utility and via other
+ methods for accessing the server.
</para>
</refsect1>
@@ -86,6 +87,22 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will update the <literal>datallowconn</literal> attribute to
+ disallow incoming connections to target database, send
+ <literal>SIGTERM</literal> to target backends, and sleep some time
+ to allow the sessions to terminate.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index ba0038891d..4bfd8a9397 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -15,6 +15,10 @@
#include "fe_utils/string_utils.h"
+/* Time to sleep after isuing SIGTERM to backends */
+#define TERMINATE_SLEEP_TIME 1
+
+
static void help(const char *progname);
@@ -33,6 +37,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -48,6 +53,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -59,7 +65,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -84,6 +90,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -121,9 +130,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -132,6 +138,64 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ if (force)
+ {
+ /* TODO: revert this UPDATE in case removal fails for any reason */
+ appendPQExpBufferStr(&sql,
+ "UPDATE pg_catalog.pg_database\n"
+ " SET datallowconn = 'false'\n"
+ " WHERE datname OPERATOR(pg_catalog.=) ");
+ appendStringLiteralConn(&sql, dbname, conn);
+ appendPQExpBufferStr(&sql, ";\n");
+ if (echo)
+ printf("%s\n", sql.data);
+ result = PQexec(conn, sql.data);
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, _("%s: database removal failed: %s"),
+ progname, PQerrorMessage(conn));
+ PQfinish(conn);
+ exit(1);
+ }
+
+ PQclear(result);
+
+ resetPQExpBuffer(&sql);
+
+ appendPQExpBufferStr(&sql,
+ "SELECT pg_catalog.pg_terminate_backend(pid)\n"
+ " FROM pg_catalog.pg_stat_activity\n"
+ " WHERE pid OPERATOR(pg_catalog.<>) pg_catalog.pg_backend_pid()\n"
+ " AND datname OPERATOR(pg_catalog.=) ");
+ appendStringLiteralConn(&sql, dbname, conn);
+ appendPQExpBufferStr(&sql, ";\n");
+
+ if (echo)
+ printf("%s\n", sql.data);
+ result = PQexec(conn, sql.data);
+ if (PQresultStatus(result) == PGRES_TUPLES_OK)
+ {
+ if (PQntuples(result) > 0)
+ printf("%s: sent SIGTERM to %d backend(s)\n",
+ progname, PQntuples(result));
+ pg_usleep(TERMINATE_SLEEP_TIME * 1000000);
+ }
+ else
+ {
+ fprintf(stderr, _("%s: database removal failed: %s"),
+ progname, PQerrorMessage(conn));
+ PQfinish(conn);
+ exit(1);
+ }
+
+ PQclear(result);
+
+ resetPQExpBuffer(&sql);
+ }
+
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -158,6 +222,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..24663255ec 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: SELECT pg_catalog[.]pg_terminate_backend[(]pid[)]/,
+ 'SQL pg_terminate_backend run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
Hi
út 18. 12. 2018 v 16:11 odesílatel Filip Rembiałkowski <
filip.rembialkowski@gmail.com> napsal:
Hi,
I propose a simple patch (works-for-me), which adds --force (-f)
option to dropdb utility.Pros: This seems to be a desired option for many sysadmins, as this
thread proves:
https://dba.stackexchange.com/questions/11893/force-drop-db-while-others-may-be-connected
Cons: another possible foot-gun for the unwary.Obviously this patch needs some more work (see TODO note inside).
Please share opinions if this makes sense at all, and has any chance
going upstream.The regression tests is simplistic, please help with an example of
multi-session test so I can follow.
Still one my customer use a patch that implement FORCE on SQL level. It is
necessary under higher load when is not easy to synchronize clients.
Regards
Pavel
Show quoted text
Thanks,
Filip
Attachments:
drop-database-force-10.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-10.patchDownload
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 68c43620ef..008cac25cc 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -496,7 +496,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
* to wait 5 sec. We try to raise warning after 1 minute and and raise
* a error after 5 minutes.
*/
- if (!CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts, true))
+ if (!CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts, true, false))
break;
if (loops++ % 12 == 0)
@@ -798,7 +798,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -806,6 +806,7 @@ dropdb(const char *dbname, bool missing_ok)
HeapTuple tup;
int notherbackends;
int npreparedxacts;
+ int loops = 0;
int nslots,
nslots_active;
int nsubscriptions;
@@ -889,12 +890,33 @@ dropdb(const char *dbname, bool missing_ok)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("database \"%s\" is being accessed by other users",
- dbname),
- errdetail_busy_db(notherbackends, npreparedxacts)));
+ for (;;)
+ {
+ /*
+ * CountOtherDBBackends check usage of database by other backends and try
+ * to wait 5 sec. We try to raise warning after 1 minute and and raise
+ * a error after 5 minutes.
+ */
+ if (!CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, true, force))
+ break;
+
+ if (force && loops++ % 12 == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ /* without "force" flag raise exception immediately, or after 5 minutes */
+ if (!force || loops % 60 == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ CHECK_FOR_INTERRUPTS();
+ }
/*
* Check if there are subscriptions defined in the target database.
@@ -1053,7 +1075,7 @@ RenameDatabase(const char *oldname, const char *newname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
@@ -1183,7 +1205,7 @@ movedb(const char *dbname, const char *tblspcname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8c8384cd6b..e147e594ab 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3755,6 +3755,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_SCALAR_FIELD(force);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 68d38fcba1..ba7bee4bb0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1655,6 +1655,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_SCALAR_FIELD(force);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99baf..358e878c70 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9817,12 +9817,30 @@ DropdbStmt: DROP DATABASE database_name
n->dbname = $3;
n->missing_ok = FALSE;
$$ = (Node *)n;
+ n->force = false;
}
| DROP DATABASE IF_P EXISTS database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = TRUE;
+ n->force = false;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->force = true;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->force = true;
$$ = (Node *)n;
}
;
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8690a97dee..3080a81f7f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2895,10 +2895,13 @@ CountUserBackends(Oid roleid)
* continue (in simple implementation based only on PGPROC entries). In this case we should
* not calculate this process safely, becase createdb holds a lock.
*
+ * A option "force_terminate" enforce termination other sessions, that uses database. When we
+ * try to drop database, we should to calculate with all attached process.
*
*/
bool
-CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared, bool is_createdb_cmd)
+CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared,
+ bool is_createdb_cmd, bool force_terminate)
{
ProcArrayStruct *arrayP = procArray;
@@ -2943,6 +2946,18 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared, bool is_cre
if ((pgxact->vacuumFlags & PROC_IS_AUTOVACUUM) &&
nautovacs < MAXAUTOVACPIDS)
autovac_pids[nautovacs++] = proc->pid;
+ else
+ {
+ if (force_terminate)
+ {
+ /* try to terminate backend */
+#ifdef HAVE_SETSID
+ kill(-(proc->pid), SIGTERM);
+#else
+ kill(proc->pid, SIGTERM)
+#endif
+ }
+ }
}
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 82a707af7b..2304dce9d9 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -610,7 +610,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, stmt->force);
}
break;
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index f42c8cdbe3..e267dc0a28 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecb6cd0249..d3147ab6f9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3053,6 +3053,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ bool force; /* terminate all other sessions in db */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 298492afa1..f6561d11c7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,7 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared,
- bool is_createdb_cmd);
+ bool is_createdb_cmd, bool force_terminate);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
Hi
út 18. 12. 2018 v 16:11 odesílatel Filip Rembiałkowski <filip.rembialkowski@gmail.com> napsal:
Please share opinions if this makes sense at all, and has any chance
going upstream.
Clearly since Pavel has another implementation of the same concept,
there is some interest in this feature. :)
On Tue, Dec 18, 2018 at 5:20 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Still one my customer use a patch that implement FORCE on SQL level. It is necessary under higher load when is not easy to synchronize clients.
I think Filip's approach of setting pg_database.datallowconn='false'
is pretty clever to avoid the synchronization problem. But it's also a
good idea to expose this functionality via DROP DATABASE in SQL, like
Pavel's patch, not just the 'dropdb' binary.
If this is to be accepted into PostgreSQL core, I think the two
approaches should be combined on the server side.
Regards,
Marti Raudsepp
On Tue, Dec 18, 2018 at 01:25:32PM +0100, Filip Rembiałkowski wrote:
Hi,
I propose a simple patch (works-for-me), which adds --force (-f)
option to dropdb utility.
Nice!
I did something like this in user space back in 2010.
so there appears to be interest in such a feature.
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Marti Raudsepp <marti@juffo.org> writes:
I think Filip's approach of setting pg_database.datallowconn='false'
is pretty clever to avoid the synchronization problem.
Some bull-in-a-china-shop has recently added logic that allows ignoring
datallowconn and connecting anyway, so I'm not sure that that'd provide
a production-quality solution. On the other hand, maybe we could revert
BGWORKER_BYPASS_ALLOWCONN.
regards, tom lane
I wonder if this idea from seven years ago might be useful:
/messages/by-id/1305688547-sup-7028@alvh.no-ip.org
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
st 19. 12. 2018 v 16:55 odesílatel Alvaro Herrera <alvherre@2ndquadrant.com>
napsal:
I wonder if this idea from seven years ago might be useful:
/messages/by-id/1305688547-sup-7028@alvh.no-ip.org
Why not - I see this as little bit different case - when I used drop
database force than I didn't need to wait on clients.
The target is clean, but the methods can be different due different
environments, goals and sensitivity
Show quoted text
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Here is Pavel's patch rebased to master branch, added the dropdb
--force option, a test case & documentation.
I'm willing to work on it if needed. What are possible bad things that
could happen here? Is the documentation clear enough?
Thanks.
Show quoted text
On Tue, Dec 18, 2018 at 4:34 PM Marti Raudsepp <marti@juffo.org> wrote:
Hi
út 18. 12. 2018 v 16:11 odesílatel Filip Rembiałkowski <filip.rembialkowski@gmail.com> napsal:
Please share opinions if this makes sense at all, and has any chance
going upstream.Clearly since Pavel has another implementation of the same concept,
there is some interest in this feature. :)On Tue, Dec 18, 2018 at 5:20 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Still one my customer use a patch that implement FORCE on SQL level. It is necessary under higher load when is not easy to synchronize clients.
I think Filip's approach of setting pg_database.datallowconn='false'
is pretty clever to avoid the synchronization problem. But it's also a
good idea to expose this functionality via DROP DATABASE in SQL, like
Pavel's patch, not just the 'dropdb' binary.If this is to be accepted into PostgreSQL core, I think the two
approaches should be combined on the server side.Regards,
Marti Raudsepp
Attachments:
drop-database-force-20190305_01.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190305_01.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..84df485e11 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
@@ -32,9 +32,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +66,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 60 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 38f38f01ce..365ba317c1 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index d207cd899f..2137bee3a7 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -487,7 +487,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
* potential waiting; we may as well throw an error first if we're gonna
* throw one.
*/
- if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("source database \"%s\" is being accessed by other users",
@@ -777,7 +777,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -785,6 +785,7 @@ dropdb(const char *dbname, bool missing_ok)
HeapTuple tup;
int notherbackends;
int npreparedxacts;
+ int loops = 0;
int nslots,
nslots_active;
int nsubscriptions;
@@ -868,12 +869,33 @@ dropdb(const char *dbname, bool missing_ok)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("database \"%s\" is being accessed by other users",
- dbname),
- errdetail_busy_db(notherbackends, npreparedxacts)));
+ for (;;)
+ {
+ /*
+ * CountOtherDBBackends check usage of database by other backends and try
+ * to wait 5 sec. We try to raise warning after 1 minute and and raise
+ * a error after 5 minutes.
+ */
+ if (!CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, force))
+ break;
+
+ if (force && loops++ % 12 == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ /* without "force" flag raise exception immediately, or after 5 minutes */
+ if (!force || loops % 60 == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ CHECK_FOR_INTERRUPTS();
+ }
/*
* Check if there are subscriptions defined in the target database.
@@ -1032,7 +1054,7 @@ RenameDatabase(const char *oldname, const char *newname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
@@ -1162,7 +1184,7 @@ movedb(const char *dbname, const char *tblspcname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e15724bb0e..beed4ffb16 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3839,6 +3839,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_SCALAR_FIELD(force);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 31499eb798..135be360ad 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1661,6 +1661,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_SCALAR_FIELD(force);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0279013120..2bbc71c2ed 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10125,6 +10125,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->force = false;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10132,6 +10133,23 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->force = false;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->force = true;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->force = true;
$$ = (Node *)n;
}
;
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index cf93357997..cf92cc01ec 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2920,7 +2920,7 @@ CountUserBackends(Oid roleid)
* indefinitely.
*/
bool
-CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
+CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared, bool force_terminate)
{
ProcArrayStruct *arrayP = procArray;
@@ -2962,6 +2962,18 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
if ((pgxact->vacuumFlags & PROC_IS_AUTOVACUUM) &&
nautovacs < MAXAUTOVACPIDS)
autovac_pids[nautovacs++] = proc->pid;
+ else
+ {
+ if (force_terminate)
+ {
+ /* try to terminate backend */
+#ifdef HAVE_SETSID
+ kill(-(proc->pid), SIGTERM);
+#else
+ kill(proc->pid, SIGTERM)
+#endif
+ }
+ }
}
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6ec795f1b4..ef9b66e573 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -601,7 +601,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, stmt->force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index df7823df99..f8f4409d44 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -15,6 +15,10 @@
#include "fe_utils/string_utils.h"
+/* Time to sleep after isuing SIGTERM to backends */
+#define TERMINATE_SLEEP_TIME 1
+
+
static void help(const char *progname);
@@ -33,6 +37,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -48,6 +53,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -59,7 +65,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -84,6 +90,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -121,9 +130,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -132,6 +138,9 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname), (force ? " FORCE" : ""));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -158,6 +167,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..5f4ba20e2a 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 FORCE/,
+ 'SQL DROP DATABASE FORCE run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 28bf21153d..7ee4bbe13a 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a7e859dc90..34bc47dc77 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3116,6 +3116,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ bool force; /* terminate all other sessions in db */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index bd24850989..3a75358eab 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -112,7 +112,8 @@ extern int CountDBConnections(Oid databaseid);
extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+ int *nbackends, int *nprepared,
+ bool force_terminate);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
On Wed, Mar 6, 2019 at 1:39 PM Filip Rembiałkowski
<filip.rembialkowski@gmail.com> wrote:
Here is Pavel's patch rebased to master branch, added the dropdb
--force option, a test case & documentation.
Hello,
cfbot.cputube.org says this fails on Windows, due to a missing semicolon here:
#ifdef HAVE_SETSID
kill(-(proc->pid), SIGTERM);
#else
kill(proc->pid, SIGTERM)
#endif
The test case failed on Linux, I didn't check why exactly:
Test Summary Report
-------------------
t/050_dropdb.pl (Wstat: 65280 Tests: 13 Failed: 2)
Failed tests: 12-13
Non-zero exit status: 255
Parse errors: Bad plan. You planned 11 tests but ran 13.
+/* Time to sleep after isuing SIGTERM to backends */
+#define TERMINATE_SLEEP_TIME 1
s/isuing/issuing/
But, hmm, this macro doesn't actually seem to be used in the patch.
Wait, is that because the retry loop forgot to actually include the
sleep?
+ /* without "force" flag raise exception immediately, or after
5 minutes */
Normally we call it an "error", not an "exception".
--
Thomas Munro
https://enterprisedb.com
Thank you. Updated patch attached.
Show quoted text
On Sat, Mar 9, 2019 at 2:53 AM Thomas Munro <thomas.munro@gmail.com> wrote:
On Wed, Mar 6, 2019 at 1:39 PM Filip Rembiałkowski
<filip.rembialkowski@gmail.com> wrote:Here is Pavel's patch rebased to master branch, added the dropdb
--force option, a test case & documentation.Hello,
cfbot.cputube.org says this fails on Windows, due to a missing semicolon here:
#ifdef HAVE_SETSID
kill(-(proc->pid), SIGTERM);
#else
kill(proc->pid, SIGTERM)
#endifThe test case failed on Linux, I didn't check why exactly:
Test Summary Report
-------------------
t/050_dropdb.pl (Wstat: 65280 Tests: 13 Failed: 2)
Failed tests: 12-13
Non-zero exit status: 255
Parse errors: Bad plan. You planned 11 tests but ran 13.+/* Time to sleep after isuing SIGTERM to backends */ +#define TERMINATE_SLEEP_TIME 1s/isuing/issuing/
But, hmm, this macro doesn't actually seem to be used in the patch.
Wait, is that because the retry loop forgot to actually include the
sleep?+ /* without "force" flag raise exception immediately, or after
5 minutes */Normally we call it an "error", not an "exception".
--
Thomas Munro
https://enterprisedb.com
Attachments:
drop-database-force-20190310_01.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190310_01.patchDownload
doc/src/sgml/ref/drop_database.sgml | 28 +++++++++++++++++++++----
doc/src/sgml/ref/dropdb.sgml | 14 +++++++++++++
src/backend/commands/dbcommands.c | 42 ++++++++++++++++++++++++++++---------
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/parser/gram.y | 18 ++++++++++++++++
src/backend/storage/ipc/procarray.c | 14 ++++++++++++-
src/backend/tcop/utility.c | 2 +-
src/bin/scripts/dropdb.c | 14 +++++++++----
src/bin/scripts/t/050_dropdb.pl | 6 ++++++
src/include/commands/dbcommands.h | 2 +-
src/include/nodes/parsenodes.h | 1 +
src/include/storage/procarray.h | 3 ++-
13 files changed, 124 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..84df485e11 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
@@ -32,9 +32,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +66,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 60 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 38f38f01ce..365ba317c1 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index d207cd899f..0e7a23cb26 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -487,7 +487,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
* potential waiting; we may as well throw an error first if we're gonna
* throw one.
*/
- if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("source database \"%s\" is being accessed by other users",
@@ -777,7 +777,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -785,6 +785,7 @@ dropdb(const char *dbname, bool missing_ok)
HeapTuple tup;
int notherbackends;
int npreparedxacts;
+ int loops = 0;
int nslots,
nslots_active;
int nsubscriptions;
@@ -868,12 +869,33 @@ dropdb(const char *dbname, bool missing_ok)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("database \"%s\" is being accessed by other users",
- dbname),
- errdetail_busy_db(notherbackends, npreparedxacts)));
+ for (;;)
+ {
+ /*
+ * CountOtherDBBackends check usage of database by other backends and try
+ * to wait 5 sec. We try to raise warning after 1 minute and and raise
+ * error after 5 minutes.
+ */
+ if (!CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, force))
+ break;
+
+ if (force && loops++ % 12 == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ /* without "force" flag raise error immediately, or after 5 minutes */
+ if (!force || loops % 60 == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ CHECK_FOR_INTERRUPTS();
+ }
/*
* Check if there are subscriptions defined in the target database.
@@ -1032,7 +1054,7 @@ RenameDatabase(const char *oldname, const char *newname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
@@ -1162,7 +1184,7 @@ movedb(const char *dbname, const char *tblspcname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a8a735c247..f694a2890b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3842,6 +3842,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_SCALAR_FIELD(force);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3cab90e9f8..827fe0d6c1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1663,6 +1663,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_SCALAR_FIELD(force);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e23e68fdb3..1a31b409c7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10149,6 +10149,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->force = false;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10156,6 +10157,23 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->force = false;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->force = true;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->force = true;
$$ = (Node *)n;
}
;
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index cf93357997..6e89d2d9df 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2920,7 +2920,7 @@ CountUserBackends(Oid roleid)
* indefinitely.
*/
bool
-CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
+CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared, bool force_terminate)
{
ProcArrayStruct *arrayP = procArray;
@@ -2962,6 +2962,18 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
if ((pgxact->vacuumFlags & PROC_IS_AUTOVACUUM) &&
nautovacs < MAXAUTOVACPIDS)
autovac_pids[nautovacs++] = proc->pid;
+ else
+ {
+ if (force_terminate)
+ {
+ /* try to terminate backend */
+#ifdef HAVE_SETSID
+ kill(-(proc->pid), SIGTERM);
+#else
+ kill(proc->pid, SIGTERM);
+#endif
+ }
+ }
}
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6ec795f1b4..ef9b66e573 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -601,7 +601,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, stmt->force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index df7823df99..caec73e89b 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -33,6 +33,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -48,6 +49,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -59,7 +61,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -84,6 +86,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -121,9 +126,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -132,6 +134,9 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname), (force ? " FORCE" : ""));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -158,6 +163,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..5f4ba20e2a 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 FORCE/,
+ 'SQL DROP DATABASE FORCE run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 28bf21153d..7ee4bbe13a 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fe35783359..2df6d3013d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3117,6 +3117,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ bool force; /* terminate all other sessions in db */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index bd24850989..3a75358eab 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -112,7 +112,8 @@ extern int CountDBConnections(Oid databaseid);
extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+ int *nbackends, int *nprepared,
+ bool force_terminate);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
Hello,
This is a feature I have wanted for a long time, thank you for your work on
this.
The latest patch [1]/messages/by-id/attachment/99536/drop-database-force-20190310_01.patch applied cleanly for me. In dbcommands.c the comment
references a 5 second delay, I don't see where that happens, am I missing
something?
I tested both the dropdb program and the in database commands. Without
FORCE I get
the expected error message about active connections.
postgres=# DROP DATABASE testdb;
ERROR: source database "testdb" is being accessed by other users
DETAIL: There is 1 other session using the database.
With FORCE the database drops cleanly.
postgres=# DROP DATABASE testdb FORCE;
DROP DATABASE
The active connections get terminated as expected. Thanks,
[1]: /messages/by-id/attachment/99536/drop-database-force-20190310_01.patch
/messages/by-id/attachment/99536/drop-database-force-20190310_01.patch
*Ryan Lambert*
RustProof Labs
On Sun, Mar 10, 2019 at 12:54 PM Filip Rembiałkowski <
filip.rembialkowski@gmail.com> wrote:
Show quoted text
Thank you. Updated patch attached.
On Sat, Mar 9, 2019 at 2:53 AM Thomas Munro <thomas.munro@gmail.com>
wrote:On Wed, Mar 6, 2019 at 1:39 PM Filip Rembiałkowski
<filip.rembialkowski@gmail.com> wrote:Here is Pavel's patch rebased to master branch, added the dropdb
--force option, a test case & documentation.Hello,
cfbot.cputube.org says this fails on Windows, due to a missing
semicolon here:
#ifdef HAVE_SETSID
kill(-(proc->pid), SIGTERM);
#else
kill(proc->pid, SIGTERM)
#endifThe test case failed on Linux, I didn't check why exactly:
Test Summary Report
-------------------
t/050_dropdb.pl (Wstat: 65280 Tests: 13 Failed: 2)
Failed tests: 12-13
Non-zero exit status: 255
Parse errors: Bad plan. You planned 11 tests but ran 13.+/* Time to sleep after isuing SIGTERM to backends */ +#define TERMINATE_SLEEP_TIME 1s/isuing/issuing/
But, hmm, this macro doesn't actually seem to be used in the patch.
Wait, is that because the retry loop forgot to actually include the
sleep?+ /* without "force" flag raise exception immediately, or after
5 minutes */Normally we call it an "error", not an "exception".
--
Thomas Munro
https://enterprisedb.com
Hi,
On 2019-03-10 11:20:42 +0100, Filip Rembiałkowski wrote:
bool -CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared) +CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared, bool force_terminate) {
That doesn't seem like a decent API to me.
Greetings,
Andres Freund
On 31.03.2019, 04:35 Andres Freund <andres@anarazel.de> wrote:
bool -CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared) +CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared, bool force_terminate) {That doesn't seem like a decent API to me.
Only excuse is that naming was already a bit off, as the function
includes killing autovacuum workers.
Please advise what would be a good approach to improve it. I would
propose something like:
bool CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared);
// make it actually do what the name announces - only the count, no
side effects.
bool KillDBBackends(Oid databaseId, bool killAutovacuum, bool killBackends);
// try to kill off all the backends, return false when there are still any.
Also, there is a question - should the FORCE option rollback prepared
transactions?
Thanks!
Is this the intended behavior? SIGTERM is received.
test=# begin;
BEGIN
test=# create table test(a int);
CREATE TABLE
In another terminal drop the database.
test=# begin;
psql: FATAL: terminating connection due to administrator command
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>
Yes, I think it is because of this code Snippet
if (force_terminate)
{
/* try to terminate backend */
#ifdef HAVE_SETSID
kill(-(proc->pid), SIGTERM);
#else
kill(proc->pid, SIGTERM);
On Tue, Apr 9, 2019 at 3:06 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:
Is this the intended behavior? SIGTERM is received.
test=# begin;
BEGIN
test=# create table test(a int);
CREATE TABLEIn another terminal drop the database.
test=# begin;
psql: FATAL: terminating connection due to administrator command
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!>
--
Ibrar Ahmed
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: not tested
The feature works fine on my machine. The code is well-written.
The new status of this patch is: Ready for Committer
Also works fine according to my testing. Documentation is also clear.
Thanks for this useful patch.
Hi,
patch no longer applies (as of 12beta2).
postgres@ubuntudev:~/pg_testing/source/postgresql-12beta2$ patch -p1 < drop-database-force-20190310_01.patch
patching file doc/src/sgml/ref/drop_database.sgml
patching file doc/src/sgml/ref/dropdb.sgml
patching file src/backend/commands/dbcommands.c
Hunk #1 succeeded at 489 (offset 2 lines).
Hunk #2 succeeded at 779 (offset 2 lines).
Hunk #3 succeeded at 787 (offset 2 lines).
Hunk #4 succeeded at 871 (offset 2 lines).
Hunk #5 succeeded at 1056 (offset 2 lines).
Hunk #6 succeeded at 1186 (offset 2 lines).
patching file src/backend/nodes/copyfuncs.c
Hunk #1 succeeded at 3852 (offset 10 lines).
patching file src/backend/nodes/equalfuncs.c
Hunk #1 succeeded at 1666 (offset 3 lines).
patching file src/backend/parser/gram.y
Hunk #1 succeeded at 10194 (offset 45 lines).
Hunk #2 succeeded at 10202 (offset 45 lines).
patching file src/backend/storage/ipc/procarray.c
Hunk #1 succeeded at 2906 (offset -14 lines).
Hunk #2 succeeded at 2948 (offset -14 lines).
patching file src/backend/tcop/utility.c
patching file src/bin/scripts/dropdb.c
Hunk #1 succeeded at 34 (offset 1 line).
Hunk #2 succeeded at 50 (offset 1 line).
Hunk #3 succeeded at 63 (offset 2 lines).
Hunk #4 succeeded at 88 (offset 2 lines).
Hunk #5 succeeded at 128 (offset 2 lines).
Hunk #6 succeeded at 136 (offset 2 lines).
Hunk #7 succeeded at 164 (offset 1 line).
patching file src/bin/scripts/t/050_dropdb.pl
patching file src/include/commands/dbcommands.h
patching file src/include/nodes/parsenodes.h
Hunk #1 succeeded at 3133 (offset 16 lines).
patching file src/include/storage/procarray.h
Hunk #1 FAILED at 112.
1 out of 1 hunk FAILED -- saving rejects to file src/include/storage/procarray.h.rej
Could you please update it ? Thanks.
Anthony
The new status of this patch is: Waiting on Author
Hi
po 24. 6. 2019 v 10:28 odesílatel Anthony Nowocien <anowocien@gmail.com>
napsal:
Hi,
patch no longer applies (as of 12beta2).postgres@ubuntudev:~/pg_testing/source/postgresql-12beta2$ patch -p1 <
drop-database-force-20190310_01.patch
patching file doc/src/sgml/ref/drop_database.sgml
patching file doc/src/sgml/ref/dropdb.sgml
patching file src/backend/commands/dbcommands.c
Hunk #1 succeeded at 489 (offset 2 lines).
Hunk #2 succeeded at 779 (offset 2 lines).
Hunk #3 succeeded at 787 (offset 2 lines).
Hunk #4 succeeded at 871 (offset 2 lines).
Hunk #5 succeeded at 1056 (offset 2 lines).
Hunk #6 succeeded at 1186 (offset 2 lines).
patching file src/backend/nodes/copyfuncs.c
Hunk #1 succeeded at 3852 (offset 10 lines).
patching file src/backend/nodes/equalfuncs.c
Hunk #1 succeeded at 1666 (offset 3 lines).
patching file src/backend/parser/gram.y
Hunk #1 succeeded at 10194 (offset 45 lines).
Hunk #2 succeeded at 10202 (offset 45 lines).
patching file src/backend/storage/ipc/procarray.c
Hunk #1 succeeded at 2906 (offset -14 lines).
Hunk #2 succeeded at 2948 (offset -14 lines).
patching file src/backend/tcop/utility.c
patching file src/bin/scripts/dropdb.c
Hunk #1 succeeded at 34 (offset 1 line).
Hunk #2 succeeded at 50 (offset 1 line).
Hunk #3 succeeded at 63 (offset 2 lines).
Hunk #4 succeeded at 88 (offset 2 lines).
Hunk #5 succeeded at 128 (offset 2 lines).
Hunk #6 succeeded at 136 (offset 2 lines).
Hunk #7 succeeded at 164 (offset 1 line).
patching file src/bin/scripts/t/050_dropdb.pl
patching file src/include/commands/dbcommands.h
patching file src/include/nodes/parsenodes.h
Hunk #1 succeeded at 3133 (offset 16 lines).
patching file src/include/storage/procarray.h
Hunk #1 FAILED at 112.
1 out of 1 hunk FAILED -- saving rejects to file
src/include/storage/procarray.h.rejCould you please update it ? Thanks.
fixed
Regards
Pavel
Anthony
Show quoted text
The new status of this patch is: Waiting on Author
Attachments:
drop-database-force-20190626_01.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190626_01.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..84df485e11 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
@@ -32,9 +32,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +66,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 60 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..5d10ad1c92 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 15207bf75a..760b43de8c 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -489,7 +489,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
* potential waiting; we may as well throw an error first if we're gonna
* throw one.
*/
- if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("source database \"%s\" is being accessed by other users",
@@ -779,7 +779,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -787,6 +787,7 @@ dropdb(const char *dbname, bool missing_ok)
HeapTuple tup;
int notherbackends;
int npreparedxacts;
+ int loops = 0;
int nslots,
nslots_active;
int nsubscriptions;
@@ -870,12 +871,33 @@ dropdb(const char *dbname, bool missing_ok)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("database \"%s\" is being accessed by other users",
- dbname),
- errdetail_busy_db(notherbackends, npreparedxacts)));
+ for (;;)
+ {
+ /*
+ * CountOtherDBBackends check usage of database by other backends and try
+ * to wait 5 sec. We try to raise warning after 1 minute and and raise
+ * error after 5 minutes.
+ */
+ if (!CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, force))
+ break;
+
+ if (force && loops++ % 12 == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ /* without "force" flag raise error immediately, or after 5 minutes */
+ if (!force || loops % 60 == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ CHECK_FOR_INTERRUPTS();
+ }
/*
* Check if there are subscriptions defined in the target database.
@@ -1034,7 +1056,7 @@ RenameDatabase(const char *oldname, const char *newname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
@@ -1164,7 +1186,7 @@ movedb(const char *dbname, const char *tblspcname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..dc32a5506d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3852,6 +3852,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_SCALAR_FIELD(force);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..8af53d8562 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1666,6 +1666,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_SCALAR_FIELD(force);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1dd46..6ca2941821 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10194,6 +10194,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->force = false;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10201,6 +10202,23 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->force = false;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->force = true;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->force = true;
$$ = (Node *)n;
}
;
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 18a0f62ba6..fdcf0af12e 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2906,7 +2906,8 @@ CountUserBackends(Oid roleid)
* indefinitely.
*/
bool
-CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
+CountOtherDBBackends(Oid databaseId,
+ int *nbackends, int *nprepared, bool force_terminate)
{
ProcArrayStruct *arrayP = procArray;
@@ -2948,6 +2949,18 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
if ((pgxact->vacuumFlags & PROC_IS_AUTOVACUUM) &&
nautovacs < MAXAUTOVACPIDS)
autovac_pids[nautovacs++] = proc->pid;
+ else
+ {
+ if (force_terminate)
+ {
+ /* try to terminate backend */
+#ifdef HAVE_SETSID
+ kill(-(proc->pid), SIGTERM);
+#else
+ kill(proc->pid, SIGTERM);
+#endif
+ }
+ }
}
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9578b5c761..0b8a9f80bb 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -601,7 +601,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, stmt->force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..af86bb9f79 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,9 +128,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,9 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname), (force ? " FORCE" : ""));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -159,6 +164,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..5f4ba20e2a 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 FORCE/,
+ 'SQL DROP DATABASE FORCE run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 28bf21153d..7ee4bbe13a 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..37a1257519 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3133,6 +3133,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ bool force; /* terminate all other sessions in db */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..d0bd7db112 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -112,7 +112,7 @@ extern int CountDBConnections(Oid databaseid);
extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+ int *nbackends, int *nprepared, bool force_terminate);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
On Thu, Jun 27, 2019 at 7:15 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
fixed
Hi Pavel,
FYI t/050_dropdb.pl fails consistently with this patch applied:
https://travis-ci.org/postgresql-cfbot/postgresql/builds/555234838
--
Thomas Munro
https://enterprisedb.com
po 8. 7. 2019 v 0:07 odesílatel Thomas Munro <thomas.munro@gmail.com>
napsal:
On Thu, Jun 27, 2019 at 7:15 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:fixed
Hi Pavel,
FYI t/050_dropdb.pl fails consistently with this patch applied:
https://travis-ci.org/postgresql-cfbot/postgresql/builds/555234838
with attached patch should be ok
Regards
Pavel
Show quoted text
--
Thomas Munro
https://enterprisedb.com
Attachments:
drop-database-force-20190708.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190708.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..84df485e11 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ FORCE ]
</synopsis>
</refsynopsisdiv>
@@ -32,9 +32,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +66,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 60 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..5d10ad1c92 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 863f89f19d..86330bad12 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -499,7 +499,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
* potential waiting; we may as well throw an error first if we're gonna
* throw one.
*/
- if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(src_dboid, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("source database \"%s\" is being accessed by other users",
@@ -789,7 +789,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -797,6 +797,7 @@ dropdb(const char *dbname, bool missing_ok)
HeapTuple tup;
int notherbackends;
int npreparedxacts;
+ int loops = 0;
int nslots,
nslots_active;
int nsubscriptions;
@@ -880,12 +881,33 @@ dropdb(const char *dbname, bool missing_ok)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("database \"%s\" is being accessed by other users",
- dbname),
- errdetail_busy_db(notherbackends, npreparedxacts)));
+ for (;;)
+ {
+ /*
+ * CountOtherDBBackends check usage of database by other backends and try
+ * to wait 5 sec. We try to raise warning after 1 minute and and raise
+ * error after 5 minutes.
+ */
+ if (!CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, force))
+ break;
+
+ if (force && loops++ % 12 == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ /* without "force" flag raise error immediately, or after 5 minutes */
+ if (!force || loops % 60 == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("source database \"%s\" is being accessed by other users",
+ dbname),
+ errdetail_busy_db(notherbackends, npreparedxacts)));
+
+ CHECK_FOR_INTERRUPTS();
+ }
/*
* Check if there are subscriptions defined in the target database.
@@ -1053,7 +1075,7 @@ RenameDatabase(const char *oldname, const char *newname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
@@ -1183,7 +1205,7 @@ movedb(const char *dbname, const char *tblspcname)
*
* As in CREATE DATABASE, check this after other error conditions.
*/
- if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
+ if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts, false))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("database \"%s\" is being accessed by other users",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..dc32a5506d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3852,6 +3852,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_SCALAR_FIELD(force);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..8af53d8562 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1666,6 +1666,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_SCALAR_FIELD(force);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 208b4a1f28..583e906763 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10194,6 +10194,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->force = false;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10201,6 +10202,23 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->force = false;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->force = true;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name FORCE
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->force = true;
$$ = (Node *)n;
}
;
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 18a0f62ba6..fdcf0af12e 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2906,7 +2906,8 @@ CountUserBackends(Oid roleid)
* indefinitely.
*/
bool
-CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
+CountOtherDBBackends(Oid databaseId,
+ int *nbackends, int *nprepared, bool force_terminate)
{
ProcArrayStruct *arrayP = procArray;
@@ -2948,6 +2949,18 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
if ((pgxact->vacuumFlags & PROC_IS_AUTOVACUUM) &&
nautovacs < MAXAUTOVACPIDS)
autovac_pids[nautovacs++] = proc->pid;
+ else
+ {
+ if (force_terminate)
+ {
+ /* try to terminate backend */
+#ifdef HAVE_SETSID
+ kill(-(proc->pid), SIGTERM);
+#else
+ kill(proc->pid, SIGTERM);
+#endif
+ }
+ }
}
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 05ec7f3ac6..5b75392482 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -601,7 +601,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, stmt->force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..af86bb9f79 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,9 +128,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,9 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname), (force ? " FORCE" : ""));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -159,6 +164,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..ffd85e4f87 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 FORCE/,
+ 'SQL DROP DATABASE FORCE run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 28bf21153d..7ee4bbe13a 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..37a1257519 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3133,6 +3133,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ bool force; /* terminate all other sessions in db */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..d0bd7db112 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -112,7 +112,7 @@ extern int CountDBConnections(Oid databaseid);
extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+ int *nbackends, int *nprepared, bool force_terminate);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: not tested
Documentation: not tested
Hi,
The latest patch [1] applies cleanly and basic functionality retested. Looks great, thanks!
/messages/by-id/attachment/102334/drop-database-force-20190708.patch
Ryan Lambert
The new status of this patch is: Ready for Committer
Pavel Stehule <pavel.stehule@gmail.com> writes:
[ drop-database-force-20190708.patch ]
I took a brief look at this, but I don't think it's really close to
being committable.
* The documentation claims FORCE will fail if you don't have privileges
to terminate the other session(s) in the target DB. This is a lie; the
code issues kill() without any regard for such niceties. You could
perhaps make that better by going through pg_signal_backend().
* You've hacked CountOtherDBBackends to do something that's completely
outside the charter one would expect from its name, and not even
bothered to update its header comment. This requires more attention
to not confusing future hackers; I'd say you can't even use that
function name anymore.
* You've also ignored the existing code in CountOtherDBBackends
that is careful *not* to issue a kill() while holding the critical
ProcArrayLock. That problem would get enormously worse if you
tried to sub in pg_signal_backend() there, since that function
may do catalog accesses --- it's pretty likely that you could
get actual deadlocks, never mind just trashing performance.
* I really dislike the addition of more hard-wired delays and
timeouts to dropdb(). It's bad enough that CountOtherDBBackends
has got that. Two layers of timeout are a rickety mess, and
who's to say that a 1-minute timeout is appropriate for anything?
* I'm concerned that the proposed syntax is not future-proof.
FORCE is not a reserved word, and we surely don't want to make
it one; but just appending it to the end of the command without
any decoration seems like a recipe for problems if anybody wants
to add other options later. (Possible examples: RESTRICT/CASCADE,
or a user-defined timeout.) Maybe some parentheses would help?
Or possibly I'm being overly paranoid, but ...
I hadn't been paying any attention to this thread before now,
but I'd assumed from the thread title that the idea was to implement
any attempted kills in the dropdb app, not on the backend side.
(As indeed it looks like the first version did.) Maybe it would be
better to go back to that, instead of putting dubious behaviors
into the core server.
regards, tom lane
čt 25. 7. 2019 v 5:11 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
[ drop-database-force-20190708.patch ]
I took a brief look at this, but I don't think it's really close to
being committable.* The documentation claims FORCE will fail if you don't have privileges
to terminate the other session(s) in the target DB. This is a lie; the
code issues kill() without any regard for such niceties. You could
perhaps make that better by going through pg_signal_backend().* You've hacked CountOtherDBBackends to do something that's completely
outside the charter one would expect from its name, and not even
bothered to update its header comment. This requires more attention
to not confusing future hackers; I'd say you can't even use that
function name anymore.* You've also ignored the existing code in CountOtherDBBackends
that is careful *not* to issue a kill() while holding the critical
ProcArrayLock. That problem would get enormously worse if you
tried to sub in pg_signal_backend() there, since that function
may do catalog accesses --- it's pretty likely that you could
get actual deadlocks, never mind just trashing performance.* I really dislike the addition of more hard-wired delays and
timeouts to dropdb(). It's bad enough that CountOtherDBBackends
has got that. Two layers of timeout are a rickety mess, and
who's to say that a 1-minute timeout is appropriate for anything?* I'm concerned that the proposed syntax is not future-proof.
FORCE is not a reserved word, and we surely don't want to make
it one; but just appending it to the end of the command without
any decoration seems like a recipe for problems if anybody wants
to add other options later. (Possible examples: RESTRICT/CASCADE,
or a user-defined timeout.) Maybe some parentheses would help?
Or possibly I'm being overly paranoid, but ...
Can be
DROP DATABASE '(' options ...) [IF EXISTS] name
ok?
I hadn't been paying any attention to this thread before now,
but I'd assumed from the thread title that the idea was to implement
any attempted kills in the dropdb app, not on the backend side.
(As indeed it looks like the first version did.) Maybe it would be
better to go back to that, instead of putting dubious behaviors
into the core server.
I don't think so server side implementation is too helpful - there is lot
of situations, where DDL command is much more practical.
Show quoted text
regards, tom lane
On Thu, Jul 25, 2019 at 8:45 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 25. 7. 2019 v 5:11 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
[ drop-database-force-20190708.patch ]
I took a brief look at this, but I don't think it's really close to
being committable.
Hi Pavel,
The concept seems popular (I've wanted this myself), but it sounds
like it needs some more work. I've moved this to the next CF. If you
don't think you'll be able to work on it for the next CF, of course,
please feel free to change it to "Returned with feedback".
--
Thomas Munro
https://enterprisedb.com
I set the status to Waiting on Author since Tom's concerns [1]/messages/by-id/15707.1564024305@sss.pgh.pa.us have not been addressed.
[1]: /messages/by-id/15707.1564024305@sss.pgh.pa.us
Thanks,
Ryan
On 2019-Jul-25, Pavel Stehule wrote:
čt 25. 7. 2019 v 5:11 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
* I'm concerned that the proposed syntax is not future-proof.
Can be
DROP DATABASE '(' options ...) [IF EXISTS] name
ok?
Seems weird to me. I'd rather have the options at the end with a WITH
keyword. But that's just me, looking at gram.y for other productions
involving ^DROP.
I don't think so server side implementation is too helpful - there is lot
of situations, where DDL command is much more practical.
I tend to agree. Not really a fan of the double-timeout business,
though.
So when are you submitting an updated patch, addressing the other items
that Tom mentions in his review?
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
út 3. 9. 2019 v 18:46 odesílatel Alvaro Herrera <alvherre@2ndquadrant.com>
napsal:
On 2019-Jul-25, Pavel Stehule wrote:
čt 25. 7. 2019 v 5:11 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
* I'm concerned that the proposed syntax is not future-proof.
Can be
DROP DATABASE '(' options ...) [IF EXISTS] name
ok?
Seems weird to me. I'd rather have the options at the end with a WITH
keyword. But that's just me, looking at gram.y for other productions
involving ^DROP.I don't think so server side implementation is too helpful - there is lot
of situations, where DDL command is much more practical.I tend to agree. Not really a fan of the double-timeout business,
though.So when are you submitting an updated patch, addressing the other items
that Tom mentions in his review?
I would to prepare patch this week.
Regards
Pavel
Show quoted text
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi
I started work on this patch. I changed syntax to
DROP DATABASE [ ( FORCE , ..) ] [IF EXISTS ...]
and now I try to fix all other points from Tom's list
út 17. 9. 2019 v 12:15 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
[ drop-database-force-20190708.patch ]
I took a brief look at this, but I don't think it's really close to
being committable.* The documentation claims FORCE will fail if you don't have privileges
to terminate the other session(s) in the target DB. This is a lie; the
code issues kill() without any regard for such niceties. You could
perhaps make that better by going through pg_signal_backend().
This is question. There are two possible requirements about necessary rights
a) rights for other process termination
b) database owner can drop database
I understand very well to Tom's objection, but request to have
ROLE_SIGNAL_BACKEND or be super user looks too strong and not too much
practical.
If I am a owner of database, and I have a right to drop this database, why
I cannot to kick some other user that block database dropping.
What do you think about it? If I use pg_signal_backend, then the necessary
requirement for using DROP FORCE have to be granted SIGNAL_BACKEND rights.
I am sending updated version
Regards
Pavel
Show quoted text
* You've hacked CountOtherDBBackends to do something that's completely
outside the charter one would expect from its name, and not even
bothered to update its header comment. This requires more attention
to not confusing future hackers; I'd say you can't even use that
function name anymore.* You've also ignored the existing code in CountOtherDBBackends
that is careful *not* to issue a kill() while holding the critical
ProcArrayLock. That problem would get enormously worse if you
tried to sub in pg_signal_backend() there, since that function
may do catalog accesses --- it's pretty likely that you could
get actual deadlocks, never mind just trashing performance.* I really dislike the addition of more hard-wired delays and
timeouts to dropdb(). It's bad enough that CountOtherDBBackends
has got that. Two layers of timeout are a rickety mess, and
who's to say that a 1-minute timeout is appropriate for anything?* I'm concerned that the proposed syntax is not future-proof.
FORCE is not a reserved word, and we surely don't want to make
it one; but just appending it to the end of the command without
any decoration seems like a recipe for problems if anybody wants
to add other options later. (Possible examples: RESTRICT/CASCADE,
or a user-defined timeout.) Maybe some parentheses would help?
Or possibly I'm being overly paranoid, but ...I hadn't been paying any attention to this thread before now,
but I'd assumed from the thread title that the idea was to implement
any attempted kills in the dropdb app, not on the backend side.
(As indeed it looks like the first version did.) Maybe it would be
better to go back to that, instead of putting dubious behaviors
into the core server.regards, tom lane
Attachments:
drop-database-force-20190917-1.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190917-1.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..4a230418c1 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 60 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..5d10ad1c92 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 95881a8550..faa5bdbf71 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -810,7 +810,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -895,6 +895,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..f7bab61175 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,56 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( FORCE ) ] [ IF EXISTS ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE opt_drop_option_list database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $3;
+ n->dbname = $4;
n->missing_ok = false;
+ n->options = $3;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE opt_drop_option_list IF_P EXISTS database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $5;
+ n->dbname = $6;
n->missing_ok = true;
+ n->options = $3;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ '(' drop_option_list ')'
+ {
+ $$ = $2;
+ }
+ | /* EMPTY */
+ {
+ $$ = NIL;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..11b3ad2a48 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2970,6 +2970,50 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+
+ (void) kill(pid, SIGTERM); /* ignore any error */
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..d69e30972f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -598,10 +598,20 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_DropdbStmt:
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *item = (DefElem *) lfirst(lc);
+
+ if (strcmp(item->defname, "force") == 0)
+ force = true;
+ }
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..8bac1faa42 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,9 +128,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,10 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? " (FORCE) " : ""),
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -159,6 +165,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..8e1899a6c5 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE (FORCE) foobar2/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..66e0b904bf 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..e82f6c7c98 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -111,8 +111,8 @@ extern int CountDBBackends(Oid databaseid);
extern int CountDBConnections(Oid databaseid);
extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
extern int CountUserBackends(Oid roleid);
-extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+extern bool CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
Hi Pavel,
I took a quick look through the patch, I'll try to build and test it
tomorrow.
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
Why put FORCE as the single item in an options list? A bool var seems like
it would be more clear and consistent.
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( FORCE ) ] [ IF EXISTS ]
Why is it `[ ( FORCE ) ]` instead of `[ FORCE ]`?
There are also places in the code that seem like extra () are around
FORCE. Like here:
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? " (FORCE) " : ""),
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
And here:
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE (FORCE) foobar2/,
+ 'SQL DROP DATABASE (FORCE) run');
+
I'll try to build and test the patch tomorrow.
Thanks,
*Ryan Lambert*
Show quoted text
st 18. 9. 2019 v 4:53 odesílatel Ryan Lambert <ryan@rustprooflabs.com>
napsal:
Hi Pavel,
I took a quick look through the patch, I'll try to build and test it
tomorrow.--- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3145,6 +3145,7 @@ typedef struct DropdbStmt NodeTag type; char *dbname; /* database to drop */ bool missing_ok; /* skip error if db is missing? */ + List *options; /* currently only FORCE is supported */ } DropdbStmt;Why put FORCE as the single item in an options list? A bool var seems
like it would be more clear and consistent.
see discussion - it was one from Tom's objections. It is due possible
future enhancing or modification. It can looks strange, because now there
is only one option, but it allow very easy modifications. More it is
consistent with lot of other pg commands.
- * DROP DATABASE [ IF EXISTS ] + * DROP DATABASE [ ( FORCE ) ] [ IF EXISTS ]Why is it `[ ( FORCE ) ]` instead of `[ FORCE ]`?
There are also places in the code that seem like extra () are around
FORCE. Like here:
It was discussed before. FORCE is not reserved keyword, so inside list is
due safety against possible collisions.
Show quoted text
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;", + (force ? " (FORCE) " : ""), + (if_exists ? "IF EXISTS " : ""), fmtId(dbname));And here:
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2'); +$node->issues_sql_like( + [ 'dropdb', '--force', 'foobar2' ], + qr/statement: DROP DATABASE (FORCE) foobar2/, + 'SQL DROP DATABASE (FORCE) run'); +I'll try to build and test the patch tomorrow.
Thanks,
*Ryan Lambert*
st 18. 9. 2019 v 4:57 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
st 18. 9. 2019 v 4:53 odesílatel Ryan Lambert <ryan@rustprooflabs.com>
napsal:Hi Pavel,
I took a quick look through the patch, I'll try to build and test it
tomorrow.--- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3145,6 +3145,7 @@ typedef struct DropdbStmt NodeTag type; char *dbname; /* database to drop */ bool missing_ok; /* skip error if db is missing? */ + List *options; /* currently only FORCE is supported */ } DropdbStmt;Why put FORCE as the single item in an options list? A bool var seems
like it would be more clear and consistent.see discussion - it was one from Tom's objections. It is due possible
future enhancing or modification. It can looks strange, because now there
is only one option, but it allow very easy modifications. More it is
consistent with lot of other pg commands.
I can imagine so somebody will write support for concurrently dropping - so
this list will be longer
Show quoted text
- * DROP DATABASE [ IF EXISTS ] + * DROP DATABASE [ ( FORCE ) ] [ IF EXISTS ]Why is it `[ ( FORCE ) ]` instead of `[ FORCE ]`?
There are also places in the code that seem like extra () are around
FORCE. Like here:It was discussed before. FORCE is not reserved keyword, so inside list is
due safety against possible collisions.+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;", + (force ? " (FORCE) " : ""), + (if_exists ? "IF EXISTS " : ""), fmtId(dbname));And here:
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2'); +$node->issues_sql_like( + [ 'dropdb', '--force', 'foobar2' ], + qr/statement: DROP DATABASE (FORCE) foobar2/, + 'SQL DROP DATABASE (FORCE) run'); +I'll try to build and test the patch tomorrow.
Thanks,
*Ryan Lambert*
On Wed, Sep 18, 2019 at 8:30 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hi Pavel,
One Comment:
In the documentation we say drop database will fail after 60 seconds
<varlistentry>
<term><literal>FORCE</literal></term>
<listitem>
<para>
Attempt to terminate all existing connections to the target database.
</para>
<para>
This will fail, if current user has no permissions to terminate other
connections. Required permissions are the same as with
<literal>pg_terminate_backend</literal>, described
in <xref linkend="functions-admin-signal"/>.
This will also fail, if the connections do not terminate in 60 seconds.
</para>
</listitem>
</varlistentry>
But in TerminateOtherDBBackends:
foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+
+ (void) kill(pid, SIGTERM); /* ignore any error */
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
We check for any connected backends after sending kill signal in
CountOtherDBBackends and throw error immediately.
I had also tested this scenario to get the following error immediately:
test=# drop database (force) test1;
ERROR: database "test1" is being accessed by other users
DETAIL: There is 1 other session using the database.
I feel some change is required to keep documentation and code in sync.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
st 18. 9. 2019 v 5:59 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Sep 18, 2019 at 8:30 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Hi Pavel,
One Comment:
In the documentation we say drop database will fail after 60 seconds
<varlistentry>
<term><literal>FORCE</literal></term>
<listitem>
<para>
Attempt to terminate all existing connections to the target database.
</para>
<para>
This will fail, if current user has no permissions to terminate other
connections. Required permissions are the same as with
<literal>pg_terminate_backend</literal>, described
in <xref linkend="functions-admin-signal"/>.This will also fail, if the connections do not terminate in 60
seconds.
</para>
</listitem>
</varlistentry>
This is not valid. With FORCE flag the clients are closed immediately
But in TerminateOtherDBBackends: foreach (lc, pids) + { + int pid = lfirst_int(lc); + + (void) kill(pid, SIGTERM); /* ignore any error */ + } + + /* sleep 100ms */ + pg_usleep(100 * 1000L); + }We check for any connected backends after sending kill signal in
CountOtherDBBackends and throw error immediately.I had also tested this scenario to get the following error immediately:
test=# drop database (force) test1;
ERROR: database "test1" is being accessed by other users
DETAIL: There is 1 other session using the database.
sure - you cannot to kill self
I feel some change is required to keep documentation and code in sync.
I am waiting to Tom's reply about necessary rights. But the doc part is not
synced, and should be fixed.
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Wed, Sep 18, 2019 at 9:41 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
st 18. 9. 2019 v 5:59 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Sep 18, 2019 at 8:30 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hi Pavel,
One Comment:
In the documentation we say drop database will fail after 60 seconds
<varlistentry>
<term><literal>FORCE</literal></term>
<listitem>
<para>
Attempt to terminate all existing connections to the target database.
</para>
<para>
This will fail, if current user has no permissions to terminate other
connections. Required permissions are the same as with
<literal>pg_terminate_backend</literal>, described
in <xref linkend="functions-admin-signal"/>.This will also fail, if the connections do not terminate in 60 seconds.
</para>
</listitem>
</varlistentry>This is not valid. With FORCE flag the clients are closed immediately
But in TerminateOtherDBBackends: foreach (lc, pids) + { + int pid = lfirst_int(lc); + + (void) kill(pid, SIGTERM); /* ignore any error */ + } + + /* sleep 100ms */ + pg_usleep(100 * 1000L); + }We check for any connected backends after sending kill signal in
CountOtherDBBackends and throw error immediately.I had also tested this scenario to get the following error immediately:
test=# drop database (force) test1;
ERROR: database "test1" is being accessed by other users
DETAIL: There is 1 other session using the database.sure - you cannot to kill self
This was not a case where we try to do drop database from the same
session, I got this error when one of the process took longer time to
terminate the other connected process.
But this scenario was simulated using gdb, I'm not sure if similar
scenario is possible without gdb in production environment. If
terminating process does not happen immediately then the above
scenario can happen.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
st 18. 9. 2019 v 19:11 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Sep 18, 2019 at 9:41 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:st 18. 9. 2019 v 5:59 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Sep 18, 2019 at 8:30 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Hi Pavel,
One Comment:
In the documentation we say drop database will fail after 60 seconds
<varlistentry>
<term><literal>FORCE</literal></term>
<listitem>
<para>
Attempt to terminate all existing connections to the targetdatabase.
</para>
<para>
This will fail, if current user has no permissions to terminateother
connections. Required permissions are the same as with
<literal>pg_terminate_backend</literal>, described
in <xref linkend="functions-admin-signal"/>.This will also fail, if the connections do not terminate in 60
seconds.
</para>
</listitem>
</varlistentry>This is not valid. With FORCE flag the clients are closed immediately
But in TerminateOtherDBBackends: foreach (lc, pids) + { + int pid = lfirst_int(lc); + + (void) kill(pid, SIGTERM); /* ignore any error */ + } + + /* sleep 100ms */ + pg_usleep(100 * 1000L); + }We check for any connected backends after sending kill signal in
CountOtherDBBackends and throw error immediately.I had also tested this scenario to get the following error immediately:
test=# drop database (force) test1;
ERROR: database "test1" is being accessed by other users
DETAIL: There is 1 other session using the database.sure - you cannot to kill self
This was not a case where we try to do drop database from the same
session, I got this error when one of the process took longer time to
terminate the other connected process.
But this scenario was simulated using gdb, I'm not sure if similar
scenario is possible without gdb in production environment. If
terminating process does not happen immediately then the above
scenario can happen.
maybe - gdb can handle SIGTERM signal.
I think so current design should be ok, because it immediately send SIGTERM
signals and then try to check 5 sec if these signals are really processed.
If not, then it raise error, and do nothing.
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Hi
I am sending updated version - the changes against last patch are two. I
use pg_terminate_backed for killing other terminates like Tom proposed. I
am not sure if it is 100% practical - on second hand the necessary right to
kill other sessions is almost available - and consistency with
pg_terminal_backend has sense. More - native implementation is
significantly better then solution implemented outer. I fixed docs little
bit - the timeout for logout (as reaction on SIGTERM is only 5 sec).
Regards
Pavel
Attachments:
drop-database-force-20190918-1.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190918-1.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..11a31899d2 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 5 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..5d10ad1c92 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 95881a8550..faa5bdbf71 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -810,7 +810,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -895,6 +895,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..f7bab61175 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,56 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( FORCE ) ] [ IF EXISTS ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE opt_drop_option_list database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $3;
+ n->dbname = $4;
n->missing_ok = false;
+ n->options = $3;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE opt_drop_option_list IF_P EXISTS database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $5;
+ n->dbname = $6;
n->missing_ok = true;
+ n->options = $3;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ '(' drop_option_list ')'
+ {
+ $$ = $2;
+ }
+ | /* EMPTY */
+ {
+ $$ = NIL;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..035fc2628c 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -2970,6 +2970,50 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+
+ (void) DirectFunctionCall1(pg_terminate_backend, Int32GetDatum(pid));
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..d69e30972f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -598,10 +598,20 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_DropdbStmt:
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *item = (DefElem *) lfirst(lc);
+
+ if (strcmp(item->defname, "force") == 0)
+ force = true;
+ }
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..8bac1faa42 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,9 +128,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,10 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? " (FORCE) " : ""),
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -159,6 +165,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..8e1899a6c5 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE (FORCE) foobar2/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..66e0b904bf 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..e82f6c7c98 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -111,8 +111,8 @@ extern int CountDBBackends(Oid databaseid);
extern int CountDBConnections(Oid databaseid);
extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
extern int CountUserBackends(Oid roleid);
-extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+extern bool CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
On Thu, Sep 19, 2019 at 12:14 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hi
I am sending updated version - the changes against last patch are two. I use pg_terminate_backed for killing other terminates like Tom proposed. I am not sure if it is 100% practical - on second hand the necessary right to kill other sessions is almost available - and consistency with pg_terminal_backend has sense. More - native implementation is significantly better then solution implemented outer. I fixed docs little bit - the timeout for logout (as reaction on SIGTERM is only 5 sec).
Regards
Pavel
I observed one thing with the latest patch:
There is a possibility that in case of drop database failure some
sessions may be terminated.
It can happen in the following scenario:
1) create database test; /* super user creates test database */
2) create user test password 'test@123'; /* super user creates test user */
3) alter user test nosuperuser; /* super user changes test use to non
super user */
4) alter database test owner to test; /* super user set test as test
database owner */
5) psql -d test /* connect to test database as super user - Session 1 */
6) psql -d postgres -U test /* connect to test database as test user -
Session 2 */
7) psql -d postgres -U test /* connect to test database as test user -
Session 3 */
8) drop database (force) test; /* Executed from Session 3 */
Drop database fails in Session 3 with:
ERROR: must be a superuser to terminate superuser process
Session 2 is terminated, Session 1 is left as it is.
Is the above behaviour ok to terminate session 2 in case of drop
database failure?
Thoughts?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
čt 19. 9. 2019 v 13:52 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Thu, Sep 19, 2019 at 12:14 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Hi
I am sending updated version - the changes against last patch are two. I
use pg_terminate_backed for killing other terminates like Tom proposed. I
am not sure if it is 100% practical - on second hand the necessary right to
kill other sessions is almost available - and consistency with
pg_terminal_backend has sense. More - native implementation is
significantly better then solution implemented outer. I fixed docs little
bit - the timeout for logout (as reaction on SIGTERM is only 5 sec).Regards
Pavel
I observed one thing with the latest patch:
There is a possibility that in case of drop database failure some
sessions may be terminated.It can happen in the following scenario:
1) create database test; /* super user creates test database */
2) create user test password 'test@123'; /* super user creates test user
*/
3) alter user test nosuperuser; /* super user changes test use to non
super user */
4) alter database test owner to test; /* super user set test as test
database owner */5) psql -d test /* connect to test database as super user - Session 1 */
6) psql -d postgres -U test /* connect to test database as test user -
Session 2 */
7) psql -d postgres -U test /* connect to test database as test user -
Session 3 */8) drop database (force) test; /* Executed from Session 3 */
Drop database fails in Session 3 with:
ERROR: must be a superuser to terminate superuser processSession 2 is terminated, Session 1 is left as it is.
Is the above behaviour ok to terminate session 2 in case of drop
database failure?
Thoughts?
I agree so it's not nice behave. Again there are two possible solutions
1. strategy - owner can all - and don't check rigts
2. implement own check of rights instead using checks from
pg_terminate_backend.
It's easy fixable when we find a agreement what is preferred behave.
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Thu, Sep 19, 2019 at 6:44 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
I am sending updated version
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? " (FORCE) " : ""),
An extra space before (FORCE) caused the test to fail:
# 2019-09-19 19:07:22.203 UTC [1516] 050_dropdb.pl LOG: statement:
DROP DATABASE (FORCE) foobar2;
# doesn't match '(?^:statement: DROP DATABASE (FORCE) foobar2)'
--
Thomas Munro
https://enterprisedb.com
On Thu, Sep 19, 2019 at 11:41 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 19. 9. 2019 v 13:52 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Thu, Sep 19, 2019 at 12:14 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hi
I am sending updated version - the changes against last patch are two. I use pg_terminate_backed for killing other terminates like Tom proposed. I am not sure if it is 100% practical - on second hand the necessary right to kill other sessions is almost available - and consistency with pg_terminal_backend has sense. More - native implementation is significantly better then solution implemented outer. I fixed docs little bit - the timeout for logout (as reaction on SIGTERM is only 5 sec).
Regards
Pavel
I observed one thing with the latest patch:
There is a possibility that in case of drop database failure some
sessions may be terminated.It can happen in the following scenario:
1) create database test; /* super user creates test database */
2) create user test password 'test@123'; /* super user creates test user */
3) alter user test nosuperuser; /* super user changes test use to non
super user */
4) alter database test owner to test; /* super user set test as test
database owner */5) psql -d test /* connect to test database as super user - Session 1 */
6) psql -d postgres -U test /* connect to test database as test user -
Session 2 */
7) psql -d postgres -U test /* connect to test database as test user -
Session 3 */8) drop database (force) test; /* Executed from Session 3 */
Drop database fails in Session 3 with:
ERROR: must be a superuser to terminate superuser processSession 2 is terminated, Session 1 is left as it is.
Is the above behaviour ok to terminate session 2 in case of drop
database failure?
Thoughts?I agree so it's not nice behave. Again there are two possible solutions
1. strategy - owner can all - and don't check rigts
2. implement own check of rights instead using checks from pg_terminate_backend.It's easy fixable when we find a agreement what is preferred behave.
Pavel
For the above I felt, if we could identify if we don't have
permissions to terminate any of the connected session, throwing the
error at that point would be appropriate.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Thu, Sep 19, 2019 at 12:14 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hi
I am sending updated version - the changes against last patch are two. I use pg_terminate_backed for killing other terminates like Tom proposed. I am not sure if it is 100% practical - on second hand the necessary right to kill other sessions is almost available - and consistency with pg_terminal_backend has sense. More - native implementation is significantly better then solution implemented outer. I fixed docs little bit - the timeout for logout (as reaction on SIGTERM is only 5 sec).
Few comments on the patch.
@@ -598,10 +598,20 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_DropdbStmt:
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *item = (DefElem *) lfirst(lc);
+
+ if (strcmp(item->defname, "force") == 0)
+ force = true;
+ }
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, force);
If you see the createdb, then options are processed inside the
createdb function but here we are processing outside
the function. Wouldn't it be good to keep them simmilar and also it
will be expandable in case we add more options
in the future?
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,10 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? " (FORCE) " : ""),
+ (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+
I did not understand why you have moved this code after
connectMaintenanceDatabase function? I don't see any problem but in
earlier code
initPQExpBuffer and appendPQExpBuffer were together now you have moved
appendPQExpBuffer after the connectMaintenanceDatabase so if there is
no reason to do that then better to keep it how it was earlier.
-extern bool CountOtherDBBackends(Oid databaseId,
- int *nbackends, int *nprepared);
+extern bool CountOtherDBBackends(Oid databaseId, int *nbackends, int
*nprepared);
Unrelated change
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
pá 20. 9. 2019 v 7:58 odesílatel Dilip Kumar <dilipbalaut@gmail.com> napsal:
On Thu, Sep 19, 2019 at 12:14 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Hi
I am sending updated version - the changes against last patch are two. I
use pg_terminate_backed for killing other terminates like Tom proposed. I
am not sure if it is 100% practical - on second hand the necessary right to
kill other sessions is almost available - and consistency with
pg_terminal_backend has sense. More - native implementation is
significantly better then solution implemented outer. I fixed docs little
bit - the timeout for logout (as reaction on SIGTERM is only 5 sec).Few comments on the patch.
@@ -598,10 +598,20 @@ standard_ProcessUtility(PlannedStmt *pstmt, case T_DropdbStmt: { DropdbStmt *stmt = (DropdbStmt *) parsetree; + bool force = false; + ListCell *lc; + + foreach(lc, stmt->options) + { + DefElem *item = (DefElem *) lfirst(lc); + + if (strcmp(item->defname, "force") == 0) + force = true; + }/* no event triggers for global objects */ PreventInTransactionBlock(isTopLevel, "DROP DATABASE"); - dropdb(stmt->dbname, stmt->missing_ok); + dropdb(stmt->dbname, stmt->missing_ok, force);If you see the createdb, then options are processed inside the
createdb function but here we are processing outside
the function. Wouldn't it be good to keep them simmilar and also it
will be expandable in case we add more options
in the future?
I though about it, but I think so current state is good enough. There are
only few parameters - and I don't think significantly large number of new
options.
When number of parameters of any functions is not too high, then I think so
is better to have a function with classic list of parameters instead
function with one parameter of some struct type.
If somebody try to use function dropdb from outside (extension), then he
don't need to create new structure, new list, and simply call simple C API.
I prefer API of simple types against structures and nodes there. Just I
think so it's more practical of somebody try to use this function from
other places than from parser.
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;", - (if_exists ? "IF EXISTS " : ""), fmtId(dbname)); - /* Avoid trying to drop postgres db while we are connected to it. */ if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) maintenance_db = "template1"; @@ -134,6 +136,10 @@ main(int argc, char *argv[]) host, port, username, prompt_password, progname, echo); + appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;", + (force ? " (FORCE) " : ""), + (if_exists ? "IF EXISTS " : ""), fmtId(dbname)); +I did not understand why you have moved this code after
connectMaintenanceDatabase function? I don't see any problem but in
earlier code
initPQExpBuffer and appendPQExpBuffer were together now you have moved
appendPQExpBuffer after the connectMaintenanceDatabase so if there is
no reason to do that then better to keep it how it was earlier.
I am not a author of this change, but it is not necessary and I returned
original order
-extern bool CountOtherDBBackends(Oid databaseId, - int *nbackends, int *nprepared); +extern bool CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared);Unrelated change
fixed
Thank you for check. I am sending updated patch
Regards
Pavel
Show quoted text
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20190921-1.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190921-1.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..11a31899d2 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 5 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..5d10ad1c92 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 95881a8550..faa5bdbf71 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -810,7 +810,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -895,6 +895,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..f7bab61175 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,56 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( FORCE ) ] [ IF EXISTS ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE opt_drop_option_list database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $3;
+ n->dbname = $4;
n->missing_ok = false;
+ n->options = $3;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE opt_drop_option_list IF_P EXISTS database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $5;
+ n->dbname = $6;
n->missing_ok = true;
+ n->options = $3;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ '(' drop_option_list ')'
+ {
+ $$ = $2;
+ }
+ | /* EMPTY */
+ {
+ $$ = NIL;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..4a0bd5b76a 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,7 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2971,92 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections. This routine is used by
+ * DROP DATABASE FORCE to eliminate all others clients from
+ * database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rigts first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not to raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..d69e30972f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -598,10 +598,20 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_DropdbStmt:
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *item = (DefElem *) lfirst(lc);
+
+ if (strcmp(item->defname, "force") == 0)
+ force = true;
+ }
/* no event triggers for global objects */
PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
+ dropdb(stmt->dbname, stmt->missing_ok, force);
}
break;
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..1bb80fda74 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,7 +128,8 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? "(FORCE) " : ""),
(if_exists ? "IF EXISTS " : ""), fmtId(dbname));
/* Avoid trying to drop postgres db while we are connected to it. */
@@ -159,6 +165,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..1c54df2cb2 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE \(FORCE\) foobar2/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..66e0b904bf 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,7 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
pá 20. 9. 2019 v 5:10 odesílatel Thomas Munro <thomas.munro@gmail.com>
napsal:
On Thu, Sep 19, 2019 at 6:44 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:I am sending updated version
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;", + (force ? " (FORCE) " : ""),An extra space before (FORCE) caused the test to fail:
# 2019-09-19 19:07:22.203 UTC [1516] 050_dropdb.pl LOG: statement:
DROP DATABASE (FORCE) foobar2;# doesn't match '(?^:statement: DROP DATABASE (FORCE) foobar2)'
should be fixed now.
thank you for echo
Pavel
Show quoted text
--
Thomas Munro
https://enterprisedb.com
On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Thank you for check. I am sending updated patch
Alvaro has up thread suggested some alternative syntax [1]/messages/by-id/20190903164633.GA16408@alvherre.pgsql for this
patch, but I don't see any good argument to not go with what he has
proposed. In other words, why we should prefer the current syntax as
in the patch over what Alvaro has proposed?
IIUC, the current syntax implemented by the patch is:
Drop Database [(options)] [If Exists] name
Alvaro suggested using options at the end (and use optional keyword
WITH) based on what other Drop commands does. I see some merits to
that idea which are (a) if tomorrow we want to introduce new options
like CASCADE, RESTRICT then it will be better to have all the options
at the end as we have for other Drop commands, (b) It will resemble
more with Create Database syntax.
Now, I think the current syntax is also not bad and we already do
something like that for other commands like Vaccum where options are
provided before object_name, but I think in this case putting at the
end is more appealing unless there are some arguments against that.
One other minor comment:
+
+ This will also fail, if the connections do not terminate in 5 seconds.
+ </para>
Is there any implementation in the patch for the above note?
[1]: /messages/by-id/20190903164633.GA16408@alvherre.pgsql
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Sep 24, 2019 at 6:22 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Thank you for check. I am sending updated patch
Session termination in case of drop database is solved.
Some typos:
+ /*
+ * Similar code to pg_terminate_backend, but we check rigts first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not to raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
"rigts" should be "rights/privilege"
"should not to raise" could be "should not raise"
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Tue, Sep 24, 2019 at 6:22 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Thank you for check. I am sending updated patch
Alvaro has up thread suggested some alternative syntax [1] for this
patch, but I don't see any good argument to not go with what he has
proposed. In other words, why we should prefer the current syntax as
in the patch over what Alvaro has proposed?IIUC, the current syntax implemented by the patch is:
Drop Database [(options)] [If Exists] name
Alvaro suggested using options at the end (and use optional keyword
WITH) based on what other Drop commands does. I see some merits to
that idea which are (a) if tomorrow we want to introduce new options
like CASCADE, RESTRICT then it will be better to have all the options
at the end as we have for other Drop commands, (b) It will resemble
more with Create Database syntax.Now, I think the current syntax is also not bad and we already do
something like that for other commands like Vaccum where options are
provided before object_name, but I think in this case putting at the
end is more appealing unless there are some arguments against that.One other minor comment: + + This will also fail, if the connections do not terminate in 5 seconds. + </para>Is there any implementation in the patch for the above note?
One more point I would like to add here is that I think it is worth
considering to split this patch by keeping the changes in dropdb
utility as a separate patch. Even though the code is not very much
but I think it can be a separate patch atop the main patch which
contains the core server changes.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
fixed
Thank you for check. I am sending updated patch
Thanks for fixing all the comments.
Couple of suggestions:
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ ( <replaceable class="parameter">option</replaceable>
[, ...] ) ] [ IF EXISTS ] <replaceable
class="parameter">name</replaceable>
+
+<phrase>where <replaceable class="parameter">option</replaceable> can
be one of:</phrase>
+
+ FORCE
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
Currently only force option is supported in: drop database (force) dbname
Should we have a list or a single element for this?
I'm not sure if we have any plans to add more option in this area.
If possible we can add an error message like 'ERROR: unrecognized
drop database option "force1"' if an invalid input is given.
The above error message will be inline with error message of vacuum's
error message whose syntax is similar to the current feature.
We could throw the error from here:
case T_DropdbStmt:
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *item = (DefElem *) lfirst(lc);
+
+ if (strcmp(item->defname, "force") == 0)
+ force = true;
+ }
Thoughts?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
út 24. 9. 2019 v 14:52 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Thank you for check. I am sending updated patch
Alvaro has up thread suggested some alternative syntax [1] for this
patch, but I don't see any good argument to not go with what he has
proposed. In other words, why we should prefer the current syntax as
in the patch over what Alvaro has proposed?IIUC, the current syntax implemented by the patch is:
Drop Database [(options)] [If Exists] name
Alvaro suggested using options at the end (and use optional keyword
WITH) based on what other Drop commands does. I see some merits to
that idea which are (a) if tomorrow we want to introduce new options
like CASCADE, RESTRICT then it will be better to have all the options
at the end as we have for other Drop commands, (b) It will resemble
more with Create Database syntax.Now, I think the current syntax is also not bad and we already do
something like that for other commands like Vaccum where options are
provided before object_name, but I think in this case putting at the
end is more appealing unless there are some arguments against that.
Originally it was placed after name. Tom's objection was possibility to
collision with some future standards and requirement to be implemented safe.
cite: "* I'm concerned that the proposed syntax is not future-proof.
FORCE is not a reserved word, and we surely don't want to make
it one; but just appending it to the end of the command without
any decoration seems like a recipe for problems if anybody wants
to add other options later. (Possible examples: RESTRICT/CASCADE,
or a user-defined timeout.) Maybe some parentheses would help?
Or possibly I'm being overly paranoid, but ..."
When I use parenthesis, then current placement looks correct - and it is
well known syntax already.
Alternative is DROP DATABASE [IF EXISTS] name [ CASCADE | RESTRICT ] [ WITH
FORCE ]
but in this case WIDTH keyword should not be optional (If I need to solve
Tom's note). Currently WITH keyword is optional every where, so I think so
using syntax with required WIDTH keyword is not happy.
When I looks to other statement, then the most similar case is DROP INDEX
CONCURRENTLY ... so most consistent syntax is DROP DATABASE FORCE ... or
DROP DATABASE (FORCE, ..)
Optional syntax can be (similar to CREATE USER MAPPING - but it looks like
too verbose
DROP DATABASE xxx OPTIONS (FORCE, ...)
It's easy to change syntax, and I'll do it - I have not strong preferences,
but If wouldn't to increase Tom's paranoia, I think so current syntax is
most common in pg, and well known.
What do you think about it?
One other minor comment: + + This will also fail, if the connections do not terminate in 5 seconds. + </para>Is there any implementation in the patch for the above note?
Yes, is there.
The force flag ensure sending SIGTERM to related clients. Nothing more.
There are still check
-->if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
<--><-->ereport(ERROR,
<--><--><--><-->(errcode(ERRCODE_OBJECT_IN_USE),
<--><--><--><--> errmsg("database \"%s\" is being accessed by other users",
<--><--><--><--><--><-->dbname),
<--><--><--><--> errdetail_busy_db(notherbackends, npreparedxacts)));
that can fails after 5 sec. Sending signal doesn't ensure nothing, so I am
for no changes in these check.
Regards
Pavel
Show quoted text
[1] -
/messages/by-id/20190903164633.GA16408@alvherre.pgsql--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
út 24. 9. 2019 v 17:51 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Tue, Sep 24, 2019 at 6:22 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Thank you for check. I am sending updated patch
Session termination in case of drop database is solved. Some typos: + /* + * Similar code to pg_terminate_backend, but we check rigts first + * here, and only when we have all necessary rights we start to + * terminate other clients. In this case we should not to raise + * some warnings - like "PID %d is not a PostgreSQL server process", + * because for this purpose - already finished session is not + * problem. + */ "rigts" should be "rights/privilege" "should not to raise" could be "should not raise"
fixed
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
st 25. 9. 2019 v 6:38 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:fixed
Thank you for check. I am sending updated patch
Thanks for fixing all the comments. Couple of suggestions: -DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> +DROP DATABASE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ IF EXISTS ] <replaceable class="parameter">name</replaceable> + +<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase> + + FORCE+drop_option_list: + drop_option + { + $$ = list_make1((Node *) $1); + } + | drop_option_list ',' drop_option + { + $$ = lappend($1, (Node *) $3); + } + ; + +drop_option: FORCE + { + $$ = makeDefElem("force", NULL, @1); + } + ;Currently only force option is supported in: drop database (force) dbname
Should we have a list or a single element for this?
I'm not sure if we have any plans to add more option in this area.
If we open door for next possible syntax enhancing and it is motivation for
this syntax, then we should to use pattern for this situation.
It looks little bit strange, but I think so current code is very well
readable. I wrote comment, so only one flag is supported now, but
syntax allows add other flags. I don't think so using defelem directly
reduces significantly enough lines - just if we implement some
what looks like possible list, then we should to use lists inside.
If possible we can add an error message like 'ERROR: unrecognized drop database option "force1"' if an invalid input is given. The above error message will be inline with error message of vacuum's error message whose syntax is similar to the current feature. We could throw the error from here: case T_DropdbStmt: { DropdbStmt *stmt = (DropdbStmt *) parsetree; + bool force = false; + ListCell *lc; + + foreach(lc, stmt->options) + { + DefElem *item = (DefElem *) lfirst(lc); + + if (strcmp(item->defname, "force") == 0) + force = true; + } Thoughts?
I moved this check to separate function DropDatabase with new check and
exception like you proposed.
Regards
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20190926.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190926.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..11a31899d2 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 5 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 95881a8550..1a94671cdc 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -810,7 +810,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -895,6 +895,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1002,6 +1005,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..efe67234a2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,60 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( options ) ] [ IF EXISTS ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE opt_drop_option_list database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $3;
+ n->dbname = $4;
n->missing_ok = false;
+ n->options = $3;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE opt_drop_option_list IF_P EXISTS database_name
{
DropdbStmt *n = makeNode(DropdbStmt);
- n->dbname = $5;
+ n->dbname = $6;
n->missing_ok = true;
+ n->options = $3;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ '(' drop_option_list ')'
+ {
+ $$ = $2;
+ }
+ | /* EMPTY */
+ {
+ $$ = NIL;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..e3ae5bd936 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,7 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2971,92 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections. This routine is used by
+ * DROP DATABASE FORCE to eliminate all others clients from
+ * database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..1bb80fda74 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,7 +128,8 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? "(FORCE) " : ""),
(if_exists ? "IF EXISTS " : ""), fmtId(dbname));
/* Avoid trying to drop postgres db while we are connected to it. */
@@ -159,6 +165,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
st 25. 9. 2019 v 4:14 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Tue, Sep 24, 2019 at 6:22 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:On Sat, Sep 21, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Thank you for check. I am sending updated patch
Alvaro has up thread suggested some alternative syntax [1] for this
patch, but I don't see any good argument to not go with what he has
proposed. In other words, why we should prefer the current syntax as
in the patch over what Alvaro has proposed?IIUC, the current syntax implemented by the patch is:
Drop Database [(options)] [If Exists] name
Alvaro suggested using options at the end (and use optional keyword
WITH) based on what other Drop commands does. I see some merits to
that idea which are (a) if tomorrow we want to introduce new options
like CASCADE, RESTRICT then it will be better to have all the options
at the end as we have for other Drop commands, (b) It will resemble
more with Create Database syntax.Now, I think the current syntax is also not bad and we already do
something like that for other commands like Vaccum where options are
provided before object_name, but I think in this case putting at the
end is more appealing unless there are some arguments against that.One other minor comment: + + This will also fail, if the connections do not terminate in 5seconds.
+ </para>
Is there any implementation in the patch for the above note?
One more point I would like to add here is that I think it is worth
considering to split this patch by keeping the changes in dropdb
utility as a separate patch. Even though the code is not very much
but I think it can be a separate patch atop the main patch which
contains the core server changes.
I did it - last patch contains server side only. I expect so client side
(very small patch) can be next.
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On 2019-Sep-26, Pavel Stehule wrote:
Alternative is DROP DATABASE [IF EXISTS] name [ CASCADE | RESTRICT ] [ WITH
FORCE ]but in this case WIDTH keyword should not be optional (If I need to solve
Tom's note). Currently WITH keyword is optional every where, so I think so
using syntax with required WIDTH keyword is not happy.
Well, you would have one of those:
DROP DATABASE [IF EXISTS] name WITH (FORCE)
DROP DATABASE [IF EXISTS] name
Naturally, the WITH is optional in the sense that the clause itself is
optional. (Note we don't have CASCADE/RESTRICT in DROP DATABASE.)
You propose
DROP DATABASE (FORCE) [IF EXISTS] name
which seems weird to me -- I think only legacy syntax uses that form.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
čt 26. 9. 2019 v 17:35 odesílatel Alvaro Herrera <alvherre@2ndquadrant.com>
napsal:
On 2019-Sep-26, Pavel Stehule wrote:
Alternative is DROP DATABASE [IF EXISTS] name [ CASCADE | RESTRICT ] [
WITH
FORCE ]
but in this case WIDTH keyword should not be optional (If I need to solve
Tom's note). Currently WITH keyword is optional every where, so I thinkso
using syntax with required WIDTH keyword is not happy.
Well, you would have one of those:
DROP DATABASE [IF EXISTS] name WITH (FORCE)
DROP DATABASE [IF EXISTS] nameNaturally, the WITH is optional in the sense that the clause itself is
optional. (Note we don't have CASCADE/RESTRICT in DROP DATABASE.)You propose
DROP DATABASE (FORCE) [IF EXISTS] name
which seems weird to me -- I think only legacy syntax uses that form.
I have not strong opinion about it, little bit prefer option list after
DROP DATABASE, because it is some what I know from EXPLAIN ANALYZE daily
work, but it is not too important. Your proposed syntax is ok.
Second patch implements Alvaro's proposed syntax.
Pavel
Show quoted text
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
drop-database-force-20190926-2.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20190926-2.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..2c3857718f 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ - unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,24 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail, if the connections do not terminate in 5 seconds.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 95881a8550..1a94671cdc 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -810,7 +810,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -895,6 +895,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1002,6 +1005,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..466e1a107e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,54 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( options ) ] [ IF EXISTS ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE database_name opt_drop_option_list
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = $4;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE IF_P EXISTS database_name opt_drop_option_list
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = $6;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ WITH '(' drop_option_list ')' { $$ = $3; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..e3ae5bd936 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,7 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2971,92 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections. This routine is used by
+ * DROP DATABASE FORCE to eliminate all others clients from
+ * database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..1bb80fda74 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,7 +128,8 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (force ? "(FORCE) " : ""),
(if_exists ? "IF EXISTS " : ""), fmtId(dbname));
/* Avoid trying to drop postgres db while we are connected to it. */
@@ -159,6 +165,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
On 2019-09-26 17:35, Alvaro Herrera wrote:
Well, you would have one of those:
DROP DATABASE [IF EXISTS] name WITH (FORCE)
DROP DATABASE [IF EXISTS] nameNaturally, the WITH is optional in the sense that the clause itself is
optional. (Note we don't have CASCADE/RESTRICT in DROP DATABASE.)
The WITH here seems weird to me. Why not leave it out?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
čt 26. 9. 2019 v 18:34 odesílatel Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> napsal:
On 2019-09-26 17:35, Alvaro Herrera wrote:
Well, you would have one of those:
DROP DATABASE [IF EXISTS] name WITH (FORCE)
DROP DATABASE [IF EXISTS] nameNaturally, the WITH is optional in the sense that the clause itself is
optional. (Note we don't have CASCADE/RESTRICT in DROP DATABASE.)The WITH here seems weird to me. Why not leave it out?
it is just my subjective opinion so it looks better with it than without
it.
so there are three variants
DROP DATABASE ( FORCE) name;
DROP DATABASE name (FORCE)
DROP DATABASE name WITH (FORCE)
It is true so in this case it is just syntactic sugar
Maybe
DROP DATABASE name [[ WITH ] OPTIONS( FORCE ) ] ?
It looks well for me
DROP DATABASE test WITH OPTIONS (FORCE)
DROP DATABASE test OPTIONS (FORCE)
?
Show quoted text
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Thu, Sep 26, 2019 at 10:04 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 2019-09-26 17:35, Alvaro Herrera wrote:
Well, you would have one of those:
DROP DATABASE [IF EXISTS] name WITH (FORCE)
DROP DATABASE [IF EXISTS] nameNaturally, the WITH is optional in the sense that the clause itself is
optional. (Note we don't have CASCADE/RESTRICT in DROP DATABASE.)The WITH here seems weird to me. Why not leave it out?
Yeah, we can leave it as well. However, other commands like COPY
seems to be using WITH clause for a somewhat similar purpose. I think
we use WITH clause in other cases while specifying multiple options.
So to me, using WITH here doesn't sound to be a bad idea.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Sep 26, 2019 at 7:18 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
út 24. 9. 2019 v 14:52 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:One other minor comment: + + This will also fail, if the connections do not terminate in 5 seconds. + </para>Is there any implementation in the patch for the above note?
Yes, is there.
The force flag ensure sending SIGTERM to related clients. Nothing more.
There are still check-->if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
<--><-->ereport(ERROR,
<--><--><--><-->(errcode(ERRCODE_OBJECT_IN_USE),
<--><--><--><--> errmsg("database \"%s\" is being accessed by other users",
<--><--><--><--><--><-->dbname),
<--><--><--><--> errdetail_busy_db(notherbackends, npreparedxacts)));that can fails after 5 sec. Sending signal doesn't ensure nothing, so I am
for no changes in these check.
I think 5 seconds is a hard-coded value that can even change in the
future. So, it is better to write something more generic like "This will
also fail if we are not able to terminate connections" or something like
that. This part of the documentation might change after addressing the
other comments below.
One more point I would like to add here is that I think it is worth
considering to split this patch by keeping the changes in dropdb
utility as a separate patch. Even though the code is not very much
but I think it can be a separate patch atop the main patch which
contains the core server changes.
I did it - last patch contains server side only. I expect so client side
(very small patch) can be nex
I still see the code related to dropdb utility in the patch. See,
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..1bb80fda74 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
Few other comments:
--------------------------------
1.
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ }
So here we are terminating only connection which doesn't have any prepared
transaction. The behavior of this patch with the prepared transactions
will be terrible. Basically, after terminating all the connections via
TerminateOtherDBBackends, we will give error once CountOtherDBBackends is
invoked. I have tested the same and it gives an error like below:
postgres=# drop database db1 With (Force);
ERROR: database "db1" is being accessed by other users
DETAIL: There is 1 prepared transaction using the database.
I think we have two options here:
(a) Document that even with force option, if there are any prepared
transactions in the same database, we won't drop the database. Along with
this, fix the code such that we don't terminate other connections if the
prepared transactions are active.
(b) Rollback the prepared transactions.
I think (a) is a better option because if the number of prepared
transactions is large, then it might take time and I am not sure if it is
worth to add the complexity of rolling back all the prepared xacts. OTOH,
if you or others think option (b) is good and doesn't add much complexity,
then I think it is worth exploring that option.
I think we should definitely do something to deal with this even if you
don't like the proposed options because the current behavior of the patch
seems worse than either of these options.
2.
-DROP DATABASE [ IF EXISTS ] <replaceable
class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable
class="parameter">name</replaceable> [ WITH ( <replaceable
class="parameter">option</replaceable> [, ...] ) ]
It is better to keep WITH clause as optional similar to Copy command. This
might address Peter E's concern related to WITH clause.
3.
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ ( options ) ] [ IF EXISTS ]
You seem to forget to change the syntax in the above comments after
changing the patch.
4.
+ If anyone else is connected to the target database, this command will
fail
+ - unless you use the <literal>FORCE</literal> option described below.
I don't understand the significance of using '-' before unless. I think we
can remove it.
Changed the patch status as 'Waiting on Author'.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
st 2. 10. 2019 v 5:20 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Thu, Sep 26, 2019 at 7:18 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:út 24. 9. 2019 v 14:52 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:One other minor comment: + + This will also fail, if the connections do not terminate in 5 seconds. + </para>Is there any implementation in the patch for the above note?
Yes, is there.
The force flag ensure sending SIGTERM to related clients. Nothing more.
There are still check-->if (CountOtherDBBackends(db_id, ¬herbackends, &npreparedxacts))
<--><-->ereport(ERROR,
<--><--><--><-->(errcode(ERRCODE_OBJECT_IN_USE),
<--><--><--><--> errmsg("database \"%s\" is being accessed by other
users",
<--><--><--><--><--><-->dbname),
<--><--><--><--> errdetail_busy_db(notherbackends, npreparedxacts)));that can fails after 5 sec. Sending signal doesn't ensure nothing, so I
am for no changes in these check.I think 5 seconds is a hard-coded value that can even change in the
future. So, it is better to write something more generic like "This will
also fail if we are not able to terminate connections" or something like
that. This part of the documentation might change after addressing the
other comments below.
done
One more point I would like to add here is that I think it is worth
considering to split this patch by keeping the changes in dropdb
utility as a separate patch. Even though the code is not very much
but I think it can be a separate patch atop the main patch which
contains the core server changes.I did it - last patch contains server side only. I expect so client side
(very small patch) can be nex
I still see the code related to dropdb utility in the patch. See, diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c index dacd8e5f1d..1bb80fda74 100644 --- a/src/bin/scripts/dropdb.c +++ b/src/bin/scripts/dropdb.c @@ -34,6 +34,7 @@ main(int argc, char *argv[]) {"interactive", no_argument, NULL, 'i'}, {"if-exists", no_argument, &if_exists, 1}, {"maintenance-db", required_argument, NULL, 2}, + {"force", no_argument, NULL, 'f'}, {NULL, 0, NULL, 0} };
removed
Few other comments: -------------------------------- 1. +void +TerminateOtherDBBackends(Oid databaseId) +{ + ProcArrayStruct *arrayP = procArray; + List *pids = NIL; + int i; + + LWLockAcquire(ProcArrayLock, LW_SHARED); + + for (i = 0; i < procArray->numProcs; i++) + { + int pgprocno = arrayP->pgprocnos[i]; + PGPROC *proc = &allProcs[pgprocno]; + + if (proc->databaseId != databaseId) + continue; + if (proc == MyProc) + continue; + + if (proc->pid != 0) + pids = lappend_int(pids, proc->pid); + }So here we are terminating only connection which doesn't have any prepared
transaction. The behavior of this patch with the prepared transactions
will be terrible. Basically, after terminating all the connections via
TerminateOtherDBBackends, we will give error once CountOtherDBBackends is
invoked. I have tested the same and it gives an error like below:postgres=# drop database db1 With (Force);
ERROR: database "db1" is being accessed by other users
DETAIL: There is 1 prepared transaction using the database.I think we have two options here:
(a) Document that even with force option, if there are any prepared
transactions in the same database, we won't drop the database. Along with
this, fix the code such that we don't terminate other connections if the
prepared transactions are active.
(b) Rollback the prepared transactions.
I not use prepared transactions often, and then I have not own strong
opinion about it. Original parch didn't touch this area, so I think we can
continue in this direction (minimally for start).
I did precheck of opened prepared transactions, and when I find any opened,
then I raise a exception (before when I try to terminate other processes).
I updated doc about possible stops.
I think (a) is a better option because if the number of prepared
transactions is large, then it might take time and I am not sure if it is
worth to add the complexity of rolling back all the prepared xacts. OTOH,
if you or others think option (b) is good and doesn't add much complexity,
then I think it is worth exploring that option.I think we should definitely do something to deal with this even if you
don't like the proposed options because the current behavior of the patch
seems worse than either of these options.2. -DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> +DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ WITH ( <replaceable class="parameter">option</replaceable> [, ...] ) ]It is better to keep WITH clause as optional similar to Copy command.
This might address Peter E's concern related to WITH clause.
WITH keyword is optional now
3. - * DROP DATABASE [ IF EXISTS ] + * DROP DATABASE [ ( options ) ] [ IF EXISTS ]
fixed
You seem to forget to change the syntax in the above comments after
changing the patch.4. + If anyone else is connected to the target database, this command will fail + - unless you use the <literal>FORCE</literal> option described below.I don't understand the significance of using '-' before unless. I think
we can remove it.
fixed
Changed the patch status as 'Waiting on Author'.
Thank you for careful review. I hope so all issues are out.
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191002.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20191002.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..11d0be0eb5 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,28 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate prepared transactions or active logical replication
+ slot(s).
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail if we are not able to terminate connections or
+ when there are active prepared transactions or active logical replication
+ slots.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..8e62359b4d 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1006,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..25f4d7fdd6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,54 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE database_name opt_drop_option_list
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = $4;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE IF_P EXISTS database_name opt_drop_option_list
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = $6;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ opt_with '(' drop_option_list ')' { $$ = $3; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..16a4cd13f0 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,102 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections. This routine is used by
+ * DROP DATABASE FORCE to eliminate all others clients from
+ * database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is used by %d prepared transaction(s)",
+ get_database_name(databaseId),
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
On Wed, Oct 2, 2019 at 10:21 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Thank you for careful review. I hope so all issues are out.
Thanks Pavel for fixing the comments.
Few comments:
The else part cannot be hit in DropDatabase function as gram.y expects FORCE.
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
We should change gram.y to accept any keyword and throw error from
DropDatabase function.
+ */
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
"This will also fail" should be "This will fail"
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will also fail if we are not able to terminate connections or
+ when there are active prepared transactions or active logical replication
+ slots.
+ </para>
Can we add few tests for this feature.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Thu, Oct 3, 2019 at 11:18 PM vignesh C <vignesh21@gmail.com> wrote:
On Wed, Oct 2, 2019 at 10:21 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Thank you for careful review. I hope so all issues are out.
Thanks Pavel for fixing the comments. Few comments: The else part cannot be hit in DropDatabase function as gram.y expects FORCE. + + if (strcmp(opt->defname, "force") == 0) + force = true; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location))); + } +We should change gram.y to accept any keyword and throw error from DropDatabase function. + */ +drop_option: FORCE + { + $$ = makeDefElem("force", NULL, @1); + } + ;"This will also fail" should be "This will fail" + <para> + This will fail, if current user has no permissions to terminate other + connections. Required permissions are the same as with + <literal>pg_terminate_backend</literal>, described + in <xref linkend="functions-admin-signal"/>. + + This will also fail if we are not able to terminate connections or + when there are active prepared transactions or active logical replication + slots. + </para>Can we add few tests for this feature.
Couple of minor comments:
DBNAME can be included
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] [ [ WITH ] ( options ) ]
can be
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] DBNAME [ [ WITH ] ( options ) ]
Should we include dbname in the below also?
+DROP DATABASE [ IF EXISTS ] <replaceable
class="parameter">name</replaceable> [ [ WITH ] ( <replaceable
class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can
be one of:</phrase>
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
čt 3. 10. 2019 v 19:48 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Oct 2, 2019 at 10:21 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Thank you for careful review. I hope so all issues are out.
Thanks Pavel for fixing the comments. Few comments: The else part cannot be hit in DropDatabase function as gram.y expects FORCE. + + if (strcmp(opt->defname, "force") == 0) + force = true; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location))); + } +
I know - but somebody can call DropDatabase function outside parser. So is
better check all possibilities.
We should change gram.y to accept any keyword and throw error from DropDatabase function. + */ +drop_option: FORCE + { + $$ = makeDefElem("force", NULL, @1); + } + ;
I spent some time with thinking about it, and I think so this variant (with
keyword) is well readable and very illustrative. This will be lost with
generic variant.
When the keyword FORCE already exists, then I prefer current state.
"This will also fail" should be "This will fail" + <para> + This will fail, if current user has no permissions to terminate other + connections. Required permissions are the same as with + <literal>pg_terminate_backend</literal>, described + in <xref linkend="functions-admin-signal"/>. + + This will also fail if we are not able to terminate connections or + when there are active prepared transactions or active logical replication + slots. + </para>
fixed
Can we add few tests for this feature.
there are not any other test for DROP DATABASE
We can check syntax later inside second patch (for -f option of dropdb
command)
Regards
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191004.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20191004.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..9b9cabe71c 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,28 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate prepared transactions or active logical replication
+ slot(s).
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will fail if we are not able to terminate connections or
+ when there are active prepared transactions or active logical replication
+ slots.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..8e62359b4d 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1006,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..b823620852 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list opt_drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,27 +10215,54 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
-DropdbStmt: DROP DATABASE database_name
+DropdbStmt: DROP DATABASE database_name opt_drop_option_list
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = $4;
$$ = (Node *)n;
}
- | DROP DATABASE IF_P EXISTS database_name
+ | DROP DATABASE IF_P EXISTS database_name opt_drop_option_list
{
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = $6;
$$ = (Node *)n;
}
;
+opt_drop_option_list:
+ opt_with '(' drop_option_list ')' { $$ = $3; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
+ ;
+
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 8abcfdf841..16a4cd13f0 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,102 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other db connections. This routine is used by
+ * DROP DATABASE FORCE to eliminate all others clients from
+ * database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is used by %d prepared transaction(s)",
+ get_database_name(databaseId),
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
On Fri, Oct 4, 2019 at 9:54 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 3. 10. 2019 v 19:48 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Oct 2, 2019 at 10:21 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Thank you for careful review. I hope so all issues are out.
Thanks Pavel for fixing the comments. Few comments: The else part cannot be hit in DropDatabase function as gram.y expects FORCE. + + if (strcmp(opt->defname, "force") == 0) + force = true; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location))); + } +I know - but somebody can call DropDatabase function outside parser. So is better check all possibilities.
We should change gram.y to accept any keyword and throw error from DropDatabase function. + */ +drop_option: FORCE + { + $$ = makeDefElem("force", NULL, @1); + } + ;I spent some time with thinking about it, and I think so this variant (with keyword) is well readable and very illustrative. This will be lost with generic variant.
When the keyword FORCE already exists, then I prefer current state.
"This will also fail" should be "This will fail" + <para> + This will fail, if current user has no permissions to terminate other + connections. Required permissions are the same as with + <literal>pg_terminate_backend</literal>, described + in <xref linkend="functions-admin-signal"/>. + + This will also fail if we are not able to terminate connections or + when there are active prepared transactions or active logical replication + slots. + </para>fixed
Can we add few tests for this feature.
there are not any other test for DROP DATABASE
We can check syntax later inside second patch (for -f option of dropdb command)
Changes in this patch looks fine to me.
I'm not sure if we have forgotten to miss attaching the second patch
or can you provide the link to second patch.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
ne 6. 10. 2019 v 10:19 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Fri, Oct 4, 2019 at 9:54 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 3. 10. 2019 v 19:48 odesílatel vignesh C <vignesh21@gmail.com>
napsal:
On Wed, Oct 2, 2019 at 10:21 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Thank you for careful review. I hope so all issues are out.
Thanks Pavel for fixing the comments.
Few comments:
The else part cannot be hit in DropDatabase function as gram.y expectsFORCE.
+ + if (strcmp(opt->defname, "force") == 0) + force = true; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location))); + } +I know - but somebody can call DropDatabase function outside parser. So
is better check all possibilities.
We should change gram.y to accept any keyword and throw error from DropDatabase function. + */ +drop_option: FORCE + { + $$ = makeDefElem("force", NULL, @1); + } + ;I spent some time with thinking about it, and I think so this variant
(with keyword) is well readable and very illustrative. This will be lost
with generic variant.When the keyword FORCE already exists, then I prefer current state.
"This will also fail" should be "This will fail" + <para> + This will fail, if current user has no permissions to terminateother
+ connections. Required permissions are the same as with + <literal>pg_terminate_backend</literal>, described + in <xref linkend="functions-admin-signal"/>. + + This will also fail if we are not able to terminate connectionsor
+ when there are active prepared transactions or active logical
replication
+ slots.
+ </para>fixed
Can we add few tests for this feature.
there are not any other test for DROP DATABASE
We can check syntax later inside second patch (for -f option of dropdb
command)
Changes in this patch looks fine to me.
I'm not sure if we have forgotten to miss attaching the second patch
or can you provide the link to second patch.
I plan to work on the second patch after committing of this first. Now we
are early on commit fest, and the complexity of this or second patch is not
too high be necessary to prepare patch series.
Regards
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Fri, Oct 4, 2019 at 9:54 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Can we add few tests for this feature.
there are not any other test for DROP DATABASE
I think there are no dedicated tests for it but in a few tests, we use
it like in contrib\sepgsql\sql\alter.sql. I am not sure if we can
write a predictable test for force option because it will never be
guaranteed to drop the database in the presence of other active
sessions.
Few more comments:
---------------------------------
1.
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is used by %d prepared transaction(s)",
+ get_database_name(databaseId),
+ nprepared)));
+
You need to use errdetail_plural here to avoid translation problems,
see docs[1] for a detailed explanation. You can use function
errdetail_busy_db. Also, the indentation is not proper.
2.
TerminateOtherDBBackends()
{
..
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
..
}
So here we are sending SIGTERM to all the processes and wait for 100ms
to allow them to exit. Have you tested this with many processes? I
think we can create 100~500 sessions or maybe more to the database
being dropped and see what is behavior? One thing to notice is that
in function CountOtherDBBackends() we are sending SIGTERM to 10
autovacuum processes at-a-time. That has been introduced in commit
4abd7b49f1e9, but the reason for the same is not mentioned. I am not
sure if it is to avoid sending SIGTERM to many processes in quick
succession.
I think there should be more comments atop TerminateOtherDBBackends to
explain the working of it and some assumptions of that function.
3.
+opt_drop_option_list:
+ opt_with '(' drop_option_list ')' { $$ = $3; }
+ | /* EMPTY */
I think it is better to keep opt_with as part of the main syntax
rather than clubbing it with drop_option_list as we have in other
cases in the code.
4.
+drop_option: FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
We generally keep the option name "FORCE" in the new line.
5. I think it is better if can support tab-completion for this feature.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
so 19. 10. 2019 v 12:37 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Fri, Oct 4, 2019 at 9:54 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Can we add few tests for this feature.
there are not any other test for DROP DATABASE
I think there are no dedicated tests for it but in a few tests, we use
it like in contrib\sepgsql\sql\alter.sql. I am not sure if we can
write a predictable test for force option because it will never be
guaranteed to drop the database in the presence of other active
sessions.
done - I push tests to /tests/regress/psql.sql
Few more comments: --------------------------------- 1. + if (nprepared > 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("database \"%s\" is used by %d prepared transaction(s)", + get_database_name(databaseId), + nprepared))); +You need to use errdetail_plural here to avoid translation problems,
see docs[1] for a detailed explanation. You can use function
errdetail_busy_db. Also, the indentation is not proper.
fixed
2. TerminateOtherDBBackends() { .. + /* We know so we have all necessary rights now */ + foreach (lc, pids) + { + int pid = lfirst_int(lc); + PGPROC *proc = BackendPidGetProc(pid); + + if (proc != NULL) + { + /* If we have setsid(), signal the backend's whole process group */ +#ifdef HAVE_SETSID + (void) kill(-pid, SIGTERM); +#else + (void) kill(pid, SIGTERM); +#endif + } + } + + /* sleep 100ms */ + pg_usleep(100 * 1000L); .. }So here we are sending SIGTERM to all the processes and wait for 100ms
to allow them to exit. Have you tested this with many processes? I
think we can create 100~500 sessions or maybe more to the database
being dropped and see what is behavior? One thing to notice is that
in function CountOtherDBBackends() we are sending SIGTERM to 10
autovacuum processes at-a-time. That has been introduced in commit
4abd7b49f1e9, but the reason for the same is not mentioned. I am not
sure if it is to avoid sending SIGTERM to many processes in quick
succession.
I tested it on linux Linux nemesis
5.3.6-300.fc31.x86_64 #1 SMP Mon Oct 14 12:26:42 UTC 2019 x86_64 x86_64
x86_64 GNU/Linux
Tested with 1800 connections without any problem (under low load (only
pg_sleep was called).
I think there should be more comments atop TerminateOtherDBBackends to
explain the working of it and some assumptions of that function.
done
3. +opt_drop_option_list: + opt_with '(' drop_option_list ')' { $$ = $3; } + | /* EMPTY */I think it is better to keep opt_with as part of the main syntax
rather than clubbing it with drop_option_list as we have in other
cases in the code.
done
4. +drop_option: FORCE + { + $$ = makeDefElem("force", NULL, @1); + } + ;We generally keep the option name "FORCE" in the new line.
done
5. I think it is better if can support tab-completion for this feature.
done
I am sending fresh patch
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191019.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20191019.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..9b9cabe71c 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,28 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate prepared transactions or active logical replication
+ slot(s).
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will fail if we are not able to terminate connections or
+ when there are active prepared transactions or active logical replication
+ slots.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..8e62359b4d 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1006,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..5131099a85 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da53074b1..b782421864 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,114 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other connections to specified database. This routine
+ * is used by DROP DATABASE FORCE to eliminate all others clients from
+ * database to be possible safely drop database.
+ *
+ * It does three steps:
+ *
+ * 1. Collects processes on specified database, and stops
+ * when any process is a prepared transaction.
+ *
+ * 2. Checks if caller has enough rights to terminate collected
+ * processes.
+ *
+ * 3. Send SIGTERM to collected processes and wait 100ms.
+ *
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg_plural("database \"%s\" is used by %d prepared transaction",
+ "database \"%s\" is used by %d prepared transactions",
+ nprepared,
+ get_database_name(databaseId), nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 100ms */
+ pg_usleep(100 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e00dbab5aa..2ac2cff226 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,11 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") &&
+ (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 242f817163..149d179c9b 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4809,3 +4809,14 @@ Owning table: "pg_catalog.pg_statistic"
Indexes:
"pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 26a0bcf718..c8f545be18 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1182,3 +1182,11 @@ drop role regress_partitioning_role;
-- \d on toast table (use pg_statistic's toast table, which has a known name)
\d pg_toast.pg_toast_2619
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
On Sun, Oct 20, 2019 at 2:06 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
so 19. 10. 2019 v 12:37 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
On Fri, Oct 4, 2019 at 9:54 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Can we add few tests for this feature.
there are not any other test for DROP DATABASE
I think there are no dedicated tests for it but in a few tests, we use
it like in contrib\sepgsql\sql\alter.sql. I am not sure if we can
write a predictable test for force option because it will never be
guaranteed to drop the database in the presence of other active
sessions.done - I push tests to /tests/regress/psql.sql
Few more comments:
---------------------------------2. TerminateOtherDBBackends() { .. + /* We know so we have all necessary rights now */ + foreach (lc, pids) + { + int pid = lfirst_int(lc); + PGPROC *proc = BackendPidGetProc(pid); + + if (proc != NULL) + { + /* If we have setsid(), signal the backend's whole process group */ +#ifdef HAVE_SETSID + (void) kill(-pid, SIGTERM); +#else + (void) kill(pid, SIGTERM); +#endif + } + } + + /* sleep 100ms */ + pg_usleep(100 * 1000L); .. }So here we are sending SIGTERM to all the processes and wait for 100ms
to allow them to exit. Have you tested this with many processes? I
think we can create 100~500 sessions or maybe more to the database
being dropped and see what is behavior? One thing to notice is that
in function CountOtherDBBackends() we are sending SIGTERM to 10
autovacuum processes at-a-time. That has been introduced in commit
4abd7b49f1e9, but the reason for the same is not mentioned. I am not
sure if it is to avoid sending SIGTERM to many processes in quick
succession.I tested it on linux Linux nemesis
5.3.6-300.fc31.x86_64 #1 SMP Mon Oct 14 12:26:42 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
Tested with 1800 connections without any problem
When you say 'without any problem', do you mean to say that the drop
database is successful? In your tests were those sessions idle? If
so, I think we should try with busy sessions. Also, if you write any
script to do these tests, kindly share the same so that others can
also repeat those tests.
(under low load (only pg_sleep was called).
I guess this is also possible because immediately after
TerminateOtherDBBackends, we call CountOtherDBBackends which again
give 5s to allow active connections to die. I am wondering why not we
call CountOtherDBBackends from TerminateOtherDBBackends after sending
the SIGTERM to all the sessions that are connected to the database
being dropped? Currently, it looks odd that first, we wait for 100ms
after sending the signal and then separately wait for 5s in another
function.
Other comments:
1.
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 26a0bcf718..c8f545be18 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1182,3 +1182,11 @@ drop role regress_partitioning_role;
-- \d on toast table (use pg_statistic's toast table, which has a known name)
\d pg_toast.pg_toast_2619
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
I don't think psql.sql is the right place to add such tests.
Actually, there doesn't appear to be any database specific test file.
However, if we want to add to any existing file, then maybe
drop_if_exits.sql could be a better place for these tests as compare
to psql.sql.
2.
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg_plural("database \"%s\" is used by %d prepared transaction",
+ "database \"%s\" is used by %d prepared transactions",
+ nprepared,
+ get_database_name(databaseId), nprepared)));
I think it is better if we mimic exactly what we have in the failure
of CountOtherDBBackends.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
po 21. 10. 2019 v 7:11 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Sun, Oct 20, 2019 at 2:06 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:so 19. 10. 2019 v 12:37 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Fri, Oct 4, 2019 at 9:54 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
Can we add few tests for this feature.
there are not any other test for DROP DATABASE
I think there are no dedicated tests for it but in a few tests, we use
it like in contrib\sepgsql\sql\alter.sql. I am not sure if we can
write a predictable test for force option because it will never be
guaranteed to drop the database in the presence of other active
sessions.done - I push tests to /tests/regress/psql.sql
Few more comments:
---------------------------------2. TerminateOtherDBBackends() { .. + /* We know so we have all necessary rights now */ + foreach (lc, pids) + { + int pid = lfirst_int(lc); + PGPROC *proc = BackendPidGetProc(pid); + + if (proc != NULL) + { + /* If we have setsid(), signal the backend's whole process group */ +#ifdef HAVE_SETSID + (void) kill(-pid, SIGTERM); +#else + (void) kill(pid, SIGTERM); +#endif + } + } + + /* sleep 100ms */ + pg_usleep(100 * 1000L); .. }So here we are sending SIGTERM to all the processes and wait for 100ms
to allow them to exit. Have you tested this with many processes? I
think we can create 100~500 sessions or maybe more to the database
being dropped and see what is behavior? One thing to notice is that
in function CountOtherDBBackends() we are sending SIGTERM to 10
autovacuum processes at-a-time. That has been introduced in commit
4abd7b49f1e9, but the reason for the same is not mentioned. I am not
sure if it is to avoid sending SIGTERM to many processes in quick
succession.I tested it on linux Linux nemesis
5.3.6-300.fc31.x86_64 #1 SMP Mon Oct 14 12:26:42 UTC 2019 x86_64 x86_64
x86_64 GNU/Linux
Tested with 1800 connections without any problem
When you say 'without any problem', do you mean to say that the drop
database is successful? In your tests were those sessions idle? If
so, I think we should try with busy sessions. Also, if you write any
script to do these tests, kindly share the same so that others can
also repeat those tests.
sessions was active - but the function pg_sleep was called. Drop table
(mainly logout of these users was successful).
I had a script just
for i in {1..1000}; do (psql -c "select pg_sleep(1000)" omega &> /dev/null
&); done
I'll try to do some experiments - unfortunately,I have not a hw where I can
test very large number of connections.
But surely I can use pg_bench, and I can check pg_bench load
(under low load (only pg_sleep was called).
I guess this is also possible because immediately after
TerminateOtherDBBackends, we call CountOtherDBBackends which again
give 5s to allow active connections to die. I am wondering why not we
call CountOtherDBBackends from TerminateOtherDBBackends after sending
the SIGTERM to all the sessions that are connected to the database
being dropped? Currently, it looks odd that first, we wait for 100ms
after sending the signal and then separately wait for 5s in another
function.
I'll look to this part, but I don't think so it is bad. 5s is maximum, not
minimum of waiting. So if sigterm is successful in first 100ms, then
CountOtherDBBackends doesn't add any time. If not, then it dynamically
waiting.
If we don't wait in TerminateOtherDBBackends, then probably there should be
necessary some cycles inside CountOtherDBBackends. I think so code like is
correct
1. send SIGTERM to target processes
2. put some time to processes for logout (100ms)
3. check in loop (max 5 sec) on logout of all processes
Maybe my feeling is wrong, but I think so it is good to wait few time
instead to call CountOtherDBBackends immediately - the first iteration
should to fail, and then first iteration is useless without chance on
success.
Show quoted text
Other comments: 1. diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 26a0bcf718..c8f545be18 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1182,3 +1182,11 @@ drop role regress_partitioning_role;-- \d on toast table (use pg_statistic's toast table, which has a known name) \d pg_toast.pg_toast_2619 + +-- +-- DROP DATABASE FORCE test of syntax (should not to raise syntax error) +-- +drop database not_exists (force); +drop database not_exists with (force); +drop database if exists not_exists (force); +drop database if exists not_exists with (force);I don't think psql.sql is the right place to add such tests.
Actually, there doesn't appear to be any database specific test file.
However, if we want to add to any existing file, then maybe
drop_if_exits.sql could be a better place for these tests as compare
to psql.sql.2. + if (nprepared > 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg_plural("database \"%s\" is used by %d prepared transaction", + "database \"%s\" is used by %d prepared transactions", + nprepared, + get_database_name(databaseId), nprepared)));I think it is better if we mimic exactly what we have in the failure
of CountOtherDBBackends.--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Oct 21, 2019 at 11:08 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 21. 10. 2019 v 7:11 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
(under low load (only pg_sleep was called).
I guess this is also possible because immediately after
TerminateOtherDBBackends, we call CountOtherDBBackends which again
give 5s to allow active connections to die. I am wondering why not we
call CountOtherDBBackends from TerminateOtherDBBackends after sending
the SIGTERM to all the sessions that are connected to the database
being dropped? Currently, it looks odd that first, we wait for 100ms
after sending the signal and then separately wait for 5s in another
function.I'll look to this part, but I don't think so it is bad. 5s is maximum, not minimum of waiting. So if sigterm is successful in first 100ms, then CountOtherDBBackends doesn't add any time. If not, then it dynamically waiting.
If we don't wait in TerminateOtherDBBackends, then probably there should be necessary some cycles inside CountOtherDBBackends. I think so code like is correct
1. send SIGTERM to target processes
2. put some time to processes for logout (100ms)
3. check in loop (max 5 sec) on logout of all processesMaybe my feeling is wrong, but I think so it is good to wait few time instead to call CountOtherDBBackends immediately - the first iteration should to fail, and then first iteration is useless without chance on success.
I think the way I am suggesting by skipping the second step will allow
sleeping only when required. Consider a case where there are just one
or two sessions connected to the database and they immediately exited
after the signal is sent. In such a case you don't need to sleep at
all whereas, under your proposal, it will always sleep. In the case
where a large number of sessions are present and the first 100ms are
not sufficient, we anyway need to wait dynamically. So, I think the
second step not only looks odd but also seems to be redundant.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
po 21. 10. 2019 v 8:38 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Oct 21, 2019 at 11:08 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 21. 10. 2019 v 7:11 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
(under low load (only pg_sleep was called).
I guess this is also possible because immediately after
TerminateOtherDBBackends, we call CountOtherDBBackends which again
give 5s to allow active connections to die. I am wondering why not we
call CountOtherDBBackends from TerminateOtherDBBackends after sending
the SIGTERM to all the sessions that are connected to the database
being dropped? Currently, it looks odd that first, we wait for 100ms
after sending the signal and then separately wait for 5s in another
function.I'll look to this part, but I don't think so it is bad. 5s is maximum,
not minimum of waiting. So if sigterm is successful in first 100ms, then
CountOtherDBBackends doesn't add any time. If not, then it dynamically
waiting.If we don't wait in TerminateOtherDBBackends, then probably there should
be necessary some cycles inside CountOtherDBBackends. I think so code like
is correct1. send SIGTERM to target processes
2. put some time to processes for logout (100ms)
3. check in loop (max 5 sec) on logout of all processesMaybe my feeling is wrong, but I think so it is good to wait few time
instead to call CountOtherDBBackends immediately - the first iteration
should to fail, and then first iteration is useless without chance on
success.I think the way I am suggesting by skipping the second step will allow
sleeping only when required. Consider a case where there are just one
or two sessions connected to the database and they immediately exited
after the signal is sent. In such a case you don't need to sleep at
all whereas, under your proposal, it will always sleep. In the case
where a large number of sessions are present and the first 100ms are
not sufficient, we anyway need to wait dynamically. So, I think the
second step not only looks odd but also seems to be redundant.
I checked the code, and I think so calling CountOtherDBBackends from
TerminateOtherDBBackends is not good idea. CountOtherDBBackends should be
called anywhere, TerminateOtherDBBackends only with FORCE flag. So I
wouldn't to change code.
But I can (and I have not any problem with it) remove or significantly
decrease sleeping time in TerminateOtherDBBackends.
100 ms is maybe very much - but zero is maybe too low. If there will not be
any time between TerminateOtherDBBackends and CountOtherDBBackends, then
probably CountOtherDBBackends hit waiting 100ms.
What about only 5 ms sleeping in TerminateOtherDBBackends?
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Hi
When you say 'without any problem', do you mean to say that the drop
database is successful? In your tests were those sessions idle? If
so, I think we should try with busy sessions. Also, if you write any
script to do these tests, kindly share the same so that others can
also repeat those tests.
I run pgbench on database with -i -S 100 and -c 1000. It is maximum for my
notebook. There was high load - about 50, and still DROP DATABASE FORCE is
working without any problems
(under low load (only pg_sleep was called).
I guess this is also possible because immediately after
TerminateOtherDBBackends, we call CountOtherDBBackends which again
give 5s to allow active connections to die. I am wondering why not we
call CountOtherDBBackends from TerminateOtherDBBackends after sending
the SIGTERM to all the sessions that are connected to the database
being dropped? Currently, it looks odd that first, we wait for 100ms
after sending the signal and then separately wait for 5s in another
function.
I checked code, and would not to change calls. Now I think the code is well
readable and has logical sense. But we can decrease sleep in
TerminateOtherDBBackends from 100 ms to 5 ms (just to increase chance to be
all killed processes done, and then waiting in CountOtherDBBackends 100ms
will not be hit.
Other comments: 1. diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 26a0bcf718..c8f545be18 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -1182,3 +1182,11 @@ drop role regress_partitioning_role;-- \d on toast table (use pg_statistic's toast table, which has a known name) \d pg_toast.pg_toast_2619 + +-- +-- DROP DATABASE FORCE test of syntax (should not to raise syntax error) +-- +drop database not_exists (force); +drop database not_exists with (force); +drop database if exists not_exists (force); +drop database if exists not_exists with (force);I don't think psql.sql is the right place to add such tests.
Actually, there doesn't appear to be any database specific test file.
However, if we want to add to any existing file, then maybe
drop_if_exits.sql could be a better place for these tests as compare
to psql.sql.
moved
2. + if (nprepared > 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg_plural("database \"%s\" is used by %d prepared transaction", + "database \"%s\" is used by %d prepared transactions", + nprepared, + get_database_name(databaseId), nprepared)));I think it is better if we mimic exactly what we have in the failure
of CountOtherDBBackends.
done
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191021.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20191021.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..9b9cabe71c 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,28 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate prepared transactions or active logical replication
+ slot(s).
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will fail if we are not able to terminate connections or
+ when there are active prepared transactions or active logical replication
+ slots.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..8e62359b4d 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1006,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..5131099a85 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da53074b1..4a26c005c6 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,116 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other connections to specified database. This routine
+ * is used by DROP DATABASE FORCE to eliminate all others clients from
+ * database to be possible safely drop database.
+ *
+ * It does three steps:
+ *
+ * 1. Collects processes on specified database, and stops
+ * when any process is a prepared transaction.
+ *
+ * 2. Checks if caller has enough rights to terminate collected
+ * processes.
+ *
+ * 3. Send SIGTERM to collected processes and wait 100ms.
+ *
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+
+ /* sleep 5ms */
+ pg_usleep(5 * 1000L);
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e00dbab5aa..2ac2cff226 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,11 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") &&
+ (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467717..eba0f3f8c7 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,14 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1ef2..03aebb0ee5 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,11 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
On Mon, Oct 21, 2019 at 12:24 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 21. 10. 2019 v 8:38 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
If we don't wait in TerminateOtherDBBackends, then probably there should be necessary some cycles inside CountOtherDBBackends. I think so code like is correct
1. send SIGTERM to target processes
2. put some time to processes for logout (100ms)
3. check in loop (max 5 sec) on logout of all processesMaybe my feeling is wrong, but I think so it is good to wait few time instead to call CountOtherDBBackends immediately - the first iteration should to fail, and then first iteration is useless without chance on success.
I think the way I am suggesting by skipping the second step will allow
sleeping only when required. Consider a case where there are just one
or two sessions connected to the database and they immediately exited
after the signal is sent. In such a case you don't need to sleep at
all whereas, under your proposal, it will always sleep. In the case
where a large number of sessions are present and the first 100ms are
not sufficient, we anyway need to wait dynamically. So, I think the
second step not only looks odd but also seems to be redundant.I checked the code, and I think so calling CountOtherDBBackends from TerminateOtherDBBackends is not good idea. CountOtherDBBackends should be called anywhere, TerminateOtherDBBackends only with FORCE flag. So I wouldn't to change code.
Sorry, but I am not able to understand the reason. Are you worried
about the comments atop CountOtherDBBackends which says it is used in
Drop Database and related commands?
But I can (and I have not any problem with it) remove or significantly decrease sleeping time in TerminateOtherDBBackends.
100 ms is maybe very much - but zero is maybe too low. If there will not be any time between TerminateOtherDBBackends and CountOtherDBBackends, then probably CountOtherDBBackends hit waiting 100ms.
What about only 5 ms sleeping in TerminateOtherDBBackends?
I am not completely sure about what is the most appropriate thing to
do, but I favor removing sleep from TerminateOtherDBBackends. OTOH,
there is nothing broken with the logic. Anyone else wants to weigh in
here?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
po 21. 10. 2019 v 10:25 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Oct 21, 2019 at 12:24 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 21. 10. 2019 v 8:38 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
If we don't wait in TerminateOtherDBBackends, then probably there
should be necessary some cycles inside CountOtherDBBackends. I think so
code like is correct1. send SIGTERM to target processes
2. put some time to processes for logout (100ms)
3. check in loop (max 5 sec) on logout of all processesMaybe my feeling is wrong, but I think so it is good to wait few time
instead to call CountOtherDBBackends immediately - the first iteration
should to fail, and then first iteration is useless without chance on
success.I think the way I am suggesting by skipping the second step will allow
sleeping only when required. Consider a case where there are just one
or two sessions connected to the database and they immediately exited
after the signal is sent. In such a case you don't need to sleep at
all whereas, under your proposal, it will always sleep. In the case
where a large number of sessions are present and the first 100ms are
not sufficient, we anyway need to wait dynamically. So, I think the
second step not only looks odd but also seems to be redundant.I checked the code, and I think so calling CountOtherDBBackends from
TerminateOtherDBBackends is not good idea. CountOtherDBBackends should be
called anywhere, TerminateOtherDBBackends only with FORCE flag. So I
wouldn't to change code.Sorry, but I am not able to understand the reason. Are you worried
about the comments atop CountOtherDBBackends which says it is used in
Drop Database and related commands?
no, just now the code in dropdb looks like
if (force)
TerminateOtherDBBackends(...);
CountOtherDBBackends(...);
if I call CountOtherDBBackends from TerminateOtherDBBackends, then code
will look like
if (force)
TerminateOtherDBBackends(...);
else
CountOtherDBBackends(...);
That looks like CountOtherDBBackends is not called when force clause is
active. And this is not true.
So I prefer current relations between routines.
But I can (and I have not any problem with it) remove or significantly
decrease sleeping time in TerminateOtherDBBackends.
100 ms is maybe very much - but zero is maybe too low. If there will not
be any time between TerminateOtherDBBackends and CountOtherDBBackends, then
probably CountOtherDBBackends hit waiting 100ms.What about only 5 ms sleeping in TerminateOtherDBBackends?
I am not completely sure about what is the most appropriate thing to
do, but I favor removing sleep from TerminateOtherDBBackends. OTOH,
there is nothing broken with the logic. Anyone else wants to weigh in
here?
ok. But when I remove it, should not be better to set waiting in
CountOtherDBBackends to some smaller number than 100ms?
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Oct 21, 2019 at 2:15 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 21. 10. 2019 v 10:25 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
Sorry, but I am not able to understand the reason. Are you worried
about the comments atop CountOtherDBBackends which says it is used in
Drop Database and related commands?no, just now the code in dropdb looks like
if (force)
TerminateOtherDBBackends(...);CountOtherDBBackends(...);
if I call CountOtherDBBackends from TerminateOtherDBBackends, then code will look like
if (force)
TerminateOtherDBBackends(...);
else
CountOtherDBBackends(...);That looks like CountOtherDBBackends is not called when force clause is active. And this is not true.
Hmm, can't we pass force as a parameter to TerminateOtherDBBackends()
and then take the decision inside that function? That will make the
code of dropdb function a bit simpler.
So I prefer current relations between routines.
But I can (and I have not any problem with it) remove or significantly decrease sleeping time in TerminateOtherDBBackends.
100 ms is maybe very much - but zero is maybe too low. If there will not be any time between TerminateOtherDBBackends and CountOtherDBBackends, then probably CountOtherDBBackends hit waiting 100ms.
What about only 5 ms sleeping in TerminateOtherDBBackends?
I am not completely sure about what is the most appropriate thing to
do, but I favor removing sleep from TerminateOtherDBBackends. OTOH,
there is nothing broken with the logic. Anyone else wants to weigh in
here?ok. But when I remove it, should not be better to set waiting in CountOtherDBBackends to some smaller number than 100ms?
CountOtherDBBackends is called from other places as well, so I don't
think it is advisable to change the sleep time in that function.
Also, I don't want to add a parameter for it. I think you have a
point that in some cases we might end up sleeping for 100ms when we
could do with less sleeping time, but I think it is true to some
extent today as well. I think we can anyway change it in the future
if there is a problem with the sleep timing, but for now, I think we
can just call CountOtherDBBackends after sending SIGTERM and call it
good. You might want to add a futuristic note in the code.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
út 22. 10. 2019 v 5:09 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Oct 21, 2019 at 2:15 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 21. 10. 2019 v 10:25 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
Sorry, but I am not able to understand the reason. Are you worried
about the comments atop CountOtherDBBackends which says it is used in
Drop Database and related commands?no, just now the code in dropdb looks like
if (force)
TerminateOtherDBBackends(...);CountOtherDBBackends(...);
if I call CountOtherDBBackends from TerminateOtherDBBackends, then code
will look like
if (force)
TerminateOtherDBBackends(...);
else
CountOtherDBBackends(...);That looks like CountOtherDBBackends is not called when force clause is
active. And this is not true.
Hmm, can't we pass force as a parameter to TerminateOtherDBBackends()
and then take the decision inside that function? That will make the
code of dropdb function a bit simpler.
I don't think so it is correct. Because without FORCE flag, you should not
to call TeminateOtherDBBackend ever.
Maybe I don't understand what is wrong.
if (force)
terminate();
CountOtherDBBackends()
if (some numbers)
ereport(ERROR, ..
This is fully correct for me.
So I prefer current relations between routines.
But I can (and I have not any problem with it) remove or
significantly decrease sleeping time in TerminateOtherDBBackends.
100 ms is maybe very much - but zero is maybe too low. If there will
not be any time between TerminateOtherDBBackends and CountOtherDBBackends,
then probably CountOtherDBBackends hit waiting 100ms.What about only 5 ms sleeping in TerminateOtherDBBackends?
I am not completely sure about what is the most appropriate thing to
do, but I favor removing sleep from TerminateOtherDBBackends. OTOH,
there is nothing broken with the logic. Anyone else wants to weigh in
here?ok. But when I remove it, should not be better to set waiting in
CountOtherDBBackends to some smaller number than 100ms?
CountOtherDBBackends is called from other places as well, so I don't
think it is advisable to change the sleep time in that function.
Also, I don't want to add a parameter for it. I think you have a
point that in some cases we might end up sleeping for 100ms when we
could do with less sleeping time, but I think it is true to some
extent today as well. I think we can anyway change it in the future
if there is a problem with the sleep timing, but for now, I think we
can just call CountOtherDBBackends after sending SIGTERM and call it
good. You might want to add a futuristic note in the code.
ok.
I removed sleeping from TerminateOtherDBBackends().
If you want to change any logic there, please, do it without any
hesitations. Maybe I don't see, what you think.
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191022.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20191022.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..9b9cabe71c 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ Also, it cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ If anyone else is connected to the target database, this command will fail
+ unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,28 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate prepared transactions or active logical replication
+ slot(s).
+ </para>
+ <para>
+ This will fail, if current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described
+ in <xref linkend="functions-admin-signal"/>.
+
+ This will fail if we are not able to terminate connections or
+ when there are active prepared transactions or active logical replication
+ slots.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..8e62359b4d 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,9 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1006,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..5131099a85 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but syntax is designed
+ * to be extensible, and then we use same patterns like on other places.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da53074b1..74736e940e 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,113 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate other connections to specified database. This routine
+ * is used by DROP DATABASE FORCE to eliminate all others clients from
+ * database to be possible safely drop database.
+ *
+ * It does three steps:
+ *
+ * 1. Collects processes on specified database, and stops
+ * when any process is a prepared transaction.
+ *
+ * 2. Checks if caller has enough rights to terminate collected
+ * processes.
+ *
+ * 3. Send SIGTERM to collected processes and wait 100ms.
+ *
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Similar code to pg_terminate_backend, but we check rights first
+ * here, and only when we have all necessary rights we start to
+ * terminate other clients. In this case we should not raise
+ * some warnings - like "PID %d is not a PostgreSQL server process",
+ * because for this purpose - already finished session is not
+ * problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e00dbab5aa..2ac2cff226 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,11 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") &&
+ (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467717..eba0f3f8c7 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,14 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1ef2..03aebb0ee5 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,11 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
On Tue, Oct 22, 2019 at 4:51 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
út 22. 10. 2019 v 5:09 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
CountOtherDBBackends is called from other places as well, so I don't
think it is advisable to change the sleep time in that function.
Also, I don't want to add a parameter for it. I think you have a
point that in some cases we might end up sleeping for 100ms when we
could do with less sleeping time, but I think it is true to some
extent today as well. I think we can anyway change it in the future
if there is a problem with the sleep timing, but for now, I think we
can just call CountOtherDBBackends after sending SIGTERM and call it
good. You might want to add a futuristic note in the code.ok.
I removed sleeping from TerminateOtherDBBackends().
If you want to change any logic there, please, do it without any hesitations. Maybe I don't see, what you think.
Fair enough, I will see if I need to change anything. In the
meantime, can you look into thread related to CountDBSubscriptions[1]/messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com?
I think the problem reported there will be more serious after your
patch, so it is better if we can fix it before this patch.
[1]: /messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Wed, Oct 23, 2019 at 12:59 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Tue, Oct 22, 2019 at 4:51 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
út 22. 10. 2019 v 5:09 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
CountOtherDBBackends is called from other places as well, so I don't
think it is advisable to change the sleep time in that function.
Also, I don't want to add a parameter for it. I think you have a
point that in some cases we might end up sleeping for 100ms when we
could do with less sleeping time, but I think it is true to some
extent today as well. I think we can anyway change it in the future
if there is a problem with the sleep timing, but for now, I think we
can just call CountOtherDBBackends after sending SIGTERM and call it
good. You might want to add a futuristic note in the code.ok.
I removed sleeping from TerminateOtherDBBackends().
If you want to change any logic there, please, do it without any hesitations. Maybe I don't see, what you think.
Fair enough, I will see if I need to change anything.
While making some changes in the patch, I noticed that in
TerminateOtherDBBackends, there is a race condition where after we
release the ProcArrayLock, the backend process to which we decided to
send a signal exits by itself and the same pid can be assigned to
another backend which is connected to some other database. This leads
to terminating a wrong backend. I think we have some similar race
condition at few other places like in pg_terminate_backend(),
ProcSleep() and CountOtherDBBackends(). I think here the risk is a
bit more because there could be a long list of pids.
One idea could be that we write a new function similar to IsBackendPid
which takes dbid and ensures that pid belongs to that database and use
that before sending kill signal, but still it will not be completely
safe. But, I think it can be closer to cases like we already have in
code.
Another possible idea could be to use the SendProcSignal mechanism
similar to how we have used it in CancelDBBackends() to allow the
required backends to exit by themselves. This might be safer.
I am not sure if we can entirely eliminate this race condition and
whether it is a good idea to accept such a race condition even though
it exists in other parts of code. What do you think?
BTW, I have added/revised some comments in the code and done few other
cosmetic changes, the result of which is attached.
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191024.amit.patchapplication/octet-stream; name=drop-database-force-20191024.amit.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..cef1b90421 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ It cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ Also, if anyone else is connected to the target database, this command will
+ fail unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,25 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate if prepared transactions, active logical replication
+ slots or subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described in
+ <xref linkend="functions-admin-signal"/>. This will also fail if we
+ are not able to terminate connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..38a2bfa969 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,13 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ /*
+ * Attempt to terminate all existing connections to the target database if
+ * the user has requested to do so.
+ */
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1010,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..2f7bd662e8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but the syntax is designed
+ * to be extensible so that we can add more options in the future if required.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da53074b1..f7d8e2b294 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,110 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate existing connections to the specified database. This routine
+ * is used by the DROP DATABASE command when user has asked to forcefully
+ * drop the database.
+ *
+ * The current backend is always ignored; it is caller's responsibility to
+ * check whether the current backend uses the given DB, if it's important.
+ *
+ * It doesn't allow to terminate the connections even if there is a one
+ * backend with the prepared transaction in the target database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Check whether we have the necessary rights to terminate other
+ * sessions. We don't terminate any session untill we ensure that we
+ * have rights on all the sessions to be terminated. These checks are
+ * the same as we do in pg_terminate_backend.
+ *
+ * In this case we don't raise some warnings - like "PID %d is not a
+ * PostgreSQL server process", because for us already finished
+ * session is not a problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /* We know so we have all necessary rights now */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+ }
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa6619d..960b0df0e1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e00dbab5aa..2ac2cff226 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,11 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") &&
+ (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467717..eba0f3f8c7 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,14 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1ef2..03aebb0ee5 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,11 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
čt 24. 10. 2019 v 11:10 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Wed, Oct 23, 2019 at 12:59 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:On Tue, Oct 22, 2019 at 4:51 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
út 22. 10. 2019 v 5:09 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
CountOtherDBBackends is called from other places as well, so I don't
think it is advisable to change the sleep time in that function.
Also, I don't want to add a parameter for it. I think you have a
point that in some cases we might end up sleeping for 100ms when we
could do with less sleeping time, but I think it is true to some
extent today as well. I think we can anyway change it in the future
if there is a problem with the sleep timing, but for now, I think we
can just call CountOtherDBBackends after sending SIGTERM and call it
good. You might want to add a futuristic note in the code.ok.
I removed sleeping from TerminateOtherDBBackends().
If you want to change any logic there, please, do it without any
hesitations. Maybe I don't see, what you think.
Fair enough, I will see if I need to change anything.
While making some changes in the patch, I noticed that in
TerminateOtherDBBackends, there is a race condition where after we
release the ProcArrayLock, the backend process to which we decided to
send a signal exits by itself and the same pid can be assigned to
another backend which is connected to some other database. This leads
to terminating a wrong backend. I think we have some similar race
condition at few other places like in pg_terminate_backend(),
ProcSleep() and CountOtherDBBackends(). I think here the risk is a
bit more because there could be a long list of pids.One idea could be that we write a new function similar to IsBackendPid
which takes dbid and ensures that pid belongs to that database and use
that before sending kill signal, but still it will not be completely
safe. But, I think it can be closer to cases like we already have in
code.Another possible idea could be to use the SendProcSignal mechanism
similar to how we have used it in CancelDBBackends() to allow the
required backends to exit by themselves. This might be safer.I am not sure if we can entirely eliminate this race condition and
whether it is a good idea to accept such a race condition even though
it exists in other parts of code. What do you think?BTW, I have added/revised some comments in the code and done few other
cosmetic changes, the result of which is attached.
Tomorrow I'll check variants that you mentioned.
We sure so there are not any new connect to removed database, because we
hold lock there. So check if oid db is same should be enough.
Pavel
Show quoted text
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Oct 24, 2019 at 8:22 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 24. 10. 2019 v 11:10 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
While making some changes in the patch, I noticed that in
TerminateOtherDBBackends, there is a race condition where after we
release the ProcArrayLock, the backend process to which we decided to
send a signal exits by itself and the same pid can be assigned to
another backend which is connected to some other database. This leads
to terminating a wrong backend. I think we have some similar race
condition at few other places like in pg_terminate_backend(),
ProcSleep() and CountOtherDBBackends(). I think here the risk is a
bit more because there could be a long list of pids.One idea could be that we write a new function similar to IsBackendPid
which takes dbid and ensures that pid belongs to that database and use
that before sending kill signal, but still it will not be completely
safe. But, I think it can be closer to cases like we already have in
code.Another possible idea could be to use the SendProcSignal mechanism
similar to how we have used it in CancelDBBackends() to allow the
required backends to exit by themselves. This might be safer.I am not sure if we can entirely eliminate this race condition and
whether it is a good idea to accept such a race condition even though
it exists in other parts of code. What do you think?BTW, I have added/revised some comments in the code and done few other
cosmetic changes, the result of which is attached.Tomorrow I'll check variants that you mentioned.
We sure so there are not any new connect to removed database, because we hold lock there.
Right.
So check if oid db is same should be enough.
We can do this before sending a kill signal but is it enough? Because
as soon as we release ProcArrayLock anytime the other process can exit
and a new process can use its pid. I think this is more of a
theoretical risk and might not be easy to hit, but still, we can't
ignore it.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
pá 25. 10. 2019 v 4:55 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Thu, Oct 24, 2019 at 8:22 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 24. 10. 2019 v 11:10 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
While making some changes in the patch, I noticed that in
TerminateOtherDBBackends, there is a race condition where after we
release the ProcArrayLock, the backend process to which we decided to
send a signal exits by itself and the same pid can be assigned to
another backend which is connected to some other database. This leads
to terminating a wrong backend. I think we have some similar race
condition at few other places like in pg_terminate_backend(),
ProcSleep() and CountOtherDBBackends(). I think here the risk is a
bit more because there could be a long list of pids.One idea could be that we write a new function similar to IsBackendPid
which takes dbid and ensures that pid belongs to that database and use
that before sending kill signal, but still it will not be completely
safe. But, I think it can be closer to cases like we already have in
code.Another possible idea could be to use the SendProcSignal mechanism
similar to how we have used it in CancelDBBackends() to allow the
required backends to exit by themselves. This might be safer.I am not sure if we can entirely eliminate this race condition and
whether it is a good idea to accept such a race condition even though
it exists in other parts of code. What do you think?BTW, I have added/revised some comments in the code and done few other
cosmetic changes, the result of which is attached.Tomorrow I'll check variants that you mentioned.
We sure so there are not any new connect to removed database, because we
hold lock there.
Right.
So check if oid db is same should be enough.
We can do this before sending a kill signal but is it enough? Because
as soon as we release ProcArrayLock anytime the other process can exit
and a new process can use its pid. I think this is more of a
theoretical risk and might not be easy to hit, but still, we can't
ignore it.
yes, there is a theoretical risk probably - the released pid should near
current fresh pid from range 0 .. pid_max.
Probably the best solutions is enhancing SendProcSignal and using it here
and fix CountOtherDBBackends.
Alternative solution can be killing in block 50 processes and recheck. I'll
try to write both and you can decide for one
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Hi
so 2. 11. 2019 v 17:18 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
pá 25. 10. 2019 v 4:55 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:On Thu, Oct 24, 2019 at 8:22 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 24. 10. 2019 v 11:10 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
While making some changes in the patch, I noticed that in
TerminateOtherDBBackends, there is a race condition where after we
release the ProcArrayLock, the backend process to which we decided to
send a signal exits by itself and the same pid can be assigned to
another backend which is connected to some other database. This leads
to terminating a wrong backend. I think we have some similar race
condition at few other places like in pg_terminate_backend(),
ProcSleep() and CountOtherDBBackends(). I think here the risk is a
bit more because there could be a long list of pids.One idea could be that we write a new function similar to IsBackendPid
which takes dbid and ensures that pid belongs to that database and use
that before sending kill signal, but still it will not be completely
safe. But, I think it can be closer to cases like we already have in
code.Another possible idea could be to use the SendProcSignal mechanism
similar to how we have used it in CancelDBBackends() to allow the
required backends to exit by themselves. This might be safer.I am not sure if we can entirely eliminate this race condition and
whether it is a good idea to accept such a race condition even though
it exists in other parts of code. What do you think?BTW, I have added/revised some comments in the code and done few other
cosmetic changes, the result of which is attached.Tomorrow I'll check variants that you mentioned.
We sure so there are not any new connect to removed database, because
we hold lock there.
Right.
So check if oid db is same should be enough.
We can do this before sending a kill signal but is it enough? Because
as soon as we release ProcArrayLock anytime the other process can exit
and a new process can use its pid. I think this is more of a
theoretical risk and might not be easy to hit, but still, we can't
ignore it.yes, there is a theoretical risk probably - the released pid should near
current fresh pid from range 0 .. pid_max.Probably the best solutions is enhancing SendProcSignal and using it here
and fix CountOtherDBBackends.Alternative solution can be killing in block 50 processes and recheck.
I'll try to write both and you can decide for one
I am sending patch where kill was replaced by SendProcSignal. I tested it
on pg_bench with 400 connections, and it works without problems.
Regards
Pavel
Show quoted text
Pavel
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191102.amit.patchtext/x-patch; charset=US-ASCII; name=drop-database-force-20191102.amit.patchDownload
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c984a..cef1b90421 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ It cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ Also, if anyone else is connected to the target database, this command will
+ fail unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,25 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate if prepared transactions, active logical replication
+ slots or subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described in
+ <xref linkend="functions-admin-signal"/>. This will also fail if we
+ are not able to terminate connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d66212e9..38a2bfa969 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -896,6 +896,13 @@ dropdb(const char *dbname, bool missing_ok)
nslots_active, nslots_active)));
}
+ /*
+ * Attempt to terminate all existing connections to the target database if
+ * the user has requested to do so.
+ */
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1003,6 +1010,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..2f267e4bb6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..da0e1d139a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..2f7bd662e8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but the syntax is designed
+ * to be extensible so that we can add more options in the future if required.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da53074b1..aad15591ad 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2970,6 +2972,101 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
return true; /* timed out, still conflicts */
}
+/*
+ * Terminate existing connections to the specified database. This routine
+ * is used by the DROP DATABASE command when user has asked to forcefully
+ * drop the database.
+ *
+ * The current backend is always ignored; it is caller's responsibility to
+ * check whether the current backend uses the given DB, if it's important.
+ *
+ * It doesn't allow to terminate the connections even if there is a one
+ * backend with the prepared transaction in the target database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ int nprepared = 0;
+ int nothers = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ /* count number of prepared transactions */
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ {
+ /*
+ * Check whether we have the necessary rights to terminate other
+ * sessions. We don't terminate any session untill we ensure that we
+ * have rights on all the sessions to be terminated. These checks are
+ * the same as we do in pg_terminate_backend.
+ *
+ * Only allow superusers to signal superuser-owned backends.
+ */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+
+ nothers++;
+ }
+ else
+ nprepared++;
+ }
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ /* Now at end we can safely send SIGTERM */
+ if (nothers > 0)
+ {
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ {
+ SendProcSignal(proc->pid,
+ PROCSIG_SIGTERM_INTERRUPT,
+ proc->backendId);
+ }
+ }
+ }
+
+ LWLockRelease(ProcArrayLock);
+}
+
/*
* ProcArraySetReplicationSlotXmin
*
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7605b2c367..1b74d7ea60 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -292,6 +292,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+ if (CheckProcSignal(PROCSIG_SIGTERM_INTERRUPT))
+ HandleSigtermInterrupt();
+
SetLatch(MyLatch);
latch_sigusr1_handler();
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 4bec40aa28..42263797ef 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -2789,6 +2789,37 @@ die(SIGNAL_ARGS)
errno = save_errno;
}
+/*
+ * Shutdown signal from postmaster: abort transaction and exit
+ * at soonest convenient time
+ */
+void
+HandleSigtermInterrupt(void)
+{
+ int save_errno = errno;
+
+ /* Don't joggle the elbow of proc_exit */
+ if (!proc_exit_inprogress)
+ {
+ InterruptPending = true;
+ ProcDiePending = true;
+ }
+
+ /* If we're still here, waken anything waiting on the process latch */
+ SetLatch(MyLatch);
+
+ /*
+ * If we're in single user mode, we want to quit immediately - we can't
+ * rely on latches as they wouldn't work when stdin/stdout is a file.
+ * Rather ugly, but it's unlikely to be worthwhile to invest much more
+ * effort just for the benefit of single user mode.
+ */
+ if (DoingCommandRead && whereToSendOutput != DestRemote)
+ ProcessInterrupts();
+
+ errno = save_errno;
+}
+
/*
* Query-cancel signal from postmaster: abort current transaction
* at soonest convenient time
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f2269ad35c..f3c01b4e3e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2b1e3cda4a..3e2d9813d8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,11 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") &&
+ (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c8157ee..d1e91a2455 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..ff626cbe61 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672096..8f67b860e7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 05b186a05c..c4e4574f5a 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -33,6 +33,7 @@ typedef enum
PROCSIG_NOTIFY_INTERRUPT, /* listen/notify interrupt */
PROCSIG_PARALLEL_MESSAGE, /* message from cooperating parallel backend */
PROCSIG_WALSND_INIT_STOPPING, /* ask walsenders to prepare for shutdown */
+ PROCSIG_SIGTERM_INTERRUPT, /* sigterm interrupt */
/* Recovery conflict reasons */
PROCSIG_RECOVERY_CONFLICT_DATABASE,
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index ec21f7e45c..967422f537 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -62,6 +62,7 @@ extern void assign_max_stack_depth(int newval, void *extra);
extern void die(SIGNAL_ARGS);
extern void quickdie(SIGNAL_ARGS) pg_attribute_noreturn();
+extern void HandleSigtermInterrupt(void);
extern void StatementCancelHandler(SIGNAL_ARGS);
extern void FloatExceptionHandler(SIGNAL_ARGS) pg_attribute_noreturn();
extern void RecoveryConflictInterrupt(ProcSignalReason reason); /* called from SIGUSR1
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467717..eba0f3f8c7 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,14 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1ef2..03aebb0ee5 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,11 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
I am sending patch where kill was replaced by SendProcSignal. I tested it
on pg_bench with 400 connections, and it works without problems.
I tested it under high load and it is works. But it is slower than just
kill - under load the killing 400 connections needs about 1.5 sec.
Maybe for forced drop database we can increase max time limit to 10 seconds
(maybe more (statement timeout)) ? It is granted so we wait just on sigterm
handling. Other situations are finished by error. So in this case is very
probable so waiting will be successful and then we can wait long time.
Show quoted text
Regards
Pavel
Pavel
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Sun, Nov 3, 2019 at 2:08 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
pá 25. 10. 2019 v 4:55 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
On Thu, Oct 24, 2019 at 8:22 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 24. 10. 2019 v 11:10 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
While making some changes in the patch, I noticed that in
TerminateOtherDBBackends, there is a race condition where after we
release the ProcArrayLock, the backend process to which we decided to
send a signal exits by itself and the same pid can be assigned to
another backend which is connected to some other database. This leads
to terminating a wrong backend. I think we have some similar race
condition at few other places like in pg_terminate_backend(),
ProcSleep() and CountOtherDBBackends(). I think here the risk is a
bit more because there could be a long list of pids.One idea could be that we write a new function similar to IsBackendPid
which takes dbid and ensures that pid belongs to that database and use
that before sending kill signal, but still it will not be completely
safe. But, I think it can be closer to cases like we already have in
code.Another possible idea could be to use the SendProcSignal mechanism
similar to how we have used it in CancelDBBackends() to allow the
required backends to exit by themselves. This might be safer.I am not sure if we can entirely eliminate this race condition and
whether it is a good idea to accept such a race condition even though
it exists in other parts of code. What do you think?BTW, I have added/revised some comments in the code and done few other
cosmetic changes, the result of which is attached.Tomorrow I'll check variants that you mentioned.
We sure so there are not any new connect to removed database, because we hold lock there.
Right.
So check if oid db is same should be enough.
We can do this before sending a kill signal but is it enough? Because
as soon as we release ProcArrayLock anytime the other process can exit
and a new process can use its pid. I think this is more of a
theoretical risk and might not be easy to hit, but still, we can't
ignore it.yes, there is a theoretical risk probably - the released pid should near current fresh pid from range 0 .. pid_max.
Probably the best solutions is enhancing SendProcSignal and using it here and fix CountOtherDBBackends.
Alternative solution can be killing in block 50 processes and recheck. I'll try to write both and you can decide for one
I am sending patch where kill was replaced by SendProcSignal. I tested it on pg_bench with 400 connections, and it works without problems.
I think there is still a window where the same problem can happen, say
the signal has been sent by SendProcSignal to the required process and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.
I am not sure but I think we can avoid such a race condition if we set
a flag (say killPending or something like that on the lines of
recoveryConflictPending) in proc and then check that flag before
allowing the process to die. If something on these lines is feasible,
then I think there will be no race condition where we will kill the
wrong backend. If we do this, then probably, just setting the flag
under ProcArrayLock is sufficient. The signal can be sent outside the
lock.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Amit Kapila <amit.kapila16@gmail.com> writes:
I think there is still a window where the same problem can happen, say
the signal has been sent by SendProcSignal to the required process and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.
In principle, no use of Unix signals is ever safe against this sort
of race condition --- process A can never know that process B didn't
exit immediately before A does kill(B, n). In practice, it's okay
because the kernel is expected not to reassign a dead PID for some
reasonable grace period [1]and also because the kernel *can't* recycle the PID until the parent process has reaped the zombie process-table entry. Thus, for example, it's unconditionally safe for the postmaster to signal its children, because those PIDs can't move until the postmaster accepts the SIGCHLD signal and does a wait() for them. Any interprocess signals between child processes are inherently a tad less safe. But we've gotten away with interprocess SIGUSR1 for decades with no reported problems. I don't really think that we need to move the goalposts for SIGINT, and I'm entirely not in favor of the sorts of complications that are being proposed here.. I'd be inclined to lean more heavily
on that expectation than anything internal to Postgres. That is,
remembering the PID we want to kill for some small number of
microseconds is probably a safer API than anything that depends on
the contents of the ProcArray, because there indeed *isn't* any
guarantee that a ProcArray entry won't be recycled immediately.
regards, tom lane
[1]: and also because the kernel *can't* recycle the PID until the parent process has reaped the zombie process-table entry. Thus, for example, it's unconditionally safe for the postmaster to signal its children, because those PIDs can't move until the postmaster accepts the SIGCHLD signal and does a wait() for them. Any interprocess signals between child processes are inherently a tad less safe. But we've gotten away with interprocess SIGUSR1 for decades with no reported problems. I don't really think that we need to move the goalposts for SIGINT, and I'm entirely not in favor of the sorts of complications that are being proposed here.
parent process has reaped the zombie process-table entry. Thus,
for example, it's unconditionally safe for the postmaster to signal
its children, because those PIDs can't move until the postmaster
accepts the SIGCHLD signal and does a wait() for them. Any
interprocess signals between child processes are inherently a tad
less safe. But we've gotten away with interprocess SIGUSR1 for
decades with no reported problems. I don't really think that we
need to move the goalposts for SIGINT, and I'm entirely not in
favor of the sorts of complications that are being proposed here.
st 6. 11. 2019 v 14:59 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Amit Kapila <amit.kapila16@gmail.com> writes:
I think there is still a window where the same problem can happen, say
the signal has been sent by SendProcSignal to the required process and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.In principle, no use of Unix signals is ever safe against this sort
of race condition --- process A can never know that process B didn't
exit immediately before A does kill(B, n). In practice, it's okay
because the kernel is expected not to reassign a dead PID for some
reasonable grace period [1]. I'd be inclined to lean more heavily
on that expectation than anything internal to Postgres. That is,
remembering the PID we want to kill for some small number of
microseconds is probably a safer API than anything that depends on
the contents of the ProcArray, because there indeed *isn't* any
guarantee that a ProcArray entry won't be recycled immediately.regards, tom lane
[1] and also because the kernel *can't* recycle the PID until the
parent process has reaped the zombie process-table entry. Thus,
for example, it's unconditionally safe for the postmaster to signal
its children, because those PIDs can't move until the postmaster
accepts the SIGCHLD signal and does a wait() for them. Any
interprocess signals between child processes are inherently a tad
less safe. But we've gotten away with interprocess SIGUSR1 for
decades with no reported problems. I don't really think that we
need to move the goalposts for SIGINT, and I'm entirely not in
favor of the sorts of complications that are being proposed here.
so we can return back to just simple killing.
Regards
Pavel
On Wed, Nov 6, 2019 at 11:46 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
st 6. 11. 2019 v 14:59 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Amit Kapila <amit.kapila16@gmail.com> writes:
I think there is still a window where the same problem can happen, say
the signal has been sent by SendProcSignal to the required process and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.In principle, no use of Unix signals is ever safe against this sort
of race condition --- process A can never know that process B didn't
exit immediately before A does kill(B, n). In practice, it's okay
because the kernel is expected not to reassign a dead PID for some
reasonable grace period [1]. I'd be inclined to lean more heavily
on that expectation than anything internal to Postgres. That is,
remembering the PID we want to kill for some small number of
microseconds is probably a safer API than anything that depends on
the contents of the ProcArray, because there indeed *isn't* any
guarantee that a ProcArray entry won't be recycled immediately.
Right, this makes sense. I think I was overly paranoid about this
behavior even though that was used at a few other places as this patch
might need to rely on many pids not being reused after the lock is
released.
so we can return back to just simple killing.
I think so. I think we might want to add a comment about this race
condition and add or reference to comments in pg_signal_backend which
mentions the same race condition.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
čt 7. 11. 2019 v 3:42 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Wed, Nov 6, 2019 at 11:46 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:st 6. 11. 2019 v 14:59 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Amit Kapila <amit.kapila16@gmail.com> writes:
I think there is still a window where the same problem can happen, say
the signal has been sent by SendProcSignal to the required process and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.In principle, no use of Unix signals is ever safe against this sort
of race condition --- process A can never know that process B didn't
exit immediately before A does kill(B, n). In practice, it's okay
because the kernel is expected not to reassign a dead PID for some
reasonable grace period [1]. I'd be inclined to lean more heavily
on that expectation than anything internal to Postgres. That is,
remembering the PID we want to kill for some small number of
microseconds is probably a safer API than anything that depends on
the contents of the ProcArray, because there indeed *isn't* any
guarantee that a ProcArray entry won't be recycled immediately.Right, this makes sense. I think I was overly paranoid about this
behavior even though that was used at a few other places as this patch
might need to rely on many pids not being reused after the lock is
released.so we can return back to just simple killing.
I think so. I think we might want to add a comment about this race
condition and add or reference to comments in pg_signal_backend which
mentions the same race condition.
Please, can you do it. It's bad task for my with my bad English.
Thank you
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Nov 7, 2019 at 10:46 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 7. 11. 2019 v 3:42 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
On Wed, Nov 6, 2019 at 11:46 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
st 6. 11. 2019 v 14:59 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Amit Kapila <amit.kapila16@gmail.com> writes:
I think there is still a window where the same problem can happen, say
the signal has been sent by SendProcSignal to the required process and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.In principle, no use of Unix signals is ever safe against this sort
of race condition --- process A can never know that process B didn't
exit immediately before A does kill(B, n). In practice, it's okay
because the kernel is expected not to reassign a dead PID for some
reasonable grace period [1]. I'd be inclined to lean more heavily
on that expectation than anything internal to Postgres. That is,
remembering the PID we want to kill for some small number of
microseconds is probably a safer API than anything that depends on
the contents of the ProcArray, because there indeed *isn't* any
guarantee that a ProcArray entry won't be recycled immediately.Right, this makes sense. I think I was overly paranoid about this
behavior even though that was used at a few other places as this patch
might need to rely on many pids not being reused after the lock is
released.so we can return back to just simple killing.
I think so. I think we might want to add a comment about this race
condition and add or reference to comments in pg_signal_backend which
mentions the same race condition.Please, can you do it. It's bad task for my with my bad English.
Okay, no problem. I will pick the previous version and do this. I
will post the patch in a day or so for your review.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
čt 7. 11. 2019 v 6:56 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Thu, Nov 7, 2019 at 10:46 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 7. 11. 2019 v 3:42 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Wed, Nov 6, 2019 at 11:46 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
st 6. 11. 2019 v 14:59 odesílatel Tom Lane <tgl@sss.pgh.pa.us>
napsal:
Amit Kapila <amit.kapila16@gmail.com> writes:
I think there is still a window where the same problem can happen,
say
the signal has been sent by SendProcSignal to the required process
and
it releases the ProcArrayLock. Now, the target process exits and a
new process gets the same pid before the signal is received.In principle, no use of Unix signals is ever safe against this sort
of race condition --- process A can never know that process B didn't
exit immediately before A does kill(B, n). In practice, it's okay
because the kernel is expected not to reassign a dead PID for some
reasonable grace period [1]. I'd be inclined to lean more heavily
on that expectation than anything internal to Postgres. That is,
remembering the PID we want to kill for some small number of
microseconds is probably a safer API than anything that depends on
the contents of the ProcArray, because there indeed *isn't* any
guarantee that a ProcArray entry won't be recycled immediately.Right, this makes sense. I think I was overly paranoid about this
behavior even though that was used at a few other places as this patch
might need to rely on many pids not being reused after the lock is
released.so we can return back to just simple killing.
I think so. I think we might want to add a comment about this race
condition and add or reference to comments in pg_signal_backend which
mentions the same race condition.Please, can you do it. It's bad task for my with my bad English.
Okay, no problem. I will pick the previous version and do this. I
will post the patch in a day or so for your review.
Thank you very much
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Nov 7, 2019 at 11:29 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 7. 11. 2019 v 6:56 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
Okay, no problem. I will pick the previous version and do this. I
will post the patch in a day or so for your review.Thank you very much
Did you get a chance to look at the other related patch posted by me
[1]: /messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com
before this. We need to avoid errors to happen after terminating the
connections as otherwise, the termination of other databases will go
be of no use.
I have added the comment and changed one condition in tab-completion
as otherwise, it was allowing tab completion for the following syntax:
"drop database db1 , FORCE" which doesn't make sense to me. Please,
find the result attached. Let me know what you think?
[1]: /messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191108.amit.patchapplication/octet-stream; name=drop-database-force-20191108.amit.patchDownload
From 54f32d3e2786dc012b8b16a592ddd0623630a76e Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Fri, 8 Nov 2019 10:51:21 +0530
Subject: [PATCH] Introduce the 'force' option for the Drop Database command.
This new option terminates the other sessions connected to the target
database and then drop it. To terminate other sessions, the current user
must have desired permissions (same as pg_terminate_backend()). We don't
allow to terminate the sessions if prepared transactions, active logical
replication slots or subscriptions are present in the target database.
Author: Pavel Stehule with some changes by me
Reviewed-by: Dilip Kumar, Vignesh C, Ibrar Ahmed, Anthony Nowocien, Ryan
Lambert and Amit Kapila
Discussion: https://postgr.es/m/CAP_rwwmLJJbn70vLOZFpxGw3XD7nLB_7+NKz46H5EOO2k5H7OQ@mail.gmail.com
---
doc/src/sgml/ref/drop_database.sgml | 33 +++++++-
src/backend/commands/dbcommands.c | 33 +++++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/parser/gram.y | 43 ++++++++++-
src/backend/storage/ipc/procarray.c | 111 +++++++++++++++++++++++++++
src/backend/tcop/utility.c | 10 +--
src/bin/psql/tab-complete.c | 4 +
src/include/commands/dbcommands.h | 3 +-
src/include/nodes/parsenodes.h | 1 +
src/include/storage/procarray.h | 1 +
src/test/regress/expected/drop_if_exists.out | 11 +++
src/test/regress/sql/drop_if_exists.sql | 8 ++
13 files changed, 246 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c9..cef1b90 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ It cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ Also, if anyone else is connected to the target database, this command will
+ fail unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,25 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate if prepared transactions, active logical replication
+ slots or subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described in
+ <xref linkend="functions-admin-signal"/>. This will also fail if we
+ are not able to terminate connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 01d6621..38a2bfa 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -897,6 +897,13 @@ dropdb(const char *dbname, bool missing_ok)
}
/*
+ * Attempt to terminate all existing connections to the target database if
+ * the user has requested to do so.
+ */
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
+ /*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
*
@@ -1003,6 +1010,30 @@ dropdb(const char *dbname, bool missing_ok)
ForceSyncCommit();
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* Rename database
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb9..2f267e4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014..da0e1d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..2f7bd66 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but the syntax is designed
+ * to be extensible so that we can add more options in the future if required.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da5307..54bc81f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2971,6 +2973,115 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
}
/*
+ * Terminate existing connections to the specified database. This routine
+ * is used by the DROP DATABASE command when user has asked to forcefully
+ * drop the database.
+ *
+ * The current backend is always ignored; it is caller's responsibility to
+ * check whether the current backend uses the given DB, if it's important.
+ *
+ * It doesn't allow to terminate the connections even if there is a one
+ * backend with the prepared transaction in the target database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Check whether we have the necessary rights to terminate other
+ * sessions. We don't terminate any session untill we ensure that we
+ * have rights on all the sessions to be terminated. These checks are
+ * the same as we do in pg_terminate_backend.
+ *
+ * In this case we don't raise some warnings - like "PID %d is not a
+ * PostgreSQL server process", because for us already finished
+ * session is not a problem.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /*
+ * There's a race condition here: once we release the ProcArrayLock,
+ * it's possible for the session to exit before we issue kill. That
+ * race condition possibility seems too unlikely to worry about. See
+ * pg_signal_backend.
+ */
+ foreach (lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* If we have setsid(), signal the backend's whole process group */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+ }
+}
+
+/*
* ProcArraySetReplicationSlotXmin
*
* Install limits to future computations of the xmin horizon to prevent vacuum
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index e984545..6d85304 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2b1e3cd..a949434 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,10 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") && (ends_with(prev_wd, '(') ))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c815..d1e91a2 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..ff626cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672..8f67b86 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467..eba0f3f 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,14 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1..03aebb0 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,11 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
--
1.8.3.1
pá 8. 11. 2019 v 6:39 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Thu, Nov 7, 2019 at 11:29 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 7. 11. 2019 v 6:56 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
Okay, no problem. I will pick the previous version and do this. I
will post the patch in a day or so for your review.Thank you very much
Did you get a chance to look at the other related patch posted by me
[1]? I have asked it before as well because I think that need to go
before this. We need to avoid errors to happen after terminating the
connections as otherwise, the termination of other databases will go
be of no use.I have added the comment and changed one condition in tab-completion
as otherwise, it was allowing tab completion for the following syntax:
"drop database db1 , FORCE" which doesn't make sense to me. Please,
find the result attached. Let me know what you think?[1] -
/messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com
I'll check it today
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
pá 8. 11. 2019 v 6:40 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
pá 8. 11. 2019 v 6:39 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:On Thu, Nov 7, 2019 at 11:29 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 7. 11. 2019 v 6:56 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
Okay, no problem. I will pick the previous version and do this. I
will post the patch in a day or so for your review.Thank you very much
Did you get a chance to look at the other related patch posted by me
[1]? I have asked it before as well because I think that need to go
before this. We need to avoid errors to happen after terminating the
connections as otherwise, the termination of other databases will go
be of no use.I have added the comment and changed one condition in tab-completion
as otherwise, it was allowing tab completion for the following syntax:
"drop database db1 , FORCE" which doesn't make sense to me. Please,
find the result attached. Let me know what you think?[1] -
/messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.comI'll check it today
I checked it for 800 active clients, and it is working without problems. I
run "check-world" without problem too.
The patch looks well, I have not any comments.
Regards
Pavel
Show quoted text
Pavel
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Nov 8, 2019 at 4:13 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
Did you get a chance to look at the other related patch posted by me
[1]? I have asked it before as well because I think that need to go
before this. We need to avoid errors to happen after terminating the
connections as otherwise, the termination of other databases will go
be of no use.I have added the comment and changed one condition in tab-completion
as otherwise, it was allowing tab completion for the following syntax:
"drop database db1 , FORCE" which doesn't make sense to me. Please,
find the result attached. Let me know what you think?[1] - /messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com
I'll check it today
I checked it for 800 active clients, and it is working without problems. I run "check-world" without problem too.
The patch looks well, I have not any comments.
Thanks, but are you planning to look at the other thread mentioned in
my previous email? We need to finish that before this.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
pá 8. 11. 2019 v 11:50 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Fri, Nov 8, 2019 at 4:13 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:Did you get a chance to look at the other related patch posted by me
[1]? I have asked it before as well because I think that need to go
before this. We need to avoid errors to happen after terminating the
connections as otherwise, the termination of other databases will go
be of no use.I have added the comment and changed one condition in tab-completion
as otherwise, it was allowing tab completion for the following syntax:
"drop database db1 , FORCE" which doesn't make sense to me. Please,
find the result attached. Let me know what you think?[1] -
/messages/by-id/CAA4eK1+qhLkCYG2oy9xug9ur_j=G2wQNRYAyd+-kZfZ1z42pLw@mail.gmail.com
I'll check it today
I checked it for 800 active clients, and it is working without problems.
I run "check-world" without problem too.
The patch looks well, I have not any comments.
Thanks, but are you planning to look at the other thread mentioned in
my previous email? We need to finish that before this.
I check it now - it has sense. the described switch can protect users
against useless client killing.
The practical benefit will not be high, but it has sense and the code will
more logic.
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Nov 8, 2019 at 4:57 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
pá 8. 11. 2019 v 11:50 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
Thanks, but are you planning to look at the other thread mentioned in
my previous email? We need to finish that before this.I check it now - it has sense. the described switch can protect users against useless client killing.
I have committed that patch. Please find the rebased patch attached.
Additionally, I ran the pgindent. I will wait for two days and see if
you or anyone else has more inputs on the patch, if not, then I will
take one more pass and commit it on Wednesday morning.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191111.amit.patchapplication/octet-stream; name=drop-database-force-20191111.amit.patchDownload
From 06f1b7d80a2ef01703df0108846d862329bc309e Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Mon, 11 Nov 2019 08:37:01 +0530
Subject: [PATCH] Introduce the 'force' option for the Drop Database command.
This new option terminates the other sessions connected to the target
database and then drop it. To terminate other sessions, the current user
must have desired permissions (same as pg_terminate_backend()). We don't
allow to terminate the sessions if prepared transactions, active logical
replication slots or subscriptions are present in the target database.
Author: Pavel Stehule with some changes by me
Reviewed-by: Dilip Kumar, Vignesh C, Ibrar Ahmed, Anthony Nowocien,
Ryan Lambert and Amit Kapila
Discussion: https://postgr.es/m/CAP_rwwmLJJbn70vLOZFpxGw3XD7nLB_7+NKz46H5EOO2k5H7OQ@mail.gmail.com
---
doc/src/sgml/ref/drop_database.sgml | 33 +++++++-
src/backend/commands/dbcommands.c | 34 +++++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/parser/gram.y | 43 +++++++++-
src/backend/storage/ipc/procarray.c | 114 +++++++++++++++++++++++++++
src/backend/tcop/utility.c | 10 +--
src/bin/psql/tab-complete.c | 4 +
src/include/commands/dbcommands.h | 3 +-
src/include/nodes/parsenodes.h | 1 +
src/include/storage/procarray.h | 1 +
src/test/regress/expected/drop_if_exists.out | 11 +++
src/test/regress/sql/drop_if_exists.sql | 8 ++
13 files changed, 250 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c9..cef1b90 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ It cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ Also, if anyone else is connected to the target database, this command will
+ fail unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,25 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate if prepared transactions, active logical replication
+ slots or subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described in
+ <xref linkend="functions-admin-signal"/>. This will also fail if we
+ are not able to terminate connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 4ad62e6..a5f67ab 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -811,7 +811,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -911,6 +911,14 @@ dropdb(const char *dbname, bool missing_ok)
"There are %d subscriptions.",
nsubscriptions, nsubscriptions)));
+
+ /*
+ * Attempt to terminate all existing connections to the target database if
+ * the user has requested to do so.
+ */
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1431,6 +1439,30 @@ movedb_failure_callback(int code, Datum arg)
(void) rmtree(dstpath, true);
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* ALTER DATABASE name ...
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb9..2f267e4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014..da0e1d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..2f7bd66 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but the syntax is designed
+ * to be extensible so that we can add more options in the future if required.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da5307..13bcbe7 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2971,6 +2973,118 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
}
/*
+ * Terminate existing connections to the specified database. This routine
+ * is used by the DROP DATABASE command when user has asked to forcefully
+ * drop the database.
+ *
+ * The current backend is always ignored; it is caller's responsibility to
+ * check whether the current backend uses the given DB, if it's important.
+ *
+ * It doesn't allow to terminate the connections even if there is a one
+ * backend with the prepared transaction in the target database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Check whether we have the necessary rights to terminate other
+ * sessions. We don't terminate any session untill we ensure that we
+ * have rights on all the sessions to be terminated. These checks are
+ * the same as we do in pg_terminate_backend.
+ *
+ * In this case we don't raise some warnings - like "PID %d is not a
+ * PostgreSQL server process", because for us already finished session
+ * is not a problem.
+ */
+ foreach(lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /*
+ * There's a race condition here: once we release the ProcArrayLock,
+ * it's possible for the session to exit before we issue kill. That
+ * race condition possibility seems too unlikely to worry about. See
+ * pg_signal_backend.
+ */
+ foreach(lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /*
+ * If we have setsid(), signal the backend's whole process
+ * group
+ */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+ }
+}
+
+/*
* ProcArraySetReplicationSlotXmin
*
* Install limits to future computations of the xmin horizon to prevent vacuum
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index e984545..6d85304 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -596,13 +596,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2b1e3cd..98c917b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,10 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") && (ends_with(prev_wd, '(')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c815..d1e91a2 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..ff626cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672..8f67b86 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467..eba0f3f 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,14 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+ERROR: database "not_exists" does not exist
+drop database not_exists with (force);
+ERROR: database "not_exists" does not exist
+drop database if exists not_exists (force);
+NOTICE: database "not_exists" does not exist, skipping
+drop database if exists not_exists with (force);
+NOTICE: database "not_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1..03aebb0 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,11 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+--
+-- DROP DATABASE FORCE test of syntax (should not to raise syntax error)
+--
+drop database not_exists (force);
+drop database not_exists with (force);
+drop database if exists not_exists (force);
+drop database if exists not_exists with (force);
--
1.8.3.1
On Mon, Nov 11, 2019 at 8:45 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Nov 8, 2019 at 4:57 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
I check it now - it has sense. the described switch can protect users against useless client killing.
I have committed that patch. Please find the rebased patch attached.
Additionally, I ran the pgindent. I will wait for two days and see if
you or anyone else has more inputs on the patch, if not, then I will
take one more pass and commit it on Wednesday morning.
This patch adds a few test cases to test the syntax in
/src/test/regress/sql/drop_if_exists.sql which I think is not the best
place to add the tests for this feature as it is primarily testing ..
IF EXISTS .. syntax. OTOH, I am not sure if there is any other better
place to add those. The other option could be to add a new test file,
but again I am not sure if it is worth to do so for a few tests. We
can even decide not to have tests for this feature as the tests are
just testing the syntax which I think can help if we want to extend
the syntax in the future.
Any opinions?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Nov 11, 2019 at 1:57 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
This patch adds a few test cases to test the syntax in
/src/test/regress/sql/drop_if_exists.sql which I think is not the best
place to add the tests for this feature as it is primarily testing ..
IF EXISTS .. syntax. OTOH, I am not sure if there is any other better
place to add those. The other option could be to add a new test file,
but again I am not sure if it is worth to do so for a few tests. We
can even decide not to have tests for this feature as the tests are
just testing the syntax which I think can help if we want to extend
the syntax in the future.
Today, I again looked at some of the other drop command syntaxes and
it seems some of them like 'Drop Extension ..' are also tested via
this file. So, I decided to keep the test in this file but changed
the name of the database used in the test to match with other tests in
that file and modified comments.
I am planning to commit this patch tomorrow unless I see more comments
or interest from someone else to review this.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
drop-database-force-20191112.amit.patchapplication/octet-stream; name=drop-database-force-20191112.amit.patchDownload
From 90fe9333ba68fcb050176b8971ec2de77969a9f2 Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Tue, 12 Nov 2019 11:06:13 +0530
Subject: [PATCH] Introduce the 'force' option for the Drop Database command.
This new option terminates the other sessions connected to the target
database and then drop it. To terminate other sessions, the current user
must have desired permissions (same as pg_terminate_backend()). We don't
allow to terminate the sessions if prepared transactions, active logical
replication slots or subscriptions are present in the target database.
Author: Pavel Stehule with changes by me
Reviewed-by: Dilip Kumar, Vignesh C, Ibrar Ahmed, Anthony Nowocien,
Ryan Lambert and Amit Kapila
Discussion: https://postgr.es/m/CAP_rwwmLJJbn70vLOZFpxGw3XD7nLB_7+NKz46H5EOO2k5H7OQ@mail.gmail.com
---
doc/src/sgml/ref/drop_database.sgml | 33 +++++++-
src/backend/commands/dbcommands.c | 34 +++++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/parser/gram.y | 43 +++++++++-
src/backend/storage/ipc/procarray.c | 114 +++++++++++++++++++++++++++
src/backend/tcop/utility.c | 10 +--
src/bin/psql/tab-complete.c | 4 +
src/include/commands/dbcommands.h | 3 +-
src/include/nodes/parsenodes.h | 1 +
src/include/storage/procarray.h | 1 +
src/test/regress/expected/drop_if_exists.out | 10 +++
src/test/regress/sql/drop_if_exists.sql | 7 ++
13 files changed, 248 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml
index 3ac06c9..cef1b90 100644
--- a/doc/src/sgml/ref/drop_database.sgml
+++ b/doc/src/sgml/ref/drop_database.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be:</phrase>
+
+ FORCE
</synopsis>
</refsynopsisdiv>
@@ -32,9 +36,11 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
<command>DROP DATABASE</command> drops a database. It removes the
catalog entries for the database and deletes the directory
containing the data. It can only be executed by the database owner.
- Also, it cannot be executed while you or anyone else are connected
- to the target database. (Connect to <literal>postgres</literal> or any
- other database to issue this command.)
+ It cannot be executed while you are connected to the target database.
+ (Connect to <literal>postgres</literal> or any other database to issue this
+ command.)
+ Also, if anyone else is connected to the target database, this command will
+ fail unless you use the <literal>FORCE</literal> option described below.
</para>
<para>
@@ -64,6 +70,25 @@ DROP DATABASE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>FORCE</literal></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database.
+ It doesn't terminate if prepared transactions, active logical replication
+ slots or subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions to terminate other
+ connections. Required permissions are the same as with
+ <literal>pg_terminate_backend</literal>, described in
+ <xref linkend="functions-admin-signal"/>. This will also fail if we
+ are not able to terminate connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 6f28859..446813f 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -810,7 +810,7 @@ createdb_failure_callback(int code, Datum arg)
* DROP DATABASE
*/
void
-dropdb(const char *dbname, bool missing_ok)
+dropdb(const char *dbname, bool missing_ok, bool force)
{
Oid db_id;
bool db_istemplate;
@@ -910,6 +910,14 @@ dropdb(const char *dbname, bool missing_ok)
"There are %d subscriptions.",
nsubscriptions, nsubscriptions)));
+
+ /*
+ * Attempt to terminate all existing connections to the target database if
+ * the user has requested to do so.
+ */
+ if (force)
+ TerminateOtherDBBackends(db_id);
+
/*
* Check for other backends in the target database. (Because we hold the
* database lock, no new ones can start after this.)
@@ -1430,6 +1438,30 @@ movedb_failure_callback(int code, Datum arg)
(void) rmtree(dstpath, true);
}
+/*
+ * Process options and call dropdb function.
+ */
+void
+DropDatabase(ParseState *pstate, DropdbStmt *stmt)
+{
+ bool force = false;
+ ListCell *lc;
+
+ foreach(lc, stmt->options)
+ {
+ DefElem *opt = (DefElem *) lfirst(lc);
+
+ if (strcmp(opt->defname, "force") == 0)
+ force = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized DROP DATABASE option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ dropdb(stmt->dbname, stmt->missing_ok, force);
+}
/*
* ALTER DATABASE name ...
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb9..2f267e4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3868,6 +3868,7 @@ _copyDropdbStmt(const DropdbStmt *from)
COPY_STRING_FIELD(dbname);
COPY_SCALAR_FIELD(missing_ok);
+ COPY_NODE_FIELD(options);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014..da0e1d1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1676,6 +1676,7 @@ _equalDropdbStmt(const DropdbStmt *a, const DropdbStmt *b)
{
COMPARE_STRING_FIELD(dbname);
COMPARE_SCALAR_FIELD(missing_ok);
+ COMPARE_NODE_FIELD(options);
return true;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..2f7bd66 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> vac_analyze_option_elem
%type <list> vac_analyze_option_list
%type <node> vac_analyze_option_arg
+%type <defelt> drop_option
%type <boolean> opt_or_replace
opt_grant_grant_option opt_grant_admin_option
opt_nowait opt_if_exists opt_with_data
@@ -406,6 +407,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
publication_name_list
vacuum_relation_list opt_vacuum_relation_list
+ drop_option_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -10213,7 +10215,7 @@ AlterDatabaseSetStmt:
/*****************************************************************************
*
- * DROP DATABASE [ IF EXISTS ]
+ * DROP DATABASE [ IF EXISTS ] dbname [ [ WITH ] ( options ) ]
*
* This is implicitly CASCADE, no need for drop behavior
*****************************************************************************/
@@ -10223,6 +10225,7 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $3;
n->missing_ok = false;
+ n->options = NULL;
$$ = (Node *)n;
}
| DROP DATABASE IF_P EXISTS database_name
@@ -10230,10 +10233,48 @@ DropdbStmt: DROP DATABASE database_name
DropdbStmt *n = makeNode(DropdbStmt);
n->dbname = $5;
n->missing_ok = true;
+ n->options = NULL;
$$ = (Node *)n;
}
+ | DROP DATABASE database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $3;
+ n->missing_ok = false;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
+ | DROP DATABASE IF_P EXISTS database_name opt_with '(' drop_option_list ')'
+ {
+ DropdbStmt *n = makeNode(DropdbStmt);
+ n->dbname = $5;
+ n->missing_ok = true;
+ n->options = $8;
+ $$ = (Node *)n;
+ }
+ ;
+
+drop_option_list:
+ drop_option
+ {
+ $$ = list_make1((Node *) $1);
+ }
+ | drop_option_list ',' drop_option
+ {
+ $$ = lappend($1, (Node *) $3);
+ }
;
+/*
+ * Currently only the FORCE option is supported, but the syntax is designed
+ * to be extensible so that we can add more options in the future if required.
+ */
+drop_option:
+ FORCE
+ {
+ $$ = makeDefElem("force", NULL, @1);
+ }
+ ;
/*****************************************************************************
*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da5307..13bcbe7 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -52,6 +52,8 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/pg_authid.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
@@ -2971,6 +2973,118 @@ CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared)
}
/*
+ * Terminate existing connections to the specified database. This routine
+ * is used by the DROP DATABASE command when user has asked to forcefully
+ * drop the database.
+ *
+ * The current backend is always ignored; it is caller's responsibility to
+ * check whether the current backend uses the given DB, if it's important.
+ *
+ * It doesn't allow to terminate the connections even if there is a one
+ * backend with the prepared transaction in the target database.
+ */
+void
+TerminateOtherDBBackends(Oid databaseId)
+{
+ ProcArrayStruct *arrayP = procArray;
+ List *pids = NIL;
+ int nprepared = 0;
+ int i;
+
+ LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+ for (i = 0; i < procArray->numProcs; i++)
+ {
+ int pgprocno = arrayP->pgprocnos[i];
+ PGPROC *proc = &allProcs[pgprocno];
+
+ if (proc->databaseId != databaseId)
+ continue;
+ if (proc == MyProc)
+ continue;
+
+ if (proc->pid != 0)
+ pids = lappend_int(pids, proc->pid);
+ else
+ nprepared++;
+ }
+
+ LWLockRelease(ProcArrayLock);
+
+ if (nprepared > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("database \"%s\" is being used by prepared transaction",
+ get_database_name(databaseId)),
+ errdetail_plural("There is %d prepared transaction using the database.",
+ "There are %d prepared transactions using the database.",
+ nprepared,
+ nprepared)));
+
+ if (pids)
+ {
+ ListCell *lc;
+
+ /*
+ * Check whether we have the necessary rights to terminate other
+ * sessions. We don't terminate any session untill we ensure that we
+ * have rights on all the sessions to be terminated. These checks are
+ * the same as we do in pg_terminate_backend.
+ *
+ * In this case we don't raise some warnings - like "PID %d is not a
+ * PostgreSQL server process", because for us already finished session
+ * is not a problem.
+ */
+ foreach(lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /* Only allow superusers to signal superuser-owned backends. */
+ if (superuser_arg(proc->roleId) && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a superuser to terminate superuser process"))));
+
+ /* Users can signal backends they have role membership in. */
+ if (!has_privs_of_role(GetUserId(), proc->roleId) &&
+ !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be a member of the role whose process is being terminated or member of pg_signal_backend"))));
+ }
+ }
+
+ /*
+ * There's a race condition here: once we release the ProcArrayLock,
+ * it's possible for the session to exit before we issue kill. That
+ * race condition possibility seems too unlikely to worry about. See
+ * pg_signal_backend.
+ */
+ foreach(lc, pids)
+ {
+ int pid = lfirst_int(lc);
+ PGPROC *proc = BackendPidGetProc(pid);
+
+ if (proc != NULL)
+ {
+ /*
+ * If we have setsid(), signal the backend's whole process
+ * group
+ */
+#ifdef HAVE_SETSID
+ (void) kill(-pid, SIGTERM);
+#else
+ (void) kill(pid, SIGTERM);
+#endif
+ }
+ }
+ }
+}
+
+/*
* ProcArraySetReplicationSlotXmin
*
* Install limits to future computations of the xmin horizon to prevent vacuum
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6787d8e..3a03ca7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -595,13 +595,9 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DropdbStmt:
- {
- DropdbStmt *stmt = (DropdbStmt *) parsetree;
-
- /* no event triggers for global objects */
- PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
- dropdb(stmt->dbname, stmt->missing_ok);
- }
+ /* no event triggers for global objects */
+ PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
+ DropDatabase(pstate, (DropdbStmt *) parsetree);
break;
/* Query-level asynchronous notification */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 2b1e3cd..98c917b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2844,6 +2844,10 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
else if (Matches("DROP", "FOREIGN"))
COMPLETE_WITH("DATA WRAPPER", "TABLE");
+ else if (Matches("DROP", "DATABASE", MatchAny))
+ COMPLETE_WITH("WITH (");
+ else if (HeadMatches("DROP", "DATABASE") && (ends_with(prev_wd, '(')))
+ COMPLETE_WITH("FORCE");
/* DROP INDEX */
else if (Matches("DROP", "INDEX"))
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 154c815..d1e91a2 100644
--- a/src/include/commands/dbcommands.h
+++ b/src/include/commands/dbcommands.h
@@ -20,7 +20,8 @@
#include "nodes/parsenodes.h"
extern Oid createdb(ParseState *pstate, const CreatedbStmt *stmt);
-extern void dropdb(const char *dbname, bool missing_ok);
+extern void dropdb(const char *dbname, bool missing_ok, bool force);
+extern void DropDatabase(ParseState *pstate, DropdbStmt *stmt);
extern ObjectAddress RenameDatabase(const char *oldname, const char *newname);
extern Oid AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel);
extern Oid AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..ff626cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3145,6 +3145,7 @@ typedef struct DropdbStmt
NodeTag type;
char *dbname; /* database to drop */
bool missing_ok; /* skip error if db is missing? */
+ List *options; /* currently only FORCE is supported */
} DropdbStmt;
/* ----------------------
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672..8f67b86 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -113,6 +113,7 @@ extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conf
extern int CountUserBackends(Oid roleid);
extern bool CountOtherDBBackends(Oid databaseId,
int *nbackends, int *nprepared);
+extern void TerminateOtherDBBackends(Oid databaseId);
extern void XidCacheRemoveRunningXids(TransactionId xid,
int nxids, const TransactionId *xids,
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 6a17467..8f37e64 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -330,3 +330,13 @@ HINT: Specify the argument list to select the routine unambiguously.
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+-- Drop the database. This test checks both the functionality of
+-- 'if exists' and the syntax of the command.
+drop database test_database_exists (force);
+ERROR: database "test_database_exists" does not exist
+drop database test_database_exists with (force);
+ERROR: database "test_database_exists" does not exist
+drop database if exists test_database_exists (force);
+NOTICE: database "test_database_exists" does not exist, skipping
+drop database if exists test_database_exists with (force);
+NOTICE: database "test_database_exists" does not exist, skipping
diff --git a/src/test/regress/sql/drop_if_exists.sql b/src/test/regress/sql/drop_if_exists.sql
index 8a791b1..ec29c00 100644
--- a/src/test/regress/sql/drop_if_exists.sql
+++ b/src/test/regress/sql/drop_if_exists.sql
@@ -295,3 +295,10 @@ DROP ROUTINE IF EXISTS test_ambiguous_procname;
-- cleanup
DROP PROCEDURE test_ambiguous_procname(int);
DROP PROCEDURE test_ambiguous_procname(text);
+
+-- Drop the database. This test checks both the functionality of
+-- 'if exists' and the syntax of the command.
+drop database test_database_exists (force);
+drop database test_database_exists with (force);
+drop database if exists test_database_exists (force);
+drop database if exists test_database_exists with (force);
--
1.8.3.1
On Tue, Nov 12, 2019 at 11:17 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
I am planning to commit this patch tomorrow unless I see more comments
or interest from someone else to review this.
Pushed. Pavel, feel free to submit dropdb utility-related patch if you want.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
st 13. 11. 2019 v 7:12 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Tue, Nov 12, 2019 at 11:17 AM Amit Kapila <amit.kapila16@gmail.com>
wrote:I am planning to commit this patch tomorrow unless I see more comments
or interest from someone else to review this.Pushed. Pavel, feel free to submit dropdb utility-related patch if you
want.
I hope I send this patch today. It's simply job.
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
st 13. 11. 2019 v 7:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
st 13. 11. 2019 v 7:12 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:On Tue, Nov 12, 2019 at 11:17 AM Amit Kapila <amit.kapila16@gmail.com>
wrote:I am planning to commit this patch tomorrow unless I see more comments
or interest from someone else to review this.Pushed. Pavel, feel free to submit dropdb utility-related patch if you
want.I hope I send this patch today. It's simply job.
here it is. It's based on Filip Rembialkowski's patch if I remember
correctly
Pavel
Show quoted text
Pavel
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
dropdb-f.patchtext/x-patch; charset=US-ASCII; name=dropdb-f.patchDownload
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..5d10ad1c92 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..3a6aac8ac3 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,9 +128,6 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,12 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ /* now, only FORCE option can be used, so usage is very simple */
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""),
+ fmtId(dbname),
+ force ? " WITH (FORCE)" : "");
+
if (echo)
printf("%s\n", sql.data);
result = PQexec(conn, sql.data);
@@ -159,6 +167,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force force termination of connected backends\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..c51babe093 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
st 13. 11. 2019 v 15:34 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
st 13. 11. 2019 v 7:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:st 13. 11. 2019 v 7:12 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:On Tue, Nov 12, 2019 at 11:17 AM Amit Kapila <amit.kapila16@gmail.com>
wrote:I am planning to commit this patch tomorrow unless I see more comments
or interest from someone else to review this.Pushed. Pavel, feel free to submit dropdb utility-related patch if you
want.I hope I send this patch today. It's simply job.
here it is. It's based on Filip Rembialkowski's patch if I remember
correctly
have I this patch assign to next commitfest or you process it in this
commitfest?
This part is trivial
Pavel
Show quoted text
Pavel
Pavel
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Nov 14, 2019 at 11:43 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
st 13. 11. 2019 v 15:34 odesílatel Pavel Stehule <pavel.stehule@gmail.com> napsal:
here it is. It's based on Filip Rembialkowski's patch if I remember correctly
have I this patch assign to next commitfest or you process it in this commitfest?
I will try to review in this CF only, but if I fail to do so, any way
you can register it in new CF after this CF.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Nov 14, 2019 at 12:14 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Thu, Nov 14, 2019 at 11:43 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
st 13. 11. 2019 v 15:34 odesílatel Pavel Stehule <pavel.stehule@gmail.com> napsal:
here it is. It's based on Filip Rembialkowski's patch if I remember correctly
have I this patch assign to next commitfest or you process it in this commitfest?
I will try to review in this CF only,
This is the reason I haven't yet marked the CF entry for this patch as
committed.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
čt 14. 11. 2019 v 7:44 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Thu, Nov 14, 2019 at 11:43 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:st 13. 11. 2019 v 15:34 odesílatel Pavel Stehule <
pavel.stehule@gmail.com> napsal:
here it is. It's based on Filip Rembialkowski's patch if I remember
correctly
have I this patch assign to next commitfest or you process it in this
commitfest?
I will try to review in this CF only, but if I fail to do so, any way
you can register it in new CF after this CF.
ok.
there is enough long time to do.
Regards
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Wed, Nov 13, 2019 at 8:05 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
st 13. 11. 2019 v 7:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com> napsal:
st 13. 11. 2019 v 7:12 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
On Tue, Nov 12, 2019 at 11:17 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
I am planning to commit this patch tomorrow unless I see more comments
or interest from someone else to review this.Pushed. Pavel, feel free to submit dropdb utility-related patch if you want.
I hope I send this patch today. It's simply job.
here it is. It's based on Filip Rembialkowski's patch if I remember correctly
Thanks for working on the patch.
Few minor comments:
+ Force termination of connected backends before removing the database.
+ </para>
+ <para>
+ This will add the <literal>FORCE</literal> option to the <literal>DROP
+ DATABASE</literal> command sent to the server.
+ </para>
The above documentation can be changed similar to drop_database.sgml:
<para>
Attempt to terminate all existing connections to the target database.
It doesn't terminate if prepared transactions, active logical replication
slots or subscriptions are present in the target database.
</para>
<para>
This will fail if the current user has no permissions to terminate other
connections. Required permissions are the same as with
<literal>pg_terminate_backend</literal>, described in
<xref linkend="functions-admin-signal"/>. This will also fail if we
are not able to terminate connections.
</para>
We can make the modification in the same location as earlier in the below case:
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
-
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
maintenance_db = "template1";
@@ -134,6 +136,12 @@ main(int argc, char *argv[])
host, port, username, prompt_password,
progname, echo);
+ /* now, only FORCE option can be used, so usage is very simple */
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""),
+ fmtId(dbname),
+ force ? " WITH (FORCE)" : "");
+
We can slightly rephrase the below:
+ printf(_(" -f, --force force termination of
connected backends\n"));
can be changed to:
+ printf(_(" -f, --force terminate the existing
connections to the target database forcefully\n"));
We can slightly rephrase the below:
+ /* now, only FORCE option can be used, so usage is very simple */
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
can be changed to:
+ /* Generate drop db command using the options specified */
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
čt 14. 11. 2019 v 12:12 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Wed, Nov 13, 2019 at 8:05 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:st 13. 11. 2019 v 7:13 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
st 13. 11. 2019 v 7:12 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Tue, Nov 12, 2019 at 11:17 AM Amit Kapila <amit.kapila16@gmail.com>
wrote:
I am planning to commit this patch tomorrow unless I see more
comments
or interest from someone else to review this.
Pushed. Pavel, feel free to submit dropdb utility-related patch if
you want.
I hope I send this patch today. It's simply job.
here it is. It's based on Filip Rembialkowski's patch if I remember
correctly
Thanks for working on the patch. Few minor comments: + Force termination of connected backends before removing the database. + </para> + <para> + This will add the <literal>FORCE</literal> option to the <literal>DROP + DATABASE</literal> command sent to the server. + </para>The above documentation can be changed similar to drop_database.sgml:
<para>
Attempt to terminate all existing connections to the target database.
It doesn't terminate if prepared transactions, active logical
replication
slots or subscriptions are present in the target database.
</para>
<para>
This will fail if the current user has no permissions to terminate
other
connections. Required permissions are the same as with
<literal>pg_terminate_backend</literal>, described in
<xref linkend="functions-admin-signal"/>. This will also fail if we
are not able to terminate connections.
</para>
done
We can make the modification in the same location as earlier in the below case: - appendPQExpBuffer(&sql, "DROP DATABASE %s%s;", - (if_exists ? "IF EXISTS " : ""), fmtId(dbname)); - /* Avoid trying to drop postgres db while we are connected to it. */ if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0) maintenance_db = "template1"; @@ -134,6 +136,12 @@ main(int argc, char *argv[]) host, port, username, prompt_password, progname, echo);+ /* now, only FORCE option can be used, so usage is very simple */ + appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;", + (if_exists ? "IF EXISTS " : ""), + fmtId(dbname), + force ? " WITH (FORCE)" : ""); +
done
We can slightly rephrase the below: + printf(_(" -f, --force force termination of connected backends\n")); can be changed to: + printf(_(" -f, --force terminate the existing connections to the target database forcefully\n"));
the proposed text is too long - I changed the sentence, and any other can
fix it
We can slightly rephrase the below: + /* now, only FORCE option can be used, so usage is very simple */ + appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;", can be changed to: + /* Generate drop db command using the options specified */ + appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
I would to say, so generating is simple due only one supported option. If
we support two or more options there, then the code should be more complex.
I changed comment to
/* Currently, only FORCE option is supported */
updated patch attached
Thank you for review
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
dropdb-f-20191115.patchtext/x-patch; charset=US-ASCII; name=dropdb-f-20191115.patchDownload
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..63b90005b4 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,27 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the
+ target database. It doesn't terminate if prepared
+ transactions, active logical replication slots or
+ subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions
+ to terminate other connections. Required permissions
+ are the same as with <literal>pg_terminate_backend</literal>,
+ described in <xref linkend="functions-admin-signal"/>.
+ This will also fail if we are not able to terminate
+ connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..ea7e5d8f75 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,8 +128,11 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+ /* Currently, only FORCE option is supported */
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""),
+ fmtId(dbname),
+ force ? " WITH (FORCE)" : "");
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
@@ -159,6 +167,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force try to terminate other connection before\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..c51babe093 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
On Fri, Nov 15, 2019 at 1:23 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
updated patch attached
Thanks Pavel for providing updated version.
Few comments:
I felt the help text seems incomplete:
@@ -159,6 +167,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being
sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force try to terminate other
connection before\n"));
printf(_(" -V, --version output version information,
then exit\n"));
we can change to:
printf(_(" -f, --force try to terminate other
connection before dropping\n"));
We can add one test including -e option which validates the command
generation including WITH (FORCE):
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
Also should we include one test where one session is connected to db
and another session tries dropping with -f option?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
so 16. 11. 2019 v 1:10 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Fri, Nov 15, 2019 at 1:23 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:updated patch attached
Thanks Pavel for providing updated version. Few comments: I felt the help text seems incomplete: @@ -159,6 +167,7 @@ help(const char *progname) printf(_("\nOptions:\n")); printf(_(" -e, --echo show the commands being sent to the server\n")); printf(_(" -i, --interactive prompt before deleting anything\n")); + printf(_(" -f, --force try to terminate other connection before\n")); printf(_(" -V, --version output version information, then exit\n")); we can change to: printf(_(" -f, --force try to terminate other connection before dropping\n"));
done. maybe alternative can be "first try to terminate other connections".
It is shorter. The current text has 78 chars, what should be acceptable
We can add one test including -e option which validates the command generation including WITH (FORCE): +$node->safe_psql('postgres', 'CREATE DATABASE foobar2'); +$node->issues_sql_like( + [ 'dropdb', '--force', 'foobar2' ], + qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/, + 'SQL DROP DATABASE (FORCE) run'); +
I don't understand to this point. It is effectively same like existing test
Also should we include one test where one session is connected to db
and another session tries dropping with -f option?
I afraid so test API doesn't allow asynchronous operations. Do you have any
idea, how to it?
Regards
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
dropdb-f-20191116.patchtext/x-patch; charset=US-ASCII; name=dropdb-f-20191116.patchDownload
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33716..63b90005b4 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -86,6 +86,27 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the
+ target database. It doesn't terminate if prepared
+ transactions, active logical replication slots or
+ subscriptions are present in the target database.
+ </para>
+ <para>
+ This will fail if the current user has no permissions
+ to terminate other connections. Required permissions
+ are the same as with <literal>pg_terminate_backend</literal>,
+ described in <xref linkend="functions-admin-signal"/>.
+ This will also fail if we are not able to terminate
+ connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5f1d..8aab5f1eaf 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,8 +128,11 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+ /* Currently, only FORCE option is supported */
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""),
+ fmtId(dbname),
+ force ? " WITH (FORCE)" : "");
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
@@ -159,6 +167,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force try to terminate other connections before dropping\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a4ae..c51babe093 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
On Sat, Nov 16, 2019 at 1:25 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
updated patch attached
Thanks Pavel for providing updated version. Few comments: I felt the help text seems incomplete: @@ -159,6 +167,7 @@ help(const char *progname) printf(_("\nOptions:\n")); printf(_(" -e, --echo show the commands being sent to the server\n")); printf(_(" -i, --interactive prompt before deleting anything\n")); + printf(_(" -f, --force try to terminate other connection before\n")); printf(_(" -V, --version output version information, then exit\n")); we can change to: printf(_(" -f, --force try to terminate other connection before dropping\n"));done. maybe alternative can be "first try to terminate other connections". It is shorter. The current text has 78 chars, what should be acceptable
We can add one test including -e option which validates the command generation including WITH (FORCE): +$node->safe_psql('postgres', 'CREATE DATABASE foobar2'); +$node->issues_sql_like( + [ 'dropdb', '--force', 'foobar2' ], + qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/, + 'SQL DROP DATABASE (FORCE) run'); +I don't understand to this point. It is effectively same like existing test
When we don't specify -e option, the query used to drop db will not be
printed like below:
./dropdb testdb1
When we specify -e option, the query used to drop db will be printed like below:
./dropdb -e testdb2
SELECT pg_catalog.set_config('search_path', '', false);
DROP DATABASE testdb2;
If we specify -e option, the query that is being used to drop db will
be printed. In the existing test I could not see the inclusion of -e
option. I was thinking to add a test including -e that way the query
that includes force option gets validated.
Also should we include one test where one session is connected to db
and another session tries dropping with -f option?I afraid so test API doesn't allow asynchronous operations. Do you have any idea, how to it?
I had seen that isolation test(src/test/isolation) has a framework to
support this. You can have a look to see if it can be handled using
that.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
po 18. 11. 2019 v 4:43 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Sat, Nov 16, 2019 at 1:25 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:updated patch attached
Thanks Pavel for providing updated version.
Few comments:
I felt the help text seems incomplete:
@@ -159,6 +167,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being
sent to the server\n"));
printf(_(" -i, --interactive prompt before deletinganything\n"));
+ printf(_(" -f, --force try to terminate other
connection before\n"));
printf(_(" -V, --version output version information,
then exit\n"));
we can change to:
printf(_(" -f, --force try to terminate other
connection before dropping\n"));done. maybe alternative can be "first try to terminate other
connections". It is shorter. The current text has 78 chars, what should be
acceptableWe can add one test including -e option which validates the command generation including WITH (FORCE): +$node->safe_psql('postgres', 'CREATE DATABASE foobar2'); +$node->issues_sql_like( + [ 'dropdb', '--force', 'foobar2' ], + qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/, + 'SQL DROP DATABASE (FORCE) run'); +I don't understand to this point. It is effectively same like existing
test
When we don't specify -e option, the query used to drop db will not be
printed like below:
./dropdb testdb1
When we specify -e option, the query used to drop db will be printed like
below:
./dropdb -e testdb2
SELECT pg_catalog.set_config('search_path', '', false);
DROP DATABASE testdb2;
If we specify -e option, the query that is being used to drop db will
be printed. In the existing test I could not see the inclusion of -e
option. I was thinking to add a test including -e that way the query
that includes force option gets validated.
still I don't understand. The created query is tested already by current
test.
Do you want to test just -e option? Then it should be done as separate
issue. Do this now is little bit messy.
Also should we include one test where one session is connected to db
and another session tries dropping with -f option?I afraid so test API doesn't allow asynchronous operations. Do you have
any idea, how to it?
I had seen that isolation test(src/test/isolation) has a framework to
support this. You can have a look to see if it can be handled using
that.
I'll look there
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Mon, Nov 18, 2019 at 10:33 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 18. 11. 2019 v 4:43 odesílatel vignesh C <vignesh21@gmail.com> napsal:
When we don't specify -e option, the query used to drop db will not be
printed like below:
./dropdb testdb1
When we specify -e option, the query used to drop db will be printed like below:
./dropdb -e testdb2
SELECT pg_catalog.set_config('search_path', '', false);
DROP DATABASE testdb2;
If we specify -e option, the query that is being used to drop db will
be printed. In the existing test I could not see the inclusion of -e
option. I was thinking to add a test including -e that way the query
that includes force option gets validated.still I don't understand. The created query is tested already by current test.
Do you want to test just -e option?
Yeah, it seems Vignesh wants to do that. It will help in verifying
that the command generated by code is correct. However, I think there
is no pressing need to have an additional test for this.
Then it should be done as separate issue.
Yeah, I agree. I think this can be done as a separate test patch to
improve coverage if someone wants.
Also should we include one test where one session is connected to db
and another session tries dropping with -f option?I afraid so test API doesn't allow asynchronous operations. Do you have any idea, how to it?
I had seen that isolation test(src/test/isolation) has a framework to
support this. You can have a look to see if it can be handled using
that.I'll look there
If we want to have a test for this, then you might want to look at
test src/test/recovery/t/013_crash_restart. In that test, we keep a
connection open and then validate whether it is terminated. Having
said that, I think it might be better to add this as a separate test
patch apart from main patch because it is a bit of a timing-dependent
test and might fail on some slow machines. We can always revert this
if it turns out to be an unstable test.
I have slightly modified the main patch and attached is the result.
Basically, I don't see any need to repeat what is mentioned in the
Drop Database page. Let me know what you guys think?
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
dropdb-f-20191118.amit.patchapplication/octet-stream; name=dropdb-f-20191118.amit.patchDownload
From c11b76d097b087cf44767d482cb38842101a7df8 Mon Sep 17 00:00:00 2001
From: Amit Kapila <akapila@postgresql.org>
Date: Mon, 18 Nov 2019 10:28:32 +0530
Subject: [PATCH] Add the support for '-f' option in dropdb utility.
Specifying '-f' will add the 'force' option to the DROP DATABASE command
sent to the server. This will try to terminate all existing connections
to the target database before dropping it.
Author: Pavel Stehule
Reviewed-by: Vignesh C and Amit Kapila
Discussion: https://postgr.es/m/CAP_rwwmLJJbn70vLOZFpxGw3XD7nLB_7+NKz46H5EOO2k5H7OQ@mail.gmail.com
---
doc/src/sgml/ref/dropdb.sgml | 12 ++++++++++++
src/bin/scripts/dropdb.c | 14 +++++++++++---
src/bin/scripts/t/050_dropdb.pl | 8 +++++++-
3 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/doc/src/sgml/ref/dropdb.sgml b/doc/src/sgml/ref/dropdb.sgml
index 3fbdb33..f79bbb9 100644
--- a/doc/src/sgml/ref/dropdb.sgml
+++ b/doc/src/sgml/ref/dropdb.sgml
@@ -87,6 +87,18 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>
+ Attempt to terminate all existing connections to the target database
+ before dropping it. See <xref linkend="sql-dropdatabase"/> for more
+ information on this option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-V</option></term>
<term><option>--version</option></term>
<listitem>
diff --git a/src/bin/scripts/dropdb.c b/src/bin/scripts/dropdb.c
index dacd8e5..382bff9 100644
--- a/src/bin/scripts/dropdb.c
+++ b/src/bin/scripts/dropdb.c
@@ -34,6 +34,7 @@ main(int argc, char *argv[])
{"interactive", no_argument, NULL, 'i'},
{"if-exists", no_argument, &if_exists, 1},
{"maintenance-db", required_argument, NULL, 2},
+ {"force", no_argument, NULL, 'f'},
{NULL, 0, NULL, 0}
};
@@ -49,6 +50,7 @@ main(int argc, char *argv[])
enum trivalue prompt_password = TRI_DEFAULT;
bool echo = false;
bool interactive = false;
+ bool force = false;
PQExpBufferData sql;
@@ -61,7 +63,7 @@ main(int argc, char *argv[])
handle_help_version_opts(argc, argv, "dropdb", help);
- while ((c = getopt_long(argc, argv, "h:p:U:wWei", long_options, &optindex)) != -1)
+ while ((c = getopt_long(argc, argv, "h:p:U:wWeif", long_options, &optindex)) != -1)
{
switch (c)
{
@@ -86,6 +88,9 @@ main(int argc, char *argv[])
case 'i':
interactive = true;
break;
+ case 'f':
+ force = true;
+ break;
case 0:
/* this covers the long options */
break;
@@ -123,8 +128,10 @@ main(int argc, char *argv[])
initPQExpBuffer(&sql);
- appendPQExpBuffer(&sql, "DROP DATABASE %s%s;",
- (if_exists ? "IF EXISTS " : ""), fmtId(dbname));
+ appendPQExpBuffer(&sql, "DROP DATABASE %s%s%s;",
+ (if_exists ? "IF EXISTS " : ""),
+ fmtId(dbname),
+ force ? " WITH (FORCE)" : "");
/* Avoid trying to drop postgres db while we are connected to it. */
if (maintenance_db == NULL && strcmp(dbname, "postgres") == 0)
@@ -159,6 +166,7 @@ help(const char *progname)
printf(_("\nOptions:\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -i, --interactive prompt before deleting anything\n"));
+ printf(_(" -f, --force try to terminate other connections before dropping\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" --if-exists don't report error if database doesn't exist\n"));
printf(_(" -?, --help show this help, then exit\n"));
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index 25aa54a..c51babe 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 13;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,5 +19,11 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
+$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar2' ],
+ qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
--
1.8.3.1
po 18. 11. 2019 v 6:24 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Nov 18, 2019 at 10:33 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 18. 11. 2019 v 4:43 odesílatel vignesh C <vignesh21@gmail.com>
napsal:
When we don't specify -e option, the query used to drop db will not be
printed like below:
./dropdb testdb1
When we specify -e option, the query used to drop db will be printedlike below:
./dropdb -e testdb2
SELECT pg_catalog.set_config('search_path', '', false);
DROP DATABASE testdb2;
If we specify -e option, the query that is being used to drop db will
be printed. In the existing test I could not see the inclusion of -e
option. I was thinking to add a test including -e that way the query
that includes force option gets validated.still I don't understand. The created query is tested already by current
test.
Do you want to test just -e option?
Yeah, it seems Vignesh wants to do that. It will help in verifying
that the command generated by code is correct. However, I think there
is no pressing need to have an additional test for this.Then it should be done as separate issue.
Yeah, I agree. I think this can be done as a separate test patch to
improve coverage if someone wants.Also should we include one test where one session is connected to db
and another session tries dropping with -f option?I afraid so test API doesn't allow asynchronous operations. Do you
have any idea, how to it?
I had seen that isolation test(src/test/isolation) has a framework to
support this. You can have a look to see if it can be handled using
that.I'll look there
If we want to have a test for this, then you might want to look at
test src/test/recovery/t/013_crash_restart. In that test, we keep a
connection open and then validate whether it is terminated. Having
said that, I think it might be better to add this as a separate test
patch apart from main patch because it is a bit of a timing-dependent
test and might fail on some slow machines. We can always revert this
if it turns out to be an unstable test.
+1
I have slightly modified the main patch and attached is the result.
Basically, I don't see any need to repeat what is mentioned in the
Drop Database page. Let me know what you guys think?
+ 1 from me - all has sense
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Nov 18, 2019 at 10:59 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 18. 11. 2019 v 6:24 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
On Mon, Nov 18, 2019 at 10:33 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 18. 11. 2019 v 4:43 odesílatel vignesh C <vignesh21@gmail.com> napsal:
I had seen that isolation test(src/test/isolation) has a framework to
support this. You can have a look to see if it can be handled using
that.I'll look there
If we want to have a test for this, then you might want to look at
test src/test/recovery/t/013_crash_restart. In that test, we keep a
connection open and then validate whether it is terminated. Having
said that, I think it might be better to add this as a separate test
patch apart from main patch because it is a bit of a timing-dependent
test and might fail on some slow machines. We can always revert this
if it turns out to be an unstable test.+1
So, are you planning to give it a try? I think even if we want to
commit this separately, it is better to have a test for this before we
commit.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
po 18. 11. 2019 v 7:37 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Nov 18, 2019 at 10:59 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 18. 11. 2019 v 6:24 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Nov 18, 2019 at 10:33 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
po 18. 11. 2019 v 4:43 odesílatel vignesh C <vignesh21@gmail.com>
napsal:
I had seen that isolation test(src/test/isolation) has a framework to
support this. You can have a look to see if it can be handled using
that.I'll look there
If we want to have a test for this, then you might want to look at
test src/test/recovery/t/013_crash_restart. In that test, we keep a
connection open and then validate whether it is terminated. Having
said that, I think it might be better to add this as a separate test
patch apart from main patch because it is a bit of a timing-dependent
test and might fail on some slow machines. We can always revert this
if it turns out to be an unstable test.+1
So, are you planning to give it a try? I think even if we want to
commit this separately, it is better to have a test for this before we
commit.
I'll send this test today
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
po 18. 11. 2019 v 7:39 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
po 18. 11. 2019 v 7:37 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:On Mon, Nov 18, 2019 at 10:59 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 18. 11. 2019 v 6:24 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Nov 18, 2019 at 10:33 AM Pavel Stehule <
pavel.stehule@gmail.com> wrote:
po 18. 11. 2019 v 4:43 odesílatel vignesh C <vignesh21@gmail.com>
napsal:
I had seen that isolation test(src/test/isolation) has a framework
to
support this. You can have a look to see if it can be handled using
that.I'll look there
If we want to have a test for this, then you might want to look at
test src/test/recovery/t/013_crash_restart. In that test, we keep a
connection open and then validate whether it is terminated. Having
said that, I think it might be better to add this as a separate test
patch apart from main patch because it is a bit of a timing-dependent
test and might fail on some slow machines. We can always revert this
if it turns out to be an unstable test.+1
So, are you planning to give it a try? I think even if we want to
commit this separately, it is better to have a test for this before we
commit.I'll send this test today
here is it
Regards
Pavel
Show quoted text
Pavel
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
Attachments:
dropdb-force2.patchtext/x-patch; charset=US-ASCII; name=dropdb-force2.patchDownload
diff --git a/src/bin/scripts/t/060_dropdb_force.pl b/src/bin/scripts/t/060_dropdb_force.pl
new file mode 100644
index 0000000000..b895a7fbba
--- /dev/null
+++ b/src/bin/scripts/t/060_dropdb_force.pl
@@ -0,0 +1,149 @@
+#
+# Tests killing session connected to dropped database
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+#Ensure killme process is active
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# and it is connected to foobar1 database
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+$node->safe_psql('postgres', 'DROP DATABASE foobar1 WITH (FORCE)');
+
+# Check that psql sees the killed backend as having been terminated
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( pump_until(
+ $killme,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# and there is not a database with this name
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+# Create database that will be dropped
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# restart psql processes, now that the crash cycle finished
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
+
+# Acquire pid of new backend
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ "acquired pid for SIGKILL");
+$pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# and it is connected to foobar1 database
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+# Now drop database with dropdb --force command
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar1' ],
+ qr/statement: DROP DATABASE foobar1 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check that psql sees the killed backend as having been terminated
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( pump_until(
+ $killme,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# and there is not a database with this name
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+$node->stop();
+
+# Pump until string is matched, or timeout occurs
+sub pump_until
+{
+ my ($proc, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($psql_timeout->is_expired)
+ {
+ diag("aborting wait: program timed out");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("aborting wait: program died");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+
+}
On Mon, Nov 18, 2019 at 10:59 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
po 18. 11. 2019 v 6:24 odesílatel Amit Kapila <amit.kapila16@gmail.com> napsal:
I have slightly modified the main patch and attached is the result.
Basically, I don't see any need to repeat what is mentioned in the
Drop Database page. Let me know what you guys think?+ 1 from me - all has sense
I have pushed this patch. The only remaining patch left now is a test
case patch.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
st 20. 11. 2019 v 12:40 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
On Mon, Nov 18, 2019 at 10:59 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:po 18. 11. 2019 v 6:24 odesílatel Amit Kapila <amit.kapila16@gmail.com>
napsal:
I have slightly modified the main patch and attached is the result.
Basically, I don't see any need to repeat what is mentioned in the
Drop Database page. Let me know what you guys think?+ 1 from me - all has sense
I have pushed this patch. The only remaining patch left now is a test
case patch.
Thank you
Pavel
Show quoted text
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Mon, Nov 18, 2019 at 6:30 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
I'll send this test today
here is it
Thanks for adding the test.
Few comments:
This function is same as in test/recovery/t/013_crash_restart.pl, we
can add to a common file and use in both places:
+# Pump until string is matched, or timeout occurs
+sub pump_until
+{
+ my ($proc, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($psql_timeout->is_expired)
+ {
This can be Tests drop database with force option:
+#
+# Tests killing session connected to dropped database
+#
This can be changed to check database foobar1 does not exist.
+# and there is not a database with this name
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM
pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
This can be changed to check the connections on foobar1 database
+
+# and it is connected to foobar1 database
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity
WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
This can be changed to restart psql as the previous connection will be
terminated by previous drop database.
+
+# restart psql processes, now that the crash cycle finished
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
čt 21. 11. 2019 v 6:33 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Mon, Nov 18, 2019 at 6:30 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:I'll send this test today
here is it
Thanks for adding the test. Few comments: This function is same as in test/recovery/t/013_crash_restart.pl, we can add to a common file and use in both places: +# Pump until string is matched, or timeout occurs +sub pump_until +{ + my ($proc, $stream, $untl) = @_; + $proc->pump_nb(); + while (1) + { + last if $$stream =~ /$untl/; + if ($psql_timeout->is_expired) + {
yes, I know - probably it can be moved to testlib.pm. Unfortunately it is
perl, and I am not able to this correctly. More it use global object - timer
This can be Tests drop database with force option:
done
+#
+# Tests killing session connected to dropped database +#This can be changed to check database foobar1 does not exist.
done
+# and there is not a database with this name
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]), + 'f', 'database foobar1 was removed');This can be changed to check the connections on foobar1 database + +# and it is connected to foobar1 database +is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]), + $pid, 'database foobar1 is used');
done
This can be changed to restart psql as the previous connection will be
terminated by previous drop database.
+
done
new patch attached
Regards
Pavel
+# restart psql processes, now that the crash cycle finished
Show quoted text
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', ''); +$killme->run();Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
dropdb-force.pl-patchapplication/octet-stream; name=dropdb-force.pl-patchDownload
diff --git a/src/bin/scripts/t/060_dropdb_force.pl b/src/bin/scripts/t/060_dropdb_force.pl
new file mode 100644
index 0000000000..f73c7d31f4
--- /dev/null
+++ b/src/bin/scripts/t/060_dropdb_force.pl
@@ -0,0 +1,150 @@
+#
+# Tests drop database with force option.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Ensure killme process is active.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+$node->safe_psql('postgres', 'DROP DATABASE foobar1 WITH (FORCE)');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( pump_until(
+ $killme,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Restart psql as the previous connection will be
+# terminated by previous drop database.
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
+
+# Acquire pid of new backend.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ "acquired pid for SIGKILL");
+$pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+# Now drop database with dropdb --force command.
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar1' ],
+ qr/statement: DROP DATABASE foobar1 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( pump_until(
+ $killme,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+$node->stop();
+
+# Pump until string is matched, or timeout occurs
+sub pump_until
+{
+ my ($proc, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($psql_timeout->is_expired)
+ {
+ diag("aborting wait: program timed out");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("aborting wait: program died");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+
+}
On Fri, Nov 22, 2019 at 12:10 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:
čt 21. 11. 2019 v 6:33 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Mon, Nov 18, 2019 at 6:30 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
I'll send this test today
here is it
Thanks for adding the test. Few comments: This function is same as in test/recovery/t/013_crash_restart.pl, we can add to a common file and use in both places: +# Pump until string is matched, or timeout occurs +sub pump_until +{ + my ($proc, $stream, $untl) = @_; + $proc->pump_nb(); + while (1) + { + last if $$stream =~ /$untl/; + if ($psql_timeout->is_expired) + {yes, I know - probably it can be moved to testlib.pm. Unfortunately it is perl, and I am not able to this correctly. More it use global object - timer
This can be Tests drop database with force option:
done
+# +# Tests killing session connected to dropped database +#This can be changed to check database foobar1 does not exist.
done
+# and there is not a database with this name +is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]), + 'f', 'database foobar1 was removed');This can be changed to check the connections on foobar1 database + +# and it is connected to foobar1 database +is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]), + $pid, 'database foobar1 is used');done
This can be changed to restart psql as the previous connection will be
terminated by previous drop database.
+done
new patch attached
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0001-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchtext/x-patch; charset=US-ASCII; name=0001-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchDownload
From dc8fc7432fab576845ad7f89e221c544926496f1 Mon Sep 17 00:00:00 2001
From: Pavel Stehule <pavel.stehule@gmail.com>
Date: Fri, 22 Nov 2019 14:38:23 +0530
Subject: [PATCH 1/2] Add tests for the support of '-f' option in dropdb
utility.
Add tests for the support of '-f' option in dropdb utility and drop database
SQL.
---
src/bin/scripts/t/060_dropdb_force.pl | 122 ++++++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 src/bin/scripts/t/060_dropdb_force.pl
diff --git a/src/bin/scripts/t/060_dropdb_force.pl b/src/bin/scripts/t/060_dropdb_force.pl
new file mode 100644
index 0000000..446dc09
--- /dev/null
+++ b/src/bin/scripts/t/060_dropdb_force.pl
@@ -0,0 +1,122 @@
+#
+# Tests drop database with force option.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Ensure killme process is active.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m), 'acquired pid');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+$node->safe_psql('postgres', 'DROP DATABASE foobar1 WITH (FORCE)');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Restart psql as the previous connection will be
+# terminated by previous drop database.
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
+
+# Acquire pid of new backend.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m), "acquired pid for SIGKILL");
+$pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+# Now drop database with dropdb --force command.
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar1' ],
+ qr/statement: DROP DATABASE foobar1 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+$node->stop();
--
1.8.3.1
0002-Made-pump_until-usable-as-a-common-subroutine.patchtext/x-patch; charset=US-ASCII; name=0002-Made-pump_until-usable-as-a-common-subroutine.patchDownload
From 0810c36f444b517c40d338a42ab3946590a040cd Mon Sep 17 00:00:00 2001
From: vignesh <vignesh@localhost.localdomain>
Date: Fri, 22 Nov 2019 14:42:13 +0530
Subject: [PATCH 2/2] Made pump_until usable as a common subroutine.
Patch includes movement of pump_until subroutine from 013_crash_restart to
TestLib so that it can be used as a common sub from 013_crash_restart and
060_dropdb_force.
---
src/test/perl/TestLib.pm | 37 ++++++++++++++++++
src/test/recovery/t/013_crash_restart.pl | 66 ++++++++++----------------------
2 files changed, 57 insertions(+), 46 deletions(-)
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 905d0d1..ed16c6f 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -841,6 +841,43 @@ sub command_checks_all
=pod
+=item pump_until(proc, psql_timeout, stream, untl)
+
+# Pump until string is matched, or timeout occurs
+
+=cut
+
+sub pump_until
+{
+ my ($proc, $psql_timeout, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($psql_timeout->is_expired)
+ {
+ diag("aborting wait: program timed out");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("aborting wait: program died");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+
+}
+
+=pod
+
=back
=cut
diff --git a/src/test/recovery/t/013_crash_restart.pl b/src/test/recovery/t/013_crash_restart.pl
index 2c47797..c93d465 100644
--- a/src/test/recovery/t/013_crash_restart.pl
+++ b/src/test/recovery/t/013_crash_restart.pl
@@ -72,8 +72,8 @@ CREATE TABLE alive(status text);
INSERT INTO alive VALUES($$committed-before-sigquit$$);
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
- 'acquired pid for SIGQUIT');
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m), 'acquired pid for SIGQUIT');
my $pid = $killme_stdout;
chomp($pid);
$killme_stdout = '';
@@ -84,8 +84,8 @@ $killme_stdin .= q[
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigquit$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigquit/m),
- 'inserted in-progress-before-sigquit');
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/in-progress-before-sigquit/m), 'inserted in-progress-before-sigquit');
$killme_stdout = '';
$killme_stderr = '';
@@ -97,8 +97,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
- 'monitor connected');
+ok(TestLib::pump_until($monitor, $psql_timeout, \$monitor_stdout,
+ qr/psql-connected/m), 'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -112,8 +112,9 @@ is($ret, 0, "killed process with SIGQUIT");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -125,8 +126,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -153,8 +155,8 @@ $monitor->run();
$killme_stdin .= q[
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
- "acquired pid for SIGKILL");
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m), "acquired pid for SIGKILL");
$pid = $killme_stdout;
chomp($pid);
$killme_stdout = '';
@@ -166,8 +168,8 @@ INSERT INTO alive VALUES($$committed-before-sigkill$$) RETURNING status;
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigkill$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigkill/m),
- 'inserted in-progress-before-sigkill');
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/in-progress-before-sigkill/m), 'inserted in-progress-before-sigkill');
$killme_stdout = '';
$killme_stderr = '';
@@ -178,8 +180,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
- 'monitor connected');
+ok(TestLib::pump_until($monitor, $psql_timeout, \$monitor_stdout,
+ qr/psql-connected/m), 'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -194,8 +196,9 @@ is($ret, 0, "killed process with KILL");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/server closed the connection unexpectedly|connection to server was lost/m
),
@@ -205,8 +208,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -244,33 +248,3 @@ is( $node->safe_psql(
'can still write after orderly restart');
$node->stop();
-
-# Pump until string is matched, or timeout occurs
-sub pump_until
-{
- my ($proc, $stream, $untl) = @_;
- $proc->pump_nb();
- while (1)
- {
- last if $$stream =~ /$untl/;
- if ($psql_timeout->is_expired)
- {
- diag("aborting wait: program timed out");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- if (not $proc->pumpable())
- {
- diag("aborting wait: program died");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- $proc->pump();
- }
- return 1;
-
-}
--
1.8.3.1
pá 22. 11. 2019 v 10:46 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Fri, Nov 22, 2019 at 12:10 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:čt 21. 11. 2019 v 6:33 odesílatel vignesh C <vignesh21@gmail.com>
napsal:
On Mon, Nov 18, 2019 at 6:30 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:
I'll send this test today
here is it
Thanks for adding the test. Few comments: This function is same as in test/recovery/t/013_crash_restart.pl, we can add to a common file and use in both places: +# Pump until string is matched, or timeout occurs +sub pump_until +{ + my ($proc, $stream, $untl) = @_; + $proc->pump_nb(); + while (1) + { + last if $$stream =~ /$untl/; + if ($psql_timeout->is_expired) + {yes, I know - probably it can be moved to testlib.pm. Unfortunately it
is perl, and I am not able to this correctly. More it use global object -
timerThis can be Tests drop database with force option:
done
+# +# Tests killing session connected to dropped database +#This can be changed to check database foobar1 does not exist.
done
+# and there is not a database with this name +is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]), + 'f', 'database foobar1 was removed');This can be changed to check the connections on foobar1 database + +# and it is connected to foobar1 database +is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]), + $pid, 'database foobar1 is used');done
This can be changed to restart psql as the previous connection will be
terminated by previous drop database.
+done
new patch attached
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.
thank you
looks well
Pavel
Show quoted text
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Fri, Nov 22, 2019 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.
Few comments:
--------------------------
1. There is a lot of duplicate code in this test. Basically, almost
the same code is used once to test Drop Database command and dropdb.
I think we can create a few sub-functions to avoid duplicate code, but
do we really need to test the same thing once via command and then by
dropdb utility? I don't think it is required, but even if we want to
do so, then I am not sure if this is the right test script. I suggest
removing the command related test.
2.
ok( TestLib::pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
Extra space before TestLib.
3.
+=item pump_until(proc, psql_timeout, stream, untl)
I think moving pump_until to TestLib is okay, but if you are making it
a generic function to be used from multiple places, it is better to
name the variable as 'timeout' instead of 'psql_timeout'
4. Have you ran perltidy, if not, can you please run it? See
src/test/perl/README for the recommendation.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Sat, Nov 23, 2019 at 4:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Nov 22, 2019 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.Few comments:
--------------------------
1. There is a lot of duplicate code in this test. Basically, almost
the same code is used once to test Drop Database command and dropdb.
I think we can create a few sub-functions to avoid duplicate code, but
do we really need to test the same thing once via command and then by
dropdb utility? I don't think it is required, but even if we want to
do so, then I am not sure if this is the right test script. I suggest
removing the command related test.
Pavel: What is your opinion on this?
2. ok( TestLib::pump_until( + $killme, + $psql_timeout, + \$killme_stderr, + qr/FATAL: terminating connection due to administrator command/m + ), + "psql query died successfully after SIGTERM");Extra space before TestLib.
Ran perltidy, perltidy adds an extra space. I'm not sure which version
is right whether to include space or without space. I had noticed
similarly in 001_stream_rep.pl, in few places space is present and in
few places it is not present. If required I can update based on
suggestion.
3.
+=item pump_until(proc, psql_timeout, stream, untl)I think moving pump_until to TestLib is okay, but if you are making it
a generic function to be used from multiple places, it is better to
name the variable as 'timeout' instead of 'psql_timeout'
Fixed.
4. Have you ran perltidy, if not, can you please run it? See
src/test/perl/README for the recommendation.
I have verified by running perltidy.
Please find the updated patch attached. 1st patch is same as previous,
2nd patch includes the fix for comments 2,3 & 4.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0001-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchtext/x-patch; charset=US-ASCII; name=0001-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchDownload
From dc8fc7432fab576845ad7f89e221c544926496f1 Mon Sep 17 00:00:00 2001
From: Pavel Stehule <pavel.stehule@gmail.com>
Date: Fri, 22 Nov 2019 14:38:23 +0530
Subject: [PATCH 1/2] Add tests for the support of '-f' option in dropdb
utility.
Add tests for the support of '-f' option in dropdb utility and drop database
SQL.
---
src/bin/scripts/t/060_dropdb_force.pl | 122 ++++++++++++++++++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 src/bin/scripts/t/060_dropdb_force.pl
diff --git a/src/bin/scripts/t/060_dropdb_force.pl b/src/bin/scripts/t/060_dropdb_force.pl
new file mode 100644
index 0000000..446dc09
--- /dev/null
+++ b/src/bin/scripts/t/060_dropdb_force.pl
@@ -0,0 +1,122 @@
+#
+# Tests drop database with force option.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Ensure killme process is active.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m), 'acquired pid');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+$node->safe_psql('postgres', 'DROP DATABASE foobar1 WITH (FORCE)');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Restart psql as the previous connection will be
+# terminated by previous drop database.
+($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+$killme->run();
+
+# Acquire pid of new backend.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok(TestLib::pump_until($killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m), "acquired pid for SIGKILL");
+$pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is($node->safe_psql('postgres', qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]),
+ $pid, 'database foobar1 is used');
+
+# Now drop database with dropdb --force command.
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar1' ],
+ qr/statement: DROP DATABASE foobar1 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme,
+ $psql_timeout,
+ \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m
+ ),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is($node->safe_psql('postgres', qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]),
+ 'f', 'database foobar1 was removed');
+
+$node->stop();
--
1.8.3.1
0002-Made-pump_until-usable-as-a-common-subroutine.patchtext/x-patch; charset=US-ASCII; name=0002-Made-pump_until-usable-as-a-common-subroutine.patchDownload
From 2d6f6c63e5c4f8215e2cd8e9204ece1b388c11cf Mon Sep 17 00:00:00 2001
From: vignesh <vignesh@localhost.localdomain>
Date: Sun, 24 Nov 2019 15:39:03 +0530
Subject: [PATCH] Made pump_until usable as a common subroutine.
Patch includes movement of pump_until subroutine from 013_crash_restart to
TestLib so that it can be used as a common sub from 013_crash_restart and
060_dropdb_force.
---
src/test/perl/TestLib.pm | 37 +++++++++++++++++++
src/test/recovery/t/013_crash_restart.pl | 62 ++++++++++++--------------------
2 files changed, 59 insertions(+), 40 deletions(-)
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 905d0d1..f14e0ea 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -841,6 +841,43 @@ sub command_checks_all
=pod
+=item pump_until(proc, timeout, stream, untl)
+
+# Pump until string is matched, or timeout occurs
+
+=cut
+
+sub pump_until
+{
+ my ($proc, $timeout, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($timeout->is_expired)
+ {
+ diag("aborting wait: program timed out");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("aborting wait: program died");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+
+}
+
+=pod
+
=back
=cut
diff --git a/src/test/recovery/t/013_crash_restart.pl b/src/test/recovery/t/013_crash_restart.pl
index 2c47797..dd08924 100644
--- a/src/test/recovery/t/013_crash_restart.pl
+++ b/src/test/recovery/t/013_crash_restart.pl
@@ -72,7 +72,8 @@ CREATE TABLE alive(status text);
INSERT INTO alive VALUES($$committed-before-sigquit$$);
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
'acquired pid for SIGQUIT');
my $pid = $killme_stdout;
chomp($pid);
@@ -84,7 +85,9 @@ $killme_stdin .= q[
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigquit$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigquit/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout,
+ \$killme_stdout, qr/in-progress-before-sigquit/m),
'inserted in-progress-before-sigquit');
$killme_stdout = '';
$killme_stderr = '';
@@ -97,7 +100,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
+ok( TestLib::pump_until(
+ $monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -112,8 +116,9 @@ is($ret, 0, "killed process with SIGQUIT");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -125,8 +130,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -153,7 +159,8 @@ $monitor->run();
$killme_stdin .= q[
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
"acquired pid for SIGKILL");
$pid = $killme_stdout;
chomp($pid);
@@ -166,7 +173,9 @@ INSERT INTO alive VALUES($$committed-before-sigkill$$) RETURNING status;
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigkill$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigkill/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout,
+ \$killme_stdout, qr/in-progress-before-sigkill/m),
'inserted in-progress-before-sigkill');
$killme_stdout = '';
$killme_stderr = '';
@@ -178,7 +187,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
+ok( TestLib::pump_until(
+ $monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -194,8 +204,9 @@ is($ret, 0, "killed process with KILL");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/server closed the connection unexpectedly|connection to server was lost/m
),
@@ -205,8 +216,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -244,33 +256,3 @@ is( $node->safe_psql(
'can still write after orderly restart');
$node->stop();
-
-# Pump until string is matched, or timeout occurs
-sub pump_until
-{
- my ($proc, $stream, $untl) = @_;
- $proc->pump_nb();
- while (1)
- {
- last if $$stream =~ /$untl/;
- if ($psql_timeout->is_expired)
- {
- diag("aborting wait: program timed out");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- if (not $proc->pumpable())
- {
- diag("aborting wait: program died");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- $proc->pump();
- }
- return 1;
-
-}
--
1.8.3.1
ne 24. 11. 2019 v 11:25 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Sat, Nov 23, 2019 at 4:42 PM Amit Kapila <amit.kapila16@gmail.com>
wrote:On Fri, Nov 22, 2019 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.Few comments:
--------------------------
1. There is a lot of duplicate code in this test. Basically, almost
the same code is used once to test Drop Database command and dropdb.
I think we can create a few sub-functions to avoid duplicate code, but
do we really need to test the same thing once via command and then by
dropdb utility? I don't think it is required, but even if we want to
do so, then I am not sure if this is the right test script. I suggest
removing the command related test.Pavel: What is your opinion on this?
I have not any problem with this reduction.
We will see in future years what is benefit of this test.
Show quoted text
2. ok( TestLib::pump_until( + $killme, + $psql_timeout, + \$killme_stderr, + qr/FATAL: terminating connection due to administrator command/m + ), + "psql query died successfully after SIGTERM");Extra space before TestLib.
Ran perltidy, perltidy adds an extra space. I'm not sure which version
is right whether to include space or without space. I had noticed
similarly in 001_stream_rep.pl, in few places space is present and in
few places it is not present. If required I can update based on
suggestion.3.
+=item pump_until(proc, psql_timeout, stream, untl)I think moving pump_until to TestLib is okay, but if you are making it
a generic function to be used from multiple places, it is better to
name the variable as 'timeout' instead of 'psql_timeout'Fixed.
4. Have you ran perltidy, if not, can you please run it? See
src/test/perl/README for the recommendation.I have verified by running perltidy.
Please find the updated patch attached. 1st patch is same as previous,
2nd patch includes the fix for comments 2,3 & 4.Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
On Sun, Nov 24, 2019 at 3:55 PM vignesh C <vignesh21@gmail.com> wrote:
On Sat, Nov 23, 2019 at 4:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
2. ok( TestLib::pump_until( + $killme, + $psql_timeout, + \$killme_stderr, + qr/FATAL: terminating connection due to administrator command/m + ), + "psql query died successfully after SIGTERM");Extra space before TestLib.
Ran perltidy, perltidy adds an extra space. I'm not sure which version
is right whether to include space or without space. I had noticed
similarly in 001_stream_rep.pl, in few places space is present and in
few places it is not present. If required I can update based on
suggestion.
You can try by running perltidy on other existing .pl files where you
find the usage "without space" and see if it adds the extra space in
all places. I think keeping the version after running perltidy would
be a better choice.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Sun, Nov 24, 2019 at 5:06 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
ne 24. 11. 2019 v 11:25 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Sat, Nov 23, 2019 at 4:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Nov 22, 2019 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.Few comments:
--------------------------
1. There is a lot of duplicate code in this test. Basically, almost
the same code is used once to test Drop Database command and dropdb.
I think we can create a few sub-functions to avoid duplicate code, but
do we really need to test the same thing once via command and then by
dropdb utility? I don't think it is required, but even if we want to
do so, then I am not sure if this is the right test script. I suggest
removing the command related test.Pavel: What is your opinion on this?
I have not any problem with this reduction.
We will see in future years what is benefit of this test.
Fixed, removed dropdb utility test.
2. ok( TestLib::pump_until( + $killme, + $psql_timeout, + \$killme_stderr, + qr/FATAL: terminating connection due to administrator command/m + ), + "psql query died successfully after SIGTERM");Extra space before TestLib.
Ran perltidy, perltidy adds an extra space. I'm not sure which version
is right whether to include space or without space. I had noticed
similarly in 001_stream_rep.pl, in few places space is present and in
few places it is not present. If required I can update based on
suggestion.3.
+=item pump_until(proc, psql_timeout, stream, untl)I think moving pump_until to TestLib is okay, but if you are making it
a generic function to be used from multiple places, it is better to
name the variable as 'timeout' instead of 'psql_timeout'Fixed.
4. Have you ran perltidy, if not, can you please run it? See
src/test/perl/README for the recommendation.I have verified by running perltidy.
Please find the updated patch attached. 1st patch is same as previous,
2nd patch includes the fix for comments 2,3 & 4.
Attached patch handles the fix for the above comments.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0002-Made-pump_until-usable-as-a-common-subroutine.patchtext/x-patch; charset=US-ASCII; name=0002-Made-pump_until-usable-as-a-common-subroutine.patchDownload
From 0890c781b67595a51b04a1dfe972890fb6be18a4 Mon Sep 17 00:00:00 2001
From: vignesh <vignesh@localhost.localdomain>
Date: Mon, 25 Nov 2019 23:12:19 +0530
Subject: [PATCH 2/2] Made pump_until usable as a common subroutine.
Patch includes movement of pump_until subroutine from 013_crash_restart to
TestLib so that it can be used as a common sub from 013_crash_restart and
060_dropdb_force.
---
src/test/perl/TestLib.pm | 37 +++++++++++++++++++
src/test/recovery/t/013_crash_restart.pl | 62 ++++++++++++--------------------
2 files changed, 59 insertions(+), 40 deletions(-)
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index a377cdb..b58679a 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -860,6 +860,43 @@ sub command_checks_all
=pod
+=item pump_until(proc, timeout, stream, untl)
+
+# Pump until string is matched, or timeout occurs
+
+=cut
+
+sub pump_until
+{
+ my ($proc, $timeout, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($timeout->is_expired)
+ {
+ diag("aborting wait: program timed out");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("aborting wait: program died");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+
+}
+
+=pod
+
=back
=cut
diff --git a/src/test/recovery/t/013_crash_restart.pl b/src/test/recovery/t/013_crash_restart.pl
index 2c47797..dd08924 100644
--- a/src/test/recovery/t/013_crash_restart.pl
+++ b/src/test/recovery/t/013_crash_restart.pl
@@ -72,7 +72,8 @@ CREATE TABLE alive(status text);
INSERT INTO alive VALUES($$committed-before-sigquit$$);
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
'acquired pid for SIGQUIT');
my $pid = $killme_stdout;
chomp($pid);
@@ -84,7 +85,9 @@ $killme_stdin .= q[
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigquit$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigquit/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout,
+ \$killme_stdout, qr/in-progress-before-sigquit/m),
'inserted in-progress-before-sigquit');
$killme_stdout = '';
$killme_stderr = '';
@@ -97,7 +100,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
+ok( TestLib::pump_until(
+ $monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -112,8 +116,9 @@ is($ret, 0, "killed process with SIGQUIT");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -125,8 +130,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -153,7 +159,8 @@ $monitor->run();
$killme_stdin .= q[
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
"acquired pid for SIGKILL");
$pid = $killme_stdout;
chomp($pid);
@@ -166,7 +173,9 @@ INSERT INTO alive VALUES($$committed-before-sigkill$$) RETURNING status;
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigkill$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigkill/m),
+ok( TestLib::pump_until(
+ $killme, $psql_timeout,
+ \$killme_stdout, qr/in-progress-before-sigkill/m),
'inserted in-progress-before-sigkill');
$killme_stdout = '';
$killme_stderr = '';
@@ -178,7 +187,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
+ok( TestLib::pump_until(
+ $monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -194,8 +204,9 @@ is($ret, 0, "killed process with KILL");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/server closed the connection unexpectedly|connection to server was lost/m
),
@@ -205,8 +216,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -244,33 +256,3 @@ is( $node->safe_psql(
'can still write after orderly restart');
$node->stop();
-
-# Pump until string is matched, or timeout occurs
-sub pump_until
-{
- my ($proc, $stream, $untl) = @_;
- $proc->pump_nb();
- while (1)
- {
- last if $$stream =~ /$untl/;
- if ($psql_timeout->is_expired)
- {
- diag("aborting wait: program timed out");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- if (not $proc->pumpable())
- {
- diag("aborting wait: program died");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- $proc->pump();
- }
- return 1;
-
-}
--
1.8.3.1
0001-Add-tests-for-the-support-of-drop-database-forcefull.patchtext/x-patch; charset=US-ASCII; name=0001-Add-tests-for-the-support-of-drop-database-forcefull.patchDownload
From 52df8fecff449450d5100aa632fbdb87e11654ee Mon Sep 17 00:00:00 2001
From: Pavel Stehule <pavel.stehule@gmail.com>
Date: Mon, 25 Nov 2019 23:08:16 +0530
Subject: [PATCH 1/2] Add tests for the support of drop database forcefully.
Add tests for the support of drop database forcefully.
---
src/bin/scripts/t/060_dropdb_force.pl | 80 +++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
create mode 100644 src/bin/scripts/t/060_dropdb_force.pl
diff --git a/src/bin/scripts/t/060_dropdb_force.pl b/src/bin/scripts/t/060_dropdb_force.pl
new file mode 100644
index 0000000..c3357f6
--- /dev/null
+++ b/src/bin/scripts/t/060_dropdb_force.pl
@@ -0,0 +1,80 @@
+#
+# Tests drop database with force option.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Ensure killme process is active.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]
+ ),
+ $pid,
+ 'database foobar1 is used');
+
+$node->safe_psql('postgres', 'DROP DATABASE foobar1 WITH (FORCE)');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]
+ ),
+ 'f',
+ 'database foobar1 was removed');
+
+$node->stop();
--
1.8.3.1
On Mon, Nov 25, 2019 at 11:22 PM vignesh C <vignesh21@gmail.com> wrote:
On Sun, Nov 24, 2019 at 5:06 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
ne 24. 11. 2019 v 11:25 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Sat, Nov 23, 2019 at 4:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Nov 22, 2019 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.Few comments:
--------------------------
1. There is a lot of duplicate code in this test. Basically, almost
the same code is used once to test Drop Database command and dropdb.
I think we can create a few sub-functions to avoid duplicate code, but
do we really need to test the same thing once via command and then by
dropdb utility? I don't think it is required, but even if we want to
do so, then I am not sure if this is the right test script. I suggest
removing the command related test.Pavel: What is your opinion on this?
I have not any problem with this reduction.
We will see in future years what is benefit of this test.
Fixed, removed dropdb utility test.
Hmm, you have done the opposite of what I have asked. This test file
is in rc/bin/scripts/t/ where we generally keep the tests for
utilities. So, retaining the dropdb utility test in that file seems
more sensible.
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid');
How about changing 'acquired pid' to 'acquired pid for SIGTERM'?
I have verified by running perltidy.
I think we don't need to run perltidy on the existing code. So, I am
not sure if it is a good idea to run it for file 013_crash_restart.pl
as it changes some existing code formatting.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Tue, Nov 26, 2019 at 11:37 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Mon, Nov 25, 2019 at 11:22 PM vignesh C <vignesh21@gmail.com> wrote:
On Sun, Nov 24, 2019 at 5:06 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:
ne 24. 11. 2019 v 11:25 odesílatel vignesh C <vignesh21@gmail.com> napsal:
On Sat, Nov 23, 2019 at 4:42 PM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Fri, Nov 22, 2019 at 3:16 PM vignesh C <vignesh21@gmail.com> wrote:
Thanks for fixing the comments. The changes looks fine to me. I have
fixed the first comment, attached patch has the changes for the same.Few comments:
--------------------------
1. There is a lot of duplicate code in this test. Basically, almost
the same code is used once to test Drop Database command and dropdb.
I think we can create a few sub-functions to avoid duplicate code, but
do we really need to test the same thing once via command and then by
dropdb utility? I don't think it is required, but even if we want to
do so, then I am not sure if this is the right test script. I suggest
removing the command related test.Pavel: What is your opinion on this?
I have not any problem with this reduction.
We will see in future years what is benefit of this test.
Fixed, removed dropdb utility test.
Hmm, you have done the opposite of what I have asked. This test file
is in rc/bin/scripts/t/ where we generally keep the tests for
utilities. So, retaining the dropdb utility test in that file seems
more sensible.
Fixed. Retained the test of dropdb utility and removed drop database
sql command test.
+ok( TestLib::pump_until( + $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m), + 'acquired pid');How about changing 'acquired pid' to 'acquired pid for SIGTERM'?
Fixed. Changed as suggested.
I have verified by running perltidy.
I think we don't need to run perltidy on the existing code. So, I am
not sure if it is a good idea to run it for file 013_crash_restart.pl
as it changes some existing code formatting.
I have retained the format same as old format, one additional change I
added was to break the line if the line is lengthy in the modified
code.
Attached patch has the fixes for the above comments.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0002-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchtext/x-patch; charset=US-ASCII; name=0002-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchDownload
From ef60824cd15b3c4cab1fc7fb26f79e5243d8cb7b Mon Sep 17 00:00:00 2001
From: Pavel Stehule <pavel.stehule@gmail.com>
Date: Wed, 27 Nov 2019 09:18:26 +0530
Subject: [PATCH 2/2] Add tests for the support of '-f' option in dropdb
utility.
Add tests for the support of '-f' option in dropdb utility.
---
src/bin/scripts/t/060_dropdb_force.pl | 84 +++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
create mode 100644 src/bin/scripts/t/060_dropdb_force.pl
diff --git a/src/bin/scripts/t/060_dropdb_force.pl b/src/bin/scripts/t/060_dropdb_force.pl
new file mode 100644
index 0000000..0a1f612
--- /dev/null
+++ b/src/bin/scripts/t/060_dropdb_force.pl
@@ -0,0 +1,84 @@
+#
+# Tests drop database with force option.
+#
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 6;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Ensure killme process is active.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid for SIGTERM');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]
+ ),
+ $pid,
+ 'database foobar1 is used');
+
+# Now drop database with dropdb --force command.
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar1' ],
+ qr/statement: DROP DATABASE foobar1 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]
+ ),
+ 'f',
+ 'database foobar1 was removed');
+
+$node->stop();
--
1.8.3.1
0001-Made-pump_until-usable-as-a-common-subroutine.patchtext/x-patch; charset=US-ASCII; name=0001-Made-pump_until-usable-as-a-common-subroutine.patchDownload
From 6bfb40e665de267bac73808c9610a6722aef09ac Mon Sep 17 00:00:00 2001
From: vignesh <vignesh@localhost.localdomain>
Date: Wed, 27 Nov 2019 09:15:05 +0530
Subject: [PATCH 1/2] Made pump_until usable as a common subroutine.
Patch includes movement of pump_until subroutine from 013_crash_restart to
TestLib so that it can be used as a common sub from 013_crash_restart and
060_dropdb_force.
---
src/test/perl/TestLib.pm | 37 +++++++++++++++++++
src/test/recovery/t/013_crash_restart.pl | 63 ++++++++++++--------------------
2 files changed, 60 insertions(+), 40 deletions(-)
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 18b4ce3..e235550 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -877,6 +877,43 @@ sub command_checks_all
=pod
+=item pump_until(proc, timeout, stream, untl)
+
+# Pump until string is matched, or timeout occurs
+
+=cut
+
+sub pump_until
+{
+ my ($proc, $timeout, $stream, $untl) = @_;
+ $proc->pump_nb();
+ while (1)
+ {
+ last if $$stream =~ /$untl/;
+ if ($timeout->is_expired)
+ {
+ diag("aborting wait: program timed out");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ if (not $proc->pumpable())
+ {
+ diag("aborting wait: program died");
+ diag("stream contents: >>", $$stream, "<<");
+ diag("pattern searched for: ", $untl);
+
+ return 0;
+ }
+ $proc->pump();
+ }
+ return 1;
+
+}
+
+=pod
+
=back
=cut
diff --git a/src/test/recovery/t/013_crash_restart.pl b/src/test/recovery/t/013_crash_restart.pl
index 2c47797..5957619 100644
--- a/src/test/recovery/t/013_crash_restart.pl
+++ b/src/test/recovery/t/013_crash_restart.pl
@@ -72,7 +72,9 @@ CREATE TABLE alive(status text);
INSERT INTO alive VALUES($$committed-before-sigquit$$);
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ok(TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout,
+ qr/[[:digit:]]+[\r\n]$/m),
'acquired pid for SIGQUIT');
my $pid = $killme_stdout;
chomp($pid);
@@ -84,7 +86,9 @@ $killme_stdin .= q[
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigquit$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigquit/m),
+ok(TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout,
+ qr/in-progress-before-sigquit/m),
'inserted in-progress-before-sigquit');
$killme_stdout = '';
$killme_stderr = '';
@@ -97,7 +101,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
+ok(TestLib::pump_until(
+ $monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -112,8 +117,9 @@ is($ret, 0, "killed process with SIGQUIT");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -125,8 +131,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -153,7 +160,8 @@ $monitor->run();
$killme_stdin .= q[
SELECT pg_backend_pid();
];
-ok(pump_until($killme, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ok(TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
"acquired pid for SIGKILL");
$pid = $killme_stdout;
chomp($pid);
@@ -166,7 +174,9 @@ INSERT INTO alive VALUES($$committed-before-sigkill$$) RETURNING status;
BEGIN;
INSERT INTO alive VALUES($$in-progress-before-sigkill$$) RETURNING status;
];
-ok(pump_until($killme, \$killme_stdout, qr/in-progress-before-sigkill/m),
+ok(TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout,
+ qr/in-progress-before-sigkill/m),
'inserted in-progress-before-sigkill');
$killme_stdout = '';
$killme_stderr = '';
@@ -178,7 +188,8 @@ $monitor_stdin .= q[
SELECT $$psql-connected$$;
SELECT pg_sleep(3600);
];
-ok(pump_until($monitor, \$monitor_stdout, qr/psql-connected/m),
+ok(TestLib::pump_until(
+ $monitor, $psql_timeout, \$monitor_stdout, qr/psql-connected/m),
'monitor connected');
$monitor_stdout = '';
$monitor_stderr = '';
@@ -194,8 +205,9 @@ is($ret, 0, "killed process with KILL");
$killme_stdin .= q[
SELECT 1;
];
-ok( pump_until(
+ok( TestLib::pump_until(
$killme,
+ $psql_timeout,
\$killme_stderr,
qr/server closed the connection unexpectedly|connection to server was lost/m
),
@@ -205,8 +217,9 @@ $killme->finish;
# Wait till server restarts - we should get the WARNING here, but
# sometimes the server is unable to send that, if interrupted while
# sending.
-ok( pump_until(
+ok( TestLib::pump_until(
$monitor,
+ $psql_timeout,
\$monitor_stderr,
qr/WARNING: terminating connection because of crash of another server process|server closed the connection unexpectedly|connection to server was lost/m
),
@@ -244,33 +257,3 @@ is( $node->safe_psql(
'can still write after orderly restart');
$node->stop();
-
-# Pump until string is matched, or timeout occurs
-sub pump_until
-{
- my ($proc, $stream, $untl) = @_;
- $proc->pump_nb();
- while (1)
- {
- last if $$stream =~ /$untl/;
- if ($psql_timeout->is_expired)
- {
- diag("aborting wait: program timed out");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- if (not $proc->pumpable())
- {
- diag("aborting wait: program died");
- diag("stream contents: >>", $$stream, "<<");
- diag("pattern searched for: ", $untl);
-
- return 0;
- }
- $proc->pump();
- }
- return 1;
-
-}
--
1.8.3.1
On Wed, Nov 27, 2019 at 10:15 AM vignesh C <vignesh21@gmail.com> wrote:
Attached patch has the fixes for the above comments.
I have pushed the refactoring patch. In the second patch, I have a
few more comments. I am not completely sure if it is a good idea to
write a new test file 060_dropdb_force.pl when we already have an
existing file 050_dropdb.pl for dropdb tests, but I think if we want
to do that, then lets move existing test for dropdb '-f' from
050_dropdb.pl to new file and it might be better to name new file as
051_dropdb_force.pl. I see that in some other cases like vacuumdb and
clusterdb, we have separate test files to cover a different kinds of
scenarios, so it should be okay to have a new file for dropdb tests.
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Thu, Nov 28, 2019 at 8:54 AM Amit Kapila <amit.kapila16@gmail.com> wrote:
On Wed, Nov 27, 2019 at 10:15 AM vignesh C <vignesh21@gmail.com> wrote:
Attached patch has the fixes for the above comments.
I have pushed the refactoring patch. In the second patch, I have a
few more comments. I am not completely sure if it is a good idea to
write a new test file 060_dropdb_force.pl when we already have an
existing file 050_dropdb.pl for dropdb tests, but I think if we want
to do that, then lets move existing test for dropdb '-f' from
050_dropdb.pl to new file and it might be better to name new file as
051_dropdb_force.pl. I see that in some other cases like vacuumdb and
clusterdb, we have separate test files to cover a different kinds of
scenarios, so it should be okay to have a new file for dropdb tests.
Thanks for pushing the patch. Please find the attached patch having
the fixes for the comments suggested.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0001-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchtext/x-patch; charset=US-ASCII; name=0001-Add-tests-for-the-support-of-f-option-in-dropdb-util.patchDownload
From 21423c5e14a4279722d93866857910cbf86b5bff Mon Sep 17 00:00:00 2001
From: Pavel Stehule <pavel.stehule@gmail.com>, Vignesh
Date: Thu, 28 Nov 2019 09:58:11 +0530
Subject: [PATCH] Add tests for the support of '-f' option in dropdb utility.
Add tests for the support of '-f' option in dropdb utility.
---
src/bin/scripts/t/050_dropdb.pl | 8 +--
src/bin/scripts/t/051_dropdb_force.pl | 100 ++++++++++++++++++++++++++++++++++
2 files changed, 101 insertions(+), 7 deletions(-)
create mode 100644 src/bin/scripts/t/051_dropdb_force.pl
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
index c51babe..25aa54a 100644
--- a/src/bin/scripts/t/050_dropdb.pl
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -3,7 +3,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 13;
+use Test::More tests => 11;
program_help_ok('dropdb');
program_version_ok('dropdb');
@@ -19,11 +19,5 @@ $node->issues_sql_like(
qr/statement: DROP DATABASE foobar1/,
'SQL DROP DATABASE run');
-$node->safe_psql('postgres', 'CREATE DATABASE foobar2');
-$node->issues_sql_like(
- [ 'dropdb', '--force', 'foobar2' ],
- qr/statement: DROP DATABASE foobar2 WITH \(FORCE\);/,
- 'SQL DROP DATABASE (FORCE) run');
-
$node->command_fails([ 'dropdb', 'nonexistent' ],
'fails with nonexistent database');
diff --git a/src/bin/scripts/t/051_dropdb_force.pl b/src/bin/scripts/t/051_dropdb_force.pl
new file mode 100644
index 0000000..0f32dc5
--- /dev/null
+++ b/src/bin/scripts/t/051_dropdb_force.pl
@@ -0,0 +1,100 @@
+#
+# Tests drop database with force option.
+#
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 9;
+
+# To avoid hanging while expecting some specific input from a psql
+# instance being driven by us, add a timeout high enough that it
+# should never trigger even on very slow machines, unless something
+# is really wrong.
+my $psql_timeout = IPC::Run::timer(60);
+
+my $node = get_new_node('master');
+$node->init;
+$node->start;
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar');
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar' ],
+ qr/statement: DROP DATABASE foobar WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check database foobar does not exist.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar');]
+ ),
+ 'f',
+ 'database foobar was removed');
+
+# Create database that will be dropped.
+$node->safe_psql('postgres', 'CREATE DATABASE foobar1');
+
+# Run psql, keeping session alive, so we have an alive backend to kill.
+my ($killme_stdin, $killme_stdout, $killme_stderr) = ('', '', '');
+my $killme = IPC::Run::start(
+ [
+ 'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+ $node->connstr('foobar1')
+ ],
+ '<',
+ \$killme_stdin,
+ '>',
+ \$killme_stdout,
+ '2>',
+ \$killme_stderr,
+ $psql_timeout);
+
+# Ensure killme process is active.
+$killme_stdin .= q[
+SELECT pg_backend_pid();
+];
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stdout, qr/[[:digit:]]+[\r\n]$/m),
+ 'acquired pid for SIGTERM');
+my $pid = $killme_stdout;
+chomp($pid);
+$killme_stdout = '';
+$killme_stderr = '';
+
+# Check the connections on foobar1 database.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]
+ ),
+ $pid,
+ 'database foobar1 is used');
+
+# Now drop database with dropdb --force command.
+$node->issues_sql_like(
+ [ 'dropdb', '--force', 'foobar1' ],
+ qr/statement: DROP DATABASE foobar1 WITH \(FORCE\);/,
+ 'SQL DROP DATABASE (FORCE) run');
+
+# Check that psql sees the killed backend as having been terminated.
+$killme_stdin .= q[
+SELECT 1;
+];
+ok( TestLib::pump_until(
+ $killme, $psql_timeout, \$killme_stderr,
+ qr/FATAL: terminating connection due to administrator command/m),
+ "psql query died successfully after SIGTERM");
+$killme_stderr = '';
+$killme_stdout = '';
+$killme->finish;
+
+# Check database foobar1 does not exist.
+is( $node->safe_psql(
+ 'postgres',
+ qq[SELECT EXISTS(SELECT * FROM pg_database WHERE datname='foobar1');]
+ ),
+ 'f',
+ 'database foobar1 was removed');
+
+$node->stop();
--
1.8.3.1
On Thu, Nov 28, 2019 at 08:53:56AM +0530, Amit Kapila wrote:
I have pushed the refactoring patch. In the second patch, I have a
few more comments. I am not completely sure if it is a good idea to
write a new test file 060_dropdb_force.pl when we already have an
existing file 050_dropdb.pl for dropdb tests, but I think if we want
to do that, then lets move existing test for dropdb '-f' from
050_dropdb.pl to new file and it might be better to name new file as
051_dropdb_force.pl. I see that in some other cases like vacuumdb and
clusterdb, we have separate test files to cover a different kinds of
scenarios, so it should be okay to have a new file for dropdb tests.
Amit, as most of the patch set has been committed, would it make sense
to mark this entry as committed in the CF app?
--
Michael
On Fri, Nov 29, 2019 at 7:30 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Nov 28, 2019 at 08:53:56AM +0530, Amit Kapila wrote:
I have pushed the refactoring patch. In the second patch, I have a
few more comments. I am not completely sure if it is a good idea to
write a new test file 060_dropdb_force.pl when we already have an
existing file 050_dropdb.pl for dropdb tests, but I think if we want
to do that, then lets move existing test for dropdb '-f' from
050_dropdb.pl to new file and it might be better to name new file as
051_dropdb_force.pl. I see that in some other cases like vacuumdb and
clusterdb, we have separate test files to cover a different kinds of
scenarios, so it should be okay to have a new file for dropdb tests.Amit, as most of the patch set has been committed, would it make sense
to mark this entry as committed in the CF app?
Test 051_dropdb_force.pl is failing on Windows critters in the build farm,
e.g:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=drongo&dt=2019-11-29%2003%3A54%3A06
Regards,
Juan José Santamaría Flecha
On Fri, Nov 29, 2019 at 1:36 PM Juan José Santamaría Flecha
<juanjo.santamaria@gmail.com> wrote:
On Fri, Nov 29, 2019 at 7:30 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Nov 28, 2019 at 08:53:56AM +0530, Amit Kapila wrote:
I have pushed the refactoring patch. In the second patch, I have a
few more comments. I am not completely sure if it is a good idea to
write a new test file 060_dropdb_force.pl when we already have an
existing file 050_dropdb.pl for dropdb tests, but I think if we want
to do that, then lets move existing test for dropdb '-f' from
050_dropdb.pl to new file and it might be better to name new file as
051_dropdb_force.pl. I see that in some other cases like vacuumdb and
clusterdb, we have separate test files to cover a different kinds of
scenarios, so it should be okay to have a new file for dropdb tests.Amit, as most of the patch set has been committed, would it make sense
to mark this entry as committed in the CF app?
It might be better to move it to next CF as the discussion is still active.
Test 051_dropdb_force.pl is failing on Windows critters in the build farm, e.g:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=drongo&dt=2019-11-29%2003%3A54%3A06
Yeah, I have proposed something for it on pgsql-committers [1]/messages/by-id/CAA4eK1+GNyjaPK77y+euh5eAgM75pncG1JYZhxYZF+SgS6NpjA@mail.gmail.com.
[1]: /messages/by-id/CAA4eK1+GNyjaPK77y+euh5eAgM75pncG1JYZhxYZF+SgS6NpjA@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com
On Fri, Nov 29, 2019 at 1:36 PM Juan José Santamaría Flecha
<juanjo.santamaria@gmail.com> wrote:
On Fri, Nov 29, 2019 at 7:30 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Nov 28, 2019 at 08:53:56AM +0530, Amit Kapila wrote:
I have pushed the refactoring patch. In the second patch, I have a
few more comments. I am not completely sure if it is a good idea to
write a new test file 060_dropdb_force.pl when we already have an
existing file 050_dropdb.pl for dropdb tests, but I think if we want
to do that, then lets move existing test for dropdb '-f' from
050_dropdb.pl to new file and it might be better to name new file as
051_dropdb_force.pl. I see that in some other cases like vacuumdb and
clusterdb, we have separate test files to cover a different kinds of
scenarios, so it should be okay to have a new file for dropdb tests.Amit, as most of the patch set has been committed, would it make sense
to mark this entry as committed in the CF app?Test 051_dropdb_force.pl is failing on Windows critters in the build farm, e.g:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=drongo&dt=2019-11-29%2003%3A54%3A06
Attached patch includes the fix for the following failure in buildfarm:
Nov 28 09:00:01 # Failed test 'database foobar1 is used'
Nov 28 09:00:01 # at t/051_dropdb_force.pl line 71.
Nov 28 09:00:01 # got: '7380'
Nov 28 09:00:01 # expected: '7380
'
Nov 28 09:00:01 # aborting wait: program died
This test passes in most buildfarm environment, but it fails in few
windows environment randomly. The attached patch removes the query
which is not really needed for this test. Alternatively we could also
modify something like below as in PostgresNode.pm:
$pid =~ s/\r//g if $TestLib::windows_os;
I do not have an environment in which I could reproduce and I felt
this is not really needed as part of this testcase.
Any thoughts?
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Attachments:
0001-Drop-db-test-chomp-build-farm-failure-fix.patchtext/x-patch; charset=US-ASCII; name=0001-Drop-db-test-chomp-build-farm-failure-fix.patchDownload
diff --git a/src/bin/scripts/t/051_dropdb_force.pl b/src/bin/scripts/t/051_dropdb_force.pl
index a252b43..5326395 100644
--- a/src/bin/scripts/t/051_dropdb_force.pl
+++ b/src/bin/scripts/t/051_dropdb_force.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 9;
+use Test::More tests => 8;
# To avoid hanging while expecting some specific input from a psql
# instance being driven by us, add a timeout high enough that it
@@ -67,14 +67,6 @@ chomp($pid);
$killme_stdout = '';
$killme_stderr = '';
-# Check the connections on foobar1 database.
-is( $node->safe_psql(
- 'postgres',
- qq[SELECT pid FROM pg_stat_activity WHERE datname='foobar1' AND pid = $pid;]
- ),
- $pid,
- 'database foobar1 is used');
-
# Now drop database with dropdb --force command.
$node->issues_sql_like(
[ 'dropdb', '--force', 'foobar1' ],
On Fri, Nov 29, 2019 at 03:31:21PM +0530, Amit Kapila wrote:
It might be better to move it to next CF as the discussion is still active.
OK, just did that.
--
Michael
On Sat, Nov 30, 2019 at 7:46 AM Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Nov 29, 2019 at 03:31:21PM +0530, Amit Kapila wrote:
It might be better to move it to next CF as the discussion is still active.
OK, just did that.
I have marked this as committed in CF. This was committed some time
back[1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=1379fd537f9fc7941c8acff8c879ce3636dbdb77[2]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=80e05a088e4edd421c9c0374d54d787c8a4c0d86. I was just waiting for the conclusion on what we want to
do about Windows behavior related to socket closure which we
discovered while testing this feature. It is not very clear whether
we want to do anything about it, see discussion on thread [3]/messages/by-id/CALDaNm2tEvr_Kum7SyvFn0=6H3P0P-Zkhnd=dkkX+Q=wKutZ=A@mail.gmail.com, so I
closed this.
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=1379fd537f9fc7941c8acff8c879ce3636dbdb77
[2]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=80e05a088e4edd421c9c0374d54d787c8a4c0d86
[3]: /messages/by-id/CALDaNm2tEvr_Kum7SyvFn0=6H3P0P-Zkhnd=dkkX+Q=wKutZ=A@mail.gmail.com
--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com