diff options
author | Tom Lane | 2005-07-31 17:19:22 +0000 |
---|---|---|
committer | Tom Lane | 2005-07-31 17:19:22 +0000 |
commit | d42cf5a42a42689f68bc1ee1200aca75f954b1fd (patch) | |
tree | edce5e6641f56c2c7e836e52a8b6586d1ab45cb5 /src/backend | |
parent | b12587710701f63b4d8bac49f59901f210edf6b6 (diff) |
Add per-user and per-database connection limit options.
This patch also includes preliminary update of pg_dumpall for roles.
Petr Jelinek, with review by Bruce Momjian and Tom Lane.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/access/transam/twophase.c | 3 | ||||
-rw-r--r-- | src/backend/catalog/system_views.sql | 3 | ||||
-rw-r--r-- | src/backend/commands/dbcommands.c | 107 | ||||
-rw-r--r-- | src/backend/commands/user.c | 34 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 16 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 14 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 90 | ||||
-rw-r--r-- | src/backend/parser/keywords.c | 3 | ||||
-rw-r--r-- | src/backend/storage/ipc/procarray.c | 56 | ||||
-rw-r--r-- | src/backend/storage/lmgr/proc.c | 5 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 11 | ||||
-rw-r--r-- | src/backend/utils/init/miscinit.c | 49 | ||||
-rw-r--r-- | src/backend/utils/init/postinit.c | 47 |
13 files changed, 378 insertions, 60 deletions
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 5f7ea98ffe1..62ebf9fb406 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/twophase.c,v 1.8 2005/07/04 04:51:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/twophase.c,v 1.9 2005/07/31 17:19:17 tgl Exp $ * * NOTES * Each global transaction is associated with a global transaction @@ -272,6 +272,7 @@ MarkAsPreparing(TransactionId xid, const char *gid, gxact->proc.xmin = InvalidTransactionId; gxact->proc.pid = 0; gxact->proc.databaseId = databaseid; + gxact->proc.roleId = owner; gxact->proc.lwWaiting = false; gxact->proc.lwExclusive = false; gxact->proc.lwWaitLink = NULL; diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 22e2c911107..f3f3b356808 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -3,7 +3,7 @@ * * Copyright (c) 1996-2005, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.17 2005/07/26 16:38:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.18 2005/07/31 17:19:17 tgl Exp $ */ CREATE VIEW pg_roles AS @@ -15,6 +15,7 @@ CREATE VIEW pg_roles AS rolcreatedb, rolcatupdate, rolcanlogin, + rolconnlimit, '********'::text as rolpassword, rolvaliduntil, rolconfig, diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 4e23def8361..295ae955d1f 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.167 2005/07/14 21:46:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.168 2005/07/31 17:19:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -92,10 +92,12 @@ createdb(const CreatedbStmt *stmt) DefElem *downer = NULL; DefElem *dtemplate = NULL; DefElem *dencoding = NULL; + DefElem *dconnlimit = NULL; char *dbname = stmt->dbname; char *dbowner = NULL; const char *dbtemplate = NULL; int encoding = -1; + int dbconnlimit = -1; #ifndef WIN32 char buf[2 * MAXPGPATH + 100]; @@ -141,6 +143,14 @@ createdb(const CreatedbStmt *stmt) errmsg("conflicting or redundant options"))); dencoding = defel; } + else if (strcmp(defel->defname, "connectionlimit") == 0) + { + if (dconnlimit) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dconnlimit = defel; + } else if (strcmp(defel->defname, "location") == 0) { ereport(WARNING, @@ -186,6 +196,8 @@ createdb(const CreatedbStmt *stmt) elog(ERROR, "unrecognized node type: %d", nodeTag(dencoding->arg)); } + if (dconnlimit && dconnlimit->arg) + dbconnlimit = intVal(dconnlimit->arg); /* obtain OID of proposed owner */ if (dbowner) @@ -484,6 +496,7 @@ createdb(const CreatedbStmt *stmt) new_record[Anum_pg_database_encoding - 1] = Int32GetDatum(encoding); 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_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid); new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); @@ -791,6 +804,98 @@ RenameDatabase(const char *oldname, const char *newname) /* + * ALTER DATABASE name ... + */ +void +AlterDatabase(AlterDatabaseStmt *stmt) +{ + Relation rel; + HeapTuple tuple, + newtuple; + ScanKeyData scankey; + SysScanDesc scan; + ListCell *option; + int connlimit = -1; + DefElem *dconnlimit = NULL; + Datum new_record[Natts_pg_database]; + char new_record_nulls[Natts_pg_database]; + char new_record_repl[Natts_pg_database]; + + /* Extract options from the statement node tree */ + foreach(option, stmt->options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "connectionlimit") == 0) + { + if (dconnlimit) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dconnlimit = defel; + } + else + elog(ERROR, "option \"%s\" not recognized", + defel->defname); + } + + if (dconnlimit) + connlimit = intVal(dconnlimit->arg); + + /* + * We don't need ExclusiveLock since we aren't updating the + * flat file. + */ + rel = heap_open(DatabaseRelationId, RowExclusiveLock); + ScanKeyInit(&scankey, + Anum_pg_database_datname, + BTEqualStrategyNumber, F_NAMEEQ, + NameGetDatum(stmt->dbname)); + scan = systable_beginscan(rel, DatabaseNameIndexId, true, + SnapshotNow, 1, &scankey); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", stmt->dbname))); + + if (!pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, + stmt->dbname); + + /* + * Build an updated tuple, perusing the information just obtained + */ + MemSet(new_record, 0, sizeof(new_record)); + MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); + MemSet(new_record_repl, ' ', sizeof(new_record_repl)); + + if (dconnlimit) + { + new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(connlimit); + new_record_repl[Anum_pg_database_datconnlimit - 1] = 'r'; + } + + newtuple = heap_modifytuple(tuple, RelationGetDescr(rel), new_record, + new_record_nulls, new_record_repl); + simple_heap_update(rel, &tuple->t_self, newtuple); + + /* Update indexes */ + CatalogUpdateIndexes(rel, newtuple); + + systable_endscan(scan); + + /* Close pg_database, but keep lock till commit */ + heap_close(rel, NoLock); + + /* + * We don't bother updating the flat file since the existing options + * for ALTER DATABASE don't affect it. + */ +} + + +/* * ALTER DATABASE name SET ... */ void diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 6ef612dc4a9..082ea0cf7a0 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.159 2005/07/26 22:37:49 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.160 2005/07/31 17:19:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -86,6 +86,7 @@ CreateRole(CreateRoleStmt *stmt) bool createrole = false; /* Can this user create roles? */ bool createdb = false; /* Can the user create databases? */ bool canlogin = false; /* Can this user login? */ + int connlimit = -1; /* maximum connections allowed */ List *addroleto = NIL; /* roles to make this a member of */ List *rolemembers = NIL; /* roles to be members of this role */ List *adminmembers = NIL; /* roles to be admins of this role */ @@ -96,6 +97,7 @@ CreateRole(CreateRoleStmt *stmt) DefElem *dcreaterole = NULL; DefElem *dcreatedb = NULL; DefElem *dcanlogin = NULL; + DefElem *dconnlimit = NULL; DefElem *daddroleto = NULL; DefElem *drolemembers = NULL; DefElem *dadminmembers = NULL; @@ -178,6 +180,14 @@ CreateRole(CreateRoleStmt *stmt) errmsg("conflicting or redundant options"))); dcanlogin = defel; } + else if (strcmp(defel->defname, "connectionlimit") == 0) + { + if (dconnlimit) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dconnlimit = defel; + } else if (strcmp(defel->defname, "addroleto") == 0) { if (daddroleto) @@ -227,6 +237,8 @@ CreateRole(CreateRoleStmt *stmt) createdb = intVal(dcreatedb->arg) != 0; if (dcanlogin) canlogin = intVal(dcanlogin->arg) != 0; + if (dconnlimit) + connlimit = intVal(dconnlimit->arg); if (daddroleto) addroleto = (List *) daddroleto->arg; if (drolemembers) @@ -292,6 +304,7 @@ CreateRole(CreateRoleStmt *stmt) /* superuser gets catupdate right by default */ new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper); new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin); + new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); if (password) { @@ -401,6 +414,7 @@ AlterRole(AlterRoleStmt *stmt) int createrole = -1; /* Can this user create roles? */ int createdb = -1; /* Can the user create databases? */ int canlogin = -1; /* Can this user login? */ + int connlimit = -1; /* maximum connections allowed */ List *rolemembers = NIL; /* roles to be added/removed */ char *validUntil = NULL; /* time the login is valid until */ DefElem *dpassword = NULL; @@ -409,6 +423,7 @@ AlterRole(AlterRoleStmt *stmt) DefElem *dcreaterole = NULL; DefElem *dcreatedb = NULL; DefElem *dcanlogin = NULL; + DefElem *dconnlimit = NULL; DefElem *drolemembers = NULL; DefElem *dvalidUntil = NULL; Oid roleid; @@ -472,6 +487,14 @@ AlterRole(AlterRoleStmt *stmt) errmsg("conflicting or redundant options"))); dcanlogin = defel; } + else if (strcmp(defel->defname, "connectionlimit") == 0) + { + if (dconnlimit) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dconnlimit = defel; + } else if (strcmp(defel->defname, "rolemembers") == 0 && stmt->action != 0) { @@ -506,6 +529,8 @@ AlterRole(AlterRoleStmt *stmt) createdb = intVal(dcreatedb->arg); if (dcanlogin) canlogin = intVal(dcanlogin->arg); + if (dconnlimit) + connlimit = intVal(dconnlimit->arg); if (drolemembers) rolemembers = (List *) drolemembers->arg; if (dvalidUntil) @@ -545,6 +570,7 @@ AlterRole(AlterRoleStmt *stmt) createrole < 0 && createdb < 0 && canlogin < 0 && + !dconnlimit && !rolemembers && !validUntil && password && @@ -602,6 +628,12 @@ AlterRole(AlterRoleStmt *stmt) new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r'; } + if (dconnlimit) + { + new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); + new_record_repl[Anum_pg_authid_rolconnlimit - 1] = 'r'; + } + /* password */ if (password) { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 283cf549514..35fbee61641 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.312 2005/07/26 16:38:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.313 2005/07/31 17:19:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2204,6 +2204,17 @@ _copyCreatedbStmt(CreatedbStmt *from) return newnode; } +static AlterDatabaseStmt * +_copyAlterDatabaseStmt(AlterDatabaseStmt *from) +{ + AlterDatabaseStmt *newnode = makeNode(AlterDatabaseStmt); + + COPY_STRING_FIELD(dbname); + COPY_NODE_FIELD(options); + + return newnode; +} + static AlterDatabaseSetStmt * _copyAlterDatabaseSetStmt(AlterDatabaseSetStmt *from) { @@ -3011,6 +3022,9 @@ copyObject(void *from) case T_CreatedbStmt: retval = _copyCreatedbStmt(from); break; + case T_AlterDatabaseStmt: + retval = _copyAlterDatabaseStmt(from); + break; case T_AlterDatabaseSetStmt: retval = _copyAlterDatabaseSetStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 47e989dbc7d..6d727a63278 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.249 2005/07/26 16:38:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.250 2005/07/31 17:19:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1152,6 +1152,15 @@ _equalCreatedbStmt(CreatedbStmt *a, CreatedbStmt *b) } static bool +_equalAlterDatabaseStmt(AlterDatabaseStmt *a, AlterDatabaseStmt *b) +{ + COMPARE_STRING_FIELD(dbname); + COMPARE_NODE_FIELD(options); + + return true; +} + +static bool _equalAlterDatabaseSetStmt(AlterDatabaseSetStmt *a, AlterDatabaseSetStmt *b) { COMPARE_STRING_FIELD(dbname); @@ -2059,6 +2068,9 @@ equal(void *a, void *b) case T_CreatedbStmt: retval = _equalCreatedbStmt(a, b); break; + case T_AlterDatabaseStmt: + retval = _equalAlterDatabaseStmt(a, b); + break; case T_AlterDatabaseSetStmt: retval = _equalAlterDatabaseSetStmt(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3ce9ec411b7..f0475bf2bee 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.504 2005/07/26 22:37:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.505 2005/07/31 17:19:18 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -131,9 +131,9 @@ static void doNegateFloat(Value *v); } %type <node> stmt schema_stmt - AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt - AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt - AlterRoleStmt AlterRoleSetStmt + AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt + AlterOwnerStmt AlterSeqStmt AlterTableStmt + AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt @@ -165,8 +165,10 @@ static void doNegateFloat(Value *v); %type <dbehavior> opt_drop_behavior -%type <list> createdb_opt_list copy_opt_list transaction_mode_list -%type <defelt> createdb_opt_item copy_opt_item transaction_mode_item +%type <list> createdb_opt_list alterdb_opt_list copy_opt_list + transaction_mode_list +%type <defelt> createdb_opt_item alterdb_opt_item copy_opt_item + transaction_mode_item %type <ival> opt_lock lock_type cast_context %type <boolean> opt_force opt_or_replace @@ -257,7 +259,7 @@ static void doNegateFloat(Value *v); %type <boolean> copy_from opt_hold -%type <ival> fetch_count opt_column event cursor_options +%type <ival> opt_column event cursor_options %type <objtype> reindex_type drop_type comment_type %type <node> fetch_direction select_limit_value select_offset_value @@ -302,7 +304,7 @@ static void doNegateFloat(Value *v); %type <ival> opt_numeric opt_decimal %type <boolean> opt_varying opt_timezone -%type <ival> Iconst +%type <ival> Iconst SignedIconst %type <str> Sconst comment_text %type <str> RoleId opt_granted_by opt_boolean ColId_or_Sconst %type <list> var_list var_list_or_default @@ -342,7 +344,7 @@ static void doNegateFloat(Value *v); CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT - COMMITTED CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB + COMMITTED CONNECTION CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE @@ -486,7 +488,8 @@ stmtmulti: stmtmulti ';' stmt ; stmt : - AlterDatabaseSetStmt + AlterDatabaseStmt + | AlterDatabaseSetStmt | AlterDomainStmt | AlterFunctionStmt | AlterGroupStmt @@ -672,6 +675,10 @@ OptRoleElem: { $$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE)); } + | CONNECTION LIMIT SignedIconst + { + $$ = makeDefElem("connectionlimit", (Node *)makeInteger($3)); + } | IN_P ROLE name_list { $$ = makeDefElem("addroleto", (Node *)$3); @@ -2238,17 +2245,8 @@ FloatOnly: FCONST { $$ = makeFloat($1); } } ; -IntegerOnly: - Iconst - { - $$ = makeInteger($1); - } - | '-' Iconst - { - $$ = makeInteger($2); - $$->val.ival = - $$->val.ival; - } - ; +IntegerOnly: SignedIconst { $$ = makeInteger($1); }; + /***************************************************************************** * @@ -3044,21 +3042,21 @@ fetch_direction: n->howMany = -1; $$ = (Node *)n; } - | ABSOLUTE_P fetch_count + | ABSOLUTE_P SignedIconst { FetchStmt *n = makeNode(FetchStmt); n->direction = FETCH_ABSOLUTE; n->howMany = $2; $$ = (Node *)n; } - | RELATIVE_P fetch_count + | RELATIVE_P SignedIconst { FetchStmt *n = makeNode(FetchStmt); n->direction = FETCH_RELATIVE; n->howMany = $2; $$ = (Node *)n; } - | fetch_count + | SignedIconst { FetchStmt *n = makeNode(FetchStmt); n->direction = FETCH_FORWARD; @@ -3079,7 +3077,7 @@ fetch_direction: n->howMany = 1; $$ = (Node *)n; } - | FORWARD fetch_count + | FORWARD SignedIconst { FetchStmt *n = makeNode(FetchStmt); n->direction = FETCH_FORWARD; @@ -3100,7 +3098,7 @@ fetch_direction: n->howMany = 1; $$ = (Node *)n; } - | BACKWARD fetch_count + | BACKWARD SignedIconst { FetchStmt *n = makeNode(FetchStmt); n->direction = FETCH_BACKWARD; @@ -3116,11 +3114,6 @@ fetch_direction: } ; -fetch_count: - Iconst { $$ = $1; } - | '-' Iconst { $$ = - $2; } - ; - from_in: FROM {} | IN_P {} ; @@ -4473,6 +4466,10 @@ createdb_opt_item: { $$ = makeDefElem("encoding", NULL); } + | CONNECTION LIMIT opt_equal SignedIconst + { + $$ = makeDefElem("connectionlimit", (Node *)makeInteger($4)); + } | OWNER opt_equal name { $$ = makeDefElem("owner", (Node *)makeString($3)); @@ -4485,8 +4482,7 @@ createdb_opt_item: /* * Though the equals sign doesn't match other WITH options, pg_dump uses - * equals for backward compability, and it doesn't seem worth removing it. - * 2002-02-25 + * equals for backward compatibility, and it doesn't seem worth removing it. */ opt_equal: '=' {} | /*EMPTY*/ {} @@ -4499,6 +4495,16 @@ opt_equal: '=' {} * *****************************************************************************/ +AlterDatabaseStmt: + ALTER DATABASE database_name opt_with alterdb_opt_list + { + AlterDatabaseStmt *n = makeNode(AlterDatabaseStmt); + n->dbname = $3; + n->options = $5; + $$ = (Node *)n; + } + ; + AlterDatabaseSetStmt: ALTER DATABASE database_name SET set_rest { @@ -4519,6 +4525,19 @@ AlterDatabaseSetStmt: ; +alterdb_opt_list: + alterdb_opt_list alterdb_opt_item { $$ = lappend($1, $2); } + | /* EMPTY */ { $$ = NIL; } + ; + +alterdb_opt_item: + CONNECTION LIMIT opt_equal SignedIconst + { + $$ = makeDefElem("connectionlimit", (Node *)makeInteger($4)); + } + ; + + /***************************************************************************** * * DROP DATABASE @@ -7875,6 +7894,10 @@ Iconst: ICONST { $$ = $1; }; Sconst: SCONST { $$ = $1; }; RoleId: ColId { $$ = $1; }; +SignedIconst: ICONST { $$ = $1; } + | '-' ICONST { $$ = - $2; } + ; + /* * Name classification hierarchy. * @@ -7959,6 +7982,7 @@ unreserved_keyword: | COMMENT | COMMIT | COMMITTED + | CONNECTION | CONSTRAINTS | CONVERSION_P | COPY diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 5d4cab2124f..9c09dc5bf3b 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.163 2005/07/26 16:38:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.164 2005/07/31 17:19:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -83,6 +83,7 @@ static const ScanKeyword ScanKeywords[] = { {"comment", COMMENT}, {"commit", COMMIT}, {"committed", COMMITTED}, + {"connection", CONNECTION}, {"constraint", CONSTRAINT}, {"constraints", CONSTRAINTS}, {"conversion", CONVERSION_P}, diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 7c2766e6285..1e2783dea80 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -23,7 +23,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/ipc/procarray.c,v 1.3 2005/06/17 22:32:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/storage/ipc/procarray.c,v 1.4 2005/07/31 17:19:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -733,6 +733,60 @@ CountActiveBackends(void) return count; } +/* + * CountDBBackends --- count backends that are using specified database + */ +int +CountDBBackends(Oid databaseid) +{ + ProcArrayStruct *arrayP = procArray; + int count = 0; + int index; + + LWLockAcquire(ProcArrayLock, LW_SHARED); + + for (index = 0; index < arrayP->numProcs; index++) + { + PGPROC *proc = arrayP->procs[index]; + + if (proc->pid == 0) + continue; /* do not count prepared xacts */ + if (proc->databaseId == databaseid) + count++; + } + + LWLockRelease(ProcArrayLock); + + return count; +} + +/* + * CountUserBackends --- count backends that are used by specified user + */ +int +CountUserBackends(Oid roleid) +{ + ProcArrayStruct *arrayP = procArray; + int count = 0; + int index; + + LWLockAcquire(ProcArrayLock, LW_SHARED); + + for (index = 0; index < arrayP->numProcs; index++) + { + PGPROC *proc = arrayP->procs[index]; + + if (proc->pid == 0) + continue; /* do not count prepared xacts */ + if (proc->roleId == roleid) + count++; + } + + LWLockRelease(ProcArrayLock); + + return count; +} + #define XidCacheRemove(i) \ do { \ diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 227c3694788..11d0c70f6df 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.160 2005/06/17 22:32:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/storage/lmgr/proc.c,v 1.161 2005/07/31 17:19:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -254,6 +254,8 @@ InitProcess(void) MyProc->xmin = InvalidTransactionId; MyProc->pid = MyProcPid; MyProc->databaseId = MyDatabaseId; + /* Will be set properly after the session role id is determined */ + MyProc->roleId = InvalidOid; MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; @@ -331,6 +333,7 @@ InitDummyProcess(int proctype) MyProc->xid = InvalidTransactionId; MyProc->xmin = InvalidTransactionId; MyProc->databaseId = MyDatabaseId; + MyProc->roleId = InvalidOid; MyProc->lwWaiting = false; MyProc->lwExclusive = false; MyProc->lwWaitLink = NULL; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 6df4546538d..546aa49c2d0 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.241 2005/07/14 05:13:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.242 2005/07/31 17:19:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -275,6 +275,7 @@ check_xact_readonly(Node *parsetree) switch (nodeTag(parsetree)) { + case T_AlterDatabaseStmt: case T_AlterDatabaseSetStmt: case T_AlterDomainStmt: case T_AlterFunctionStmt: @@ -788,6 +789,10 @@ ProcessUtility(Node *parsetree, createdb((CreatedbStmt *) parsetree); break; + case T_AlterDatabaseStmt: + AlterDatabase((AlterDatabaseStmt *) parsetree); + break; + case T_AlterDatabaseSetStmt: AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree); break; @@ -1504,6 +1509,10 @@ CreateCommandTag(Node *parsetree) tag = "CREATE DATABASE"; break; + case T_AlterDatabaseStmt: + tag = "ALTER DATABASE"; + break; + case T_AlterDatabaseSetStmt: tag = "ALTER DATABASE"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 66d6d1725e0..f6c50438e03 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.147 2005/07/25 22:12:33 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.148 2005/07/31 17:19:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,8 @@ #include "storage/fd.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" +#include "storage/proc.h" +#include "storage/procarray.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/lsyscache.h" @@ -404,17 +406,52 @@ InitializeSessionUserId(const char *rolename) rform = (Form_pg_authid) GETSTRUCT(roleTup); roleid = HeapTupleGetOid(roleTup); - if (!rform->rolcanlogin) - ereport(FATAL, - (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), - errmsg("role \"%s\" is not permitted to log in", rolename))); - AuthenticatedUserId = roleid; AuthenticatedUserIsSuperuser = rform->rolsuper; /* This sets OuterUserId/CurrentUserId too */ SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); + /* Also mark our PGPROC entry with the authenticated user id */ + /* (We assume this is an atomic store so no lock is needed) */ + MyProc->roleId = roleid; + + /* + * These next checks are not enforced when in standalone mode, so that + * there is a way to recover from sillinesses like + * "UPDATE pg_authid SET rolcanlogin = false;". + * + * We do not enforce them for the autovacuum process either. + */ + if (IsUnderPostmaster && !IsAutoVacuumProcess()) + { + /* + * Is role allowed to login at all? + */ + if (!rform->rolcanlogin) + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role \"%s\" is not permitted to log in", + rolename))); + /* + * Check connection limit for this role. + * + * There is a race condition here --- we create our PGPROC before + * checking for other PGPROCs. If two backends did this at about the + * same time, they might both think they were over the limit, while + * ideally one should succeed and one fail. Getting that to work + * exactly seems more trouble than it is worth, however; instead + * we just document that the connection limit is approximate. + */ + if (rform->rolconnlimit >= 0 && + !AuthenticatedUserIsSuperuser && + CountUserBackends(roleid) > rform->rolconnlimit) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("too many connections for role \"%s\"", + rolename))); + } + /* Record username and superuser status as GUC settings too */ SetConfigOption("session_authorization", rolename, PGC_BACKEND, PGC_S_OVERRIDE); diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index b013eca86cf..d06fa5db369 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.154 2005/07/29 19:30:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.155 2005/07/31 17:19:19 tgl Exp $ * * *------------------------------------------------------------------------- @@ -169,18 +169,43 @@ ReverifyMyDatabase(const char *name) name, MyDatabaseId))); } + dbform = (Form_pg_database) GETSTRUCT(tup); + /* - * Also check that the database is currently allowing connections. - * (We do not enforce this in standalone mode, however, so that there is - * a way to recover from "UPDATE pg_database SET datallowconn = false;". - * We do not enforce it for the autovacuum process either.) + * These next checks are not enforced when in standalone mode, so that + * there is a way to recover from disabling all access to all databases, + * for example "UPDATE pg_database SET datallowconn = false;". + * + * We do not enforce them for the autovacuum process either. */ - dbform = (Form_pg_database) GETSTRUCT(tup); - if (IsUnderPostmaster && !IsAutoVacuumProcess() && !dbform->datallowconn) - ereport(FATAL, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("database \"%s\" is not currently accepting connections", - name))); + if (IsUnderPostmaster && !IsAutoVacuumProcess()) + { + /* + * Check that the database is currently allowing connections. + */ + if (!dbform->datallowconn) + ereport(FATAL, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("database \"%s\" is not currently accepting connections", + name))); + /* + * Check connection limit for this database. + * + * There is a race condition here --- we create our PGPROC before + * checking for other PGPROCs. If two backends did this at about the + * same time, they might both think they were over the limit, while + * ideally one should succeed and one fail. Getting that to work + * exactly seems more trouble than it is worth, however; instead + * we just document that the connection limit is approximate. + */ + if (dbform->datconnlimit >= 0 && + !superuser() && + CountDBBackends(MyDatabaseId) > dbform->datconnlimit) + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("too many connections for database \"%s\"", + name))); + } /* * OK, we're golden. Next to-do item is to save the encoding |