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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to