We try to tell our clients not to update the catalogs directly, but
there are at least two instances where it's not possible to do otherwise
(pg_database.datistemplate and .datallowconn). This patch aims to
remedy that.
For example, it is now possible to say
ALTER DATABASE d ALLOW CONNECTIONS = false;
and
ALTER DATABASE d IS TEMPLATE = true;
This syntax matches that of CONNECTION LIMIT but unfortunately required
me to make ALLOW and CONNECTIONS unreserved keywords. I know we try not
to do that but I didn't see any other way. The two new options are of
course also available on CREATE DATABASE.
There is a slight change in behavior with this patch in that previously
one had to be superuser or have rolcatupdate appropriately set, and now
the owner of the database is also allowed to change these settings. I
believe this is for the better.
It was suggested to me that these options should either error out if
there are existing connections or terminate said connections. I don't
agree with that because there is no harm in connecting to a template
database (how else do you modify it?), and adding a reject rule in
pg_hba.conf doesn't disconnect existing users so why should turning off
ALLOW CONNECTIONS do it?
As for regression tests, I couldn't figure out how to make CREATE/ALTER
DATABASE play nice with make installcheck and so I haven't provided any.
Other than that, I think this patch is complete and so I'm adding it the
next commitfest.
--
Vik
*** a/doc/src/sgml/ref/alter_database.sgml
--- b/doc/src/sgml/ref/alter_database.sgml
***************
*** 25,30 **** ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <rep
--- 25,32 ----
<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+ IS TEMPLATE <replaceable class="PARAMETER">istemplate</replaceable>
+ ALLOW CONNECTIONS <replaceable class="PARAMETER">allowconn</replaceable>
CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
***************
*** 107,112 **** ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> RESET ALL
--- 109,134 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">istemplate</replaceable></term>
+ <listitem>
+ <para>
+ If true, then this database can be cloned by any user with CREATEDB
+ privileges; if false, then only superusers or the owner of the
+ database can clone it.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">allowconn</replaceable></term>
+ <listitem>
+ <para>
+ If false then no one can connect to this database.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="parameter">connlimit</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/ref/create_database.sgml
--- b/doc/src/sgml/ref/create_database.sgml
***************
*** 28,33 **** CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
--- 28,35 ----
[ LC_COLLATE [=] <replaceable class="parameter">lc_collate</replaceable> ]
[ LC_CTYPE [=] <replaceable class="parameter">lc_ctype</replaceable> ]
[ TABLESPACE [=] <replaceable class="parameter">tablespace_name</replaceable> ]
+ [ IS TEMPLATE [=] <replaceable class="parameter">istemplate</replaceable>]
+ [ ALLOW CONNECTIONS [=] <replaceable class="parameter">allowconn</replaceable>]
[ CONNECTION LIMIT [=] <replaceable class="parameter">connlimit</replaceable> ] ]
</synopsis>
</refsynopsisdiv>
***************
*** 148,153 **** CREATE DATABASE <replaceable class="PARAMETER">name</replaceable>
--- 150,175 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">istemplate</replaceable></term>
+ <listitem>
+ <para>
+ If true, then this database can be cloned by any user with CREATEDB
+ privileges; if false, then only superusers or the owner of the
+ database can clone it.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">allowconn</replaceable></term>
+ <listitem>
+ <para>
+ If false then no one can connect to this database.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="parameter">connlimit</replaceable></term>
<listitem>
<para>
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
***************
*** 39,44 ****
--- 39,45 ----
#include "catalog/pg_tablespace.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
+ #include "commands/defrem.h"
#include "commands/seclabel.h"
#include "commands/tablespace.h"
#include "mb/pg_wchar.h"
***************
*** 122,127 **** createdb(const CreatedbStmt *stmt)
--- 123,130 ----
DefElem *dencoding = NULL;
DefElem *dcollate = NULL;
DefElem *dctype = NULL;
+ DefElem *distemplate = NULL;
+ DefElem *dallowconn = NULL;
DefElem *dconnlimit = NULL;
char *dbname = stmt->dbname;
char *dbowner = NULL;
***************
*** 130,135 **** createdb(const CreatedbStmt *stmt)
--- 133,140 ----
char *dbctype = NULL;
char *canonname;
int encoding = -1;
+ bool istemplate = false;
+ bool allowconn = true;
int dbconnlimit = -1;
int notherbackends;
int npreparedxacts;
***************
*** 188,193 **** createdb(const CreatedbStmt *stmt)
--- 193,214 ----
errmsg("conflicting or redundant options")));
dctype = defel;
}
+ else if (strcmp(defel->defname, "istemplate") == 0)
+ {
+ if (distemplate)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ distemplate = defel;
+ }
+ else if (strcmp(defel->defname, "allowconnections") == 0)
+ {
+ if (dallowconn)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dallowconn = defel;
+ }
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
***************
*** 246,251 **** createdb(const CreatedbStmt *stmt)
--- 267,277 ----
if (dctype && dctype->arg)
dbctype = strVal(dctype->arg);
+ if (distemplate && distemplate->arg)
+ istemplate = defGetBoolean(distemplate);
+ if (dallowconn && dallowconn->arg)
+ allowconn = defGetBoolean(dallowconn);
+
if (dconnlimit && dconnlimit->arg)
{
dbconnlimit = intVal(dconnlimit->arg);
***************
*** 488,495 **** createdb(const CreatedbStmt *stmt)
DirectFunctionCall1(namein, CStringGetDatum(dbcollate));
new_record[Anum_pg_database_datctype - 1] =
DirectFunctionCall1(namein, CStringGetDatum(dbctype));
! new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(false);
! new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true);
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
--- 514,521 ----
DirectFunctionCall1(namein, CStringGetDatum(dbcollate));
new_record[Anum_pg_database_datctype - 1] =
DirectFunctionCall1(namein, CStringGetDatum(dbctype));
! new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(istemplate);
! new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(allowconn);
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
***************
*** 1329,1335 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel)
--- 1355,1365 ----
ScanKeyData scankey;
SysScanDesc scan;
ListCell *option;
+ bool istemplate = false;
+ bool allowconn = true;
int connlimit = -1;
+ DefElem *distemplate = NULL;
+ DefElem *dallowconn = NULL;
DefElem *dconnlimit = NULL;
DefElem *dtablespace = NULL;
Datum new_record[Natts_pg_database];
***************
*** 1341,1347 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel)
{
DefElem *defel = (DefElem *) lfirst(option);
! if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
ereport(ERROR,
--- 1371,1393 ----
{
DefElem *defel = (DefElem *) lfirst(option);
! if (strcmp(defel->defname, "istemplate") == 0)
! {
! if (distemplate)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting or redundant options")));
! distemplate = defel;
! }
! else if (strcmp(defel->defname, "allowconnections") == 0)
! {
! if (dallowconn)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting or redundant options")));
! dallowconn = defel;
! }
! else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
ereport(ERROR,
***************
*** 1365,1377 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel)
if (dtablespace)
{
/* currently, can't be specified along with any other options */
! Assert(!dconnlimit);
/* this case isn't allowed within a transaction block */
PreventTransactionChain(isTopLevel, "ALTER DATABASE SET TABLESPACE");
movedb(stmt->dbname, strVal(dtablespace->arg));
return InvalidOid;
}
if (dconnlimit)
{
connlimit = intVal(dconnlimit->arg);
--- 1411,1428 ----
if (dtablespace)
{
/* currently, can't be specified along with any other options */
! Assert(!distemplate && !dallowconn && !dconnlimit);
/* this case isn't allowed within a transaction block */
PreventTransactionChain(isTopLevel, "ALTER DATABASE SET TABLESPACE");
movedb(stmt->dbname, strVal(dtablespace->arg));
return InvalidOid;
}
+ if (distemplate)
+ istemplate = defGetBoolean(distemplate);
+ if (dallowconn)
+ allowconn = defGetBoolean(dallowconn);
+
if (dconnlimit)
{
connlimit = intVal(dconnlimit->arg);
***************
*** 1412,1417 **** AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel)
--- 1463,1480 ----
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ if (distemplate)
+ {
+ new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(istemplate);
+ new_record_repl[Anum_pg_database_datistemplate -1] = true;
+ }
+
+ if (dallowconn)
+ {
+ new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(allowconn);
+ new_record_repl[Anum_pg_database_datallowconn - 1] = true;
+ }
+
if (dconnlimit)
{
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(connlimit);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 522,528 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
! AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
--- 522,528 ----
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
! AGGREGATE ALL ALLOW ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
***************
*** 531,537 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
! COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
CROSS CSV CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
--- 531,537 ----
CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
! COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONNECTIONS CONSTRAINT CONSTRAINTS
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
CROSS CSV CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
***************
*** 8374,8379 **** createdb_opt_item:
--- 8374,8387 ----
{
$$ = makeDefElem("lc_ctype", NULL);
}
+ | IS TEMPLATE opt_equal opt_boolean_or_string
+ {
+ $$ = makeDefElem("istemplate", (Node *)makeString($4));
+ }
+ | ALLOW CONNECTIONS opt_equal opt_boolean_or_string
+ {
+ $$ = makeDefElem("allowconnections", (Node *)makeString($4));
+ }
| CONNECTION LIMIT opt_equal SignedIconst
{
$$ = makeDefElem("connectionlimit", (Node *)makeInteger($4));
***************
*** 8438,8444 **** alterdb_opt_list:
;
alterdb_opt_item:
! CONNECTION LIMIT opt_equal SignedIconst
{
$$ = makeDefElem("connectionlimit", (Node *)makeInteger($4));
}
--- 8446,8460 ----
;
alterdb_opt_item:
! IS TEMPLATE opt_equal opt_boolean_or_string
! {
! $$ = makeDefElem("istemplate", (Node *)makeString($4));
! }
! | ALLOW CONNECTIONS opt_equal opt_boolean_or_string
! {
! $$ = makeDefElem("allowconnections", (Node *)makeString($4));
! }
! | CONNECTION LIMIT opt_equal SignedIconst
{
$$ = makeDefElem("connectionlimit", (Node *)makeInteger($4));
}
***************
*** 12805,12810 **** unreserved_keyword:
--- 12821,12827 ----
| ADMIN
| AFTER
| AGGREGATE
+ | ALLOW
| ALSO
| ALTER
| ALWAYS
***************
*** 12833,12838 **** unreserved_keyword:
--- 12850,12856 ----
| COMMITTED
| CONFIGURATION
| CONNECTION
+ | CONNECTIONS
| CONSTRAINTS
| CONTENT_P
| CONTINUE_P
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 987,994 **** psql_completion(const char *text, int start, int end)
{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
COMPLETE_WITH_LIST(list_ALTERGEN);
! }
!
/* ALTER CONVERSION <name> */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
--- 987,993 ----
{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
COMPLETE_WITH_LIST(list_ALTERGEN);
! }
/* ALTER CONVERSION <name> */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
***************
*** 1004,1010 **** psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "DATABASE") == 0)
{
static const char *const list_ALTERDATABASE[] =
! {"RESET", "SET", "OWNER TO", "RENAME TO", "CONNECTION LIMIT", NULL};
COMPLETE_WITH_LIST(list_ALTERDATABASE);
}
--- 1003,1010 ----
pg_strcasecmp(prev2_wd, "DATABASE") == 0)
{
static const char *const list_ALTERDATABASE[] =
! {"RESET", "SET", "OWNER TO", "RENAME TO", "IS TEMPLATE",
! "ALLOW CONNECTIONS", "CONNECTION LIMIT", NULL};
COMPLETE_WITH_LIST(list_ALTERDATABASE);
}
***************
*** 2045,2052 **** psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "DATABASE") == 0)
{
static const char *const list_DATABASE[] =
! {"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "CONNECTION LIMIT",
! NULL};
COMPLETE_WITH_LIST(list_DATABASE);
}
--- 2045,2052 ----
pg_strcasecmp(prev2_wd, "DATABASE") == 0)
{
static const char *const list_DATABASE[] =
! {"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "IS TEMPLATE",
! "ALLOW CONNECTIONS", "CONNECTION LIMIT", NULL};
COMPLETE_WITH_LIST(list_DATABASE);
}
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 35,40 **** PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD)
--- 35,41 ----
PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD)
PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD)
PG_KEYWORD("all", ALL, RESERVED_KEYWORD)
+ PG_KEYWORD("allow", ALLOW, UNRESERVED_KEYWORD)
PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD)
PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD)
PG_KEYWORD("always", ALWAYS, UNRESERVED_KEYWORD)
***************
*** 88,93 **** PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
--- 89,95 ----
PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD)
PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD)
+ PG_KEYWORD("connections", CONNECTIONS, UNRESERVED_KEYWORD)
PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD)
PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD)
PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD)
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers