Thread: per user/database connections limit again

per user/database connections limit again

From
Petr Jelinek
Date:
Hi,

I attached  second try of per-database and per-user connection limit for
your review.

This time I am using information stored in ProcArray to get number of
connections - I modified PGPROC struct to also include userid.

Limits for user and database are stored in catalog tables. This aproach
led to implementation of "universal" ALTER DATABASE query (I followed
ALTER USER ad ALTER DATABASE ... RENAME implementatons). So queries for
setting maximum connections look like this: CREATE|ALTER DATABASE|USER
name MAX CONNECTIONS = 20;
Maximum connections defaults to zero which means unlimited (limited by
global maximum only) and isn't enforced for superusers.

The actual check for maximum conenctions is done in ReverifyMyDatabase
for database and InitializeSessionUser for user because we don't have
information from system catalog before so we don't know how many
connections are allowed.

Patch includes only changes to backend, I will make pg_dump, ecpg and
documentation patches once this is completed and accepted by team.

Diff is made against cvs from today morning GMT (apply with -p1 if you
want to test it) - cvs is down now so I can't make diff against repository.

--
Regards
Petr Jelinek (PJMODOS)


diff -Nacr my-cvs/src/backend/commands/dbcommands.c my-aproach2/src/backend/commands/dbcommands.c
*** my-cvs/src/backend/commands/dbcommands.c    Sun Jun 26 00:47:30 2005
--- my-aproach2/src/backend/commands/dbcommands.c    Tue Jun 28 11:26:08 2005
***************
*** 53,60 ****

  /* non-export function prototypes */
  static bool get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
!             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
!             Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace);
  static bool have_createdb_privilege(void);
--- 53,60 ----

  /* non-export function prototypes */
  static bool get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
!             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
!             bool *dbAllowConnP,    Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace);
  static bool have_createdb_privilege(void);
***************
*** 74,79 ****
--- 74,80 ----
      int            src_encoding;
      bool        src_istemplate;
      bool        src_allowconn;
+     int            src_maxconn;
      Oid            src_lastsysoid;
      TransactionId src_vacuumxid;
      TransactionId src_frozenxid;
***************
*** 91,100 ****
--- 92,103 ----
      DefElem    *downer = NULL;
      DefElem    *dtemplate = NULL;
      DefElem    *dencoding = NULL;
+     DefElem    *dmaxconn = NULL;
      char       *dbname = stmt->dbname;
      char       *dbowner = NULL;
      const char *dbtemplate = NULL;
      int            encoding = -1;
+     int            dbmaxconn = -1;

  #ifndef WIN32
      char        buf[2 * MAXPGPATH + 100];
***************
*** 140,145 ****
--- 143,156 ----
                           errmsg("conflicting or redundant options")));
              dencoding = defel;
          }
+         else if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
          else if (strcmp(defel->defname, "location") == 0)
          {
              ereport(WARNING,
***************
*** 185,190 ****
--- 196,203 ----
              elog(ERROR, "unrecognized node type: %d",
                   nodeTag(dencoding->arg));
      }
+     if (dmaxconn && dmaxconn->arg)
+         dbmaxconn = intVal(dmaxconn->arg);

      /* obtain sysid of proposed owner */
      if (dbowner)
***************
*** 218,224 ****
       * idea, so accept possibility of race to create.  We will check again
       * after we grab the exclusive lock.
       */
!     if (get_db_info(dbname, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_DUPLICATE_DATABASE),
--- 231,237 ----
       * idea, so accept possibility of race to create.  We will check again
       * after we grab the exclusive lock.
       */
!     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_DUPLICATE_DATABASE),
***************
*** 231,238 ****
          dbtemplate = "template1";        /* Default template database name */

      if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
!                      &src_istemplate, &src_allowconn, &src_lastsysoid,
!                      &src_vacuumxid, &src_frozenxid, &src_deftablespace))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
           errmsg("template database \"%s\" does not exist", dbtemplate)));
--- 244,252 ----
          dbtemplate = "template1";        /* Default template database name */

      if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
!                      &src_maxconn, &src_istemplate, &src_allowconn,
!                      &src_lastsysoid, &src_vacuumxid, &src_frozenxid,
!                      &src_deftablespace))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
           errmsg("template database \"%s\" does not exist", dbtemplate)));
***************
*** 266,271 ****
--- 280,289 ----
      if (encoding < 0)
          encoding = src_encoding;

+     /* If dbmaxconn is defaulted, use source's dbmaxconn */
+     if (dbmaxconn < 0)
+         dbmaxconn = src_maxconn;
+
      /* Some encodings are client only */
      if (!PG_VALID_BE_ENCODING(encoding))
          ereport(ERROR,
***************
*** 461,467 ****
      pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);

      /* Check to see if someone else created same DB name meanwhile. */
!     if (get_db_info(dbname, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
      {
          /* Don't hold lock while doing recursive remove */
--- 479,485 ----
      pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);

      /* Check to see if someone else created same DB name meanwhile. */
!     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
      {
          /* Don't hold lock while doing recursive remove */
***************
*** 487,492 ****
--- 505,511 ----
      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_datmaxconn - 1] = Int32GetDatum(dbmaxconn);
      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);
***************
*** 588,594 ****
       */
      pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);

!     if (!get_db_info(dbname, &db_id, &db_owner, NULL,
                       &db_istemplate, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
--- 607,613 ----
       */
      pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);

!     if (!get_db_info(dbname, &db_id, &db_owner, NULL, NULL,
                       &db_istemplate, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
***************
*** 784,789 ****
--- 803,894 ----


  /*
+  * ALTER DATABASE name ...
+  */
+ void
+ AlterDatabase(AlterDatabaseStmt *stmt)
+ {
+     Datum        new_record[Natts_pg_database];
+     char        new_record_nulls[Natts_pg_database];
+     char        new_record_repl[Natts_pg_database];
+     Relation    rel;
+     HeapTuple    tuple,
+                 newtuple;
+     ScanKeyData scankey;
+     SysScanDesc scan;
+     ListCell   *option;
+     int            maxconn = -1;    /* Maximum connections allowed */
+
+     DefElem    *dmaxconn = NULL;
+
+     /* Extract options from the statement node tree */
+     foreach(option, stmt->options)
+     {
+         DefElem    *defel = (DefElem *) lfirst(option);
+
+         if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
+     }
+
+     if (dmaxconn)
+         maxconn = intVal(dmaxconn->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 (!(superuser()
+         || ((Form_pg_database) GETSTRUCT(tuple))->datdba == 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 (maxconn >= 0)
+     {
+         new_record[Anum_pg_database_datmaxconn - 1] = Int32GetDatum(maxconn);
+         new_record_repl[Anum_pg_database_datmaxconn - 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);
+ }
+
+
+ /*
   * ALTER DATABASE name SET ...
   */
  void
***************
*** 973,980 ****

  static bool
  get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
!             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
!             Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace)
  {
--- 1078,1085 ----

  static bool
  get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
!             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
!             bool *dbAllowConnP,    Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace)
  {
***************
*** 1019,1024 ****
--- 1124,1132 ----
          /* allowing connections? */
          if (dbAllowConnP)
              *dbAllowConnP = dbform->datallowconn;
+         /* maximum connections */
+         if (dbMaxConnP)
+             *dbMaxConnP = dbform->datmaxconn;
          /* last system OID used in database */
          if (dbLastSysOidP)
              *dbLastSysOidP = dbform->datlastsysoid;
diff -Nacr my-cvs/src/backend/commands/user.c my-aproach2/src/backend/commands/user.c
*** my-cvs/src/backend/commands/user.c    Thu Apr 14 22:03:24 2005
--- my-aproach2/src/backend/commands/user.c    Tue Jun 28 11:26:18 2005
***************
*** 64,69 ****
--- 64,70 ----
      int            sysid = 0;        /* PgSQL system id (valid if havesysid) */
      bool        createdb = false;        /* Can the user create databases? */
      bool        createuser = false;        /* Can this user create users? */
+     int            maxconn = false;        /* maximum connections allowed */
      List       *groupElts = NIL;    /* The groups the user is a member of */
      char       *validUntil = NULL;        /* The time the login is valid
                                           * until */
***************
*** 73,78 ****
--- 74,80 ----
      DefElem    *dcreateuser = NULL;
      DefElem    *dgroupElts = NULL;
      DefElem    *dvalidUntil = NULL;
+     DefElem    *dmaxconn = NULL;

      /* Extract options from the statement node tree */
      foreach(option, stmt->options)
***************
*** 117,122 ****
--- 119,132 ----
                           errmsg("conflicting or redundant options")));
              dcreateuser = defel;
          }
+         else if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
          else if (strcmp(defel->defname, "groupElts") == 0)
          {
              if (dgroupElts)
***************
*** 142,147 ****
--- 152,165 ----
          createdb = intVal(dcreatedb->arg) != 0;
      if (dcreateuser)
          createuser = intVal(dcreateuser->arg) != 0;
+     if (dmaxconn)
+     {
+         maxconn = intVal(dmaxconn->arg);
+         if (maxconn < 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("MAX CONNECTIONS must not be negative")));
+     }
      if (dsysid)
      {
          sysid = intVal(dsysid->arg);
***************
*** 233,238 ****
--- 251,257 ----
      new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
      /* superuser gets catupd right by default */
      new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
+     new_record[Anum_pg_shadow_usemaxconn - 1] = Int32GetDatum(maxconn);

      if (password)
      {
***************
*** 317,328 ****
--- 336,349 ----
      char        encrypted_password[MD5_PASSWD_LEN + 1];
      int            createdb = -1;    /* Can the user create databases? */
      int            createuser = -1;    /* Can this user create users? */
+     int            maxconn = -1;    /* Maximum connections allowed */
      char       *validUntil = NULL;        /* The time the login is valid
                                           * until */
      DefElem    *dpassword = NULL;
      DefElem    *dcreatedb = NULL;
      DefElem    *dcreateuser = NULL;
      DefElem    *dvalidUntil = NULL;
+     DefElem    *dmaxconn = NULL;

      /* Extract options from the statement node tree */
      foreach(option, stmt->options)
***************
*** 359,364 ****
--- 380,393 ----
                           errmsg("conflicting or redundant options")));
              dcreateuser = defel;
          }
+         else if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
          else if (strcmp(defel->defname, "validUntil") == 0)
          {
              if (dvalidUntil)
***************
*** 376,381 ****
--- 405,412 ----
          createdb = intVal(dcreatedb->arg);
      if (dcreateuser)
          createuser = intVal(dcreateuser->arg);
+     if (dmaxconn)
+         maxconn = intVal(dmaxconn->arg);
      if (dvalidUntil)
          validUntil = strVal(dvalidUntil->arg);
      if (dpassword)
***************
*** 427,432 ****
--- 458,469 ----
      {
          new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
          new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
+     }
+
+     if (maxconn >= 0)
+     {
+         new_record[Anum_pg_shadow_usemaxconn - 1] = Int32GetDatum(maxconn);
+         new_record_repl[Anum_pg_shadow_usemaxconn - 1] = 'r';
      }

      /*
diff -Nacr my-cvs/src/backend/nodes/copyfuncs.c my-aproach2/src/backend/nodes/copyfuncs.c
*** my-cvs/src/backend/nodes/copyfuncs.c    Mon Jun 27 00:05:38 2005
--- my-aproach2/src/backend/nodes/copyfuncs.c    Tue Jun 28 06:07:50 2005
***************
*** 2191,2196 ****
--- 2191,2207 ----
      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)
  {
diff -Nacr my-cvs/src/backend/nodes/equalfuncs.c my-aproach2/src/backend/nodes/equalfuncs.c
*** my-cvs/src/backend/nodes/equalfuncs.c    Mon Jun 27 00:05:38 2005
--- my-aproach2/src/backend/nodes/equalfuncs.c    Tue Jun 28 06:07:50 2005
***************
*** 1141,1146 ****
--- 1141,1155 ----
  }

  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);
diff -Nacr my-cvs/src/backend/parser/gram.y my-aproach2/src/backend/parser/gram.y
*** my-cvs/src/backend/parser/gram.y    Mon Jun 27 00:05:38 2005
--- my-aproach2/src/backend/parser/gram.y    Tue Jun 28 11:26:30 2005
***************
*** 131,139 ****
  }

  %type <node>    stmt schema_stmt
!         AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
!         AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
!         AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
          ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
          CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
          CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
--- 131,139 ----
  }

  %type <node>    stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
!         AlterOwnerStmt AlterSeqStmt AlterTableStmt AlterUserStmt
!         AlterUserSetStmt AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
          ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
          CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
          CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
***************
*** 164,171 ****

  %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 <ival>    opt_lock lock_type cast_context
  %type <boolean>    opt_force opt_or_replace
--- 164,173 ----

  %type <dbehavior>    opt_drop_behavior

! %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
***************
*** 346,352 ****
      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
      CREATEUSER CROSS CSV CURRENT_DATE CURRENT_TIME
      CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE

--- 348,354 ----
      CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P
      CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
      CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
!     COMMITTED CONNECTIONS CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
      CREATEUSER CROSS CSV CURRENT_DATE CURRENT_TIME
      CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE

***************
*** 377,383 ****
      LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
      LOCK_P

!     MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE

      NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
      NOCREATEUSER NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P
--- 379,385 ----
      LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
      LOCK_P

!     MATCH MAX MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE

      NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
      NOCREATEUSER NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P
***************
*** 490,496 ****
          ;

  stmt :
!             AlterDatabaseSetStmt
              | AlterDomainStmt
              | AlterFunctionStmt
              | AlterGroupStmt
--- 492,499 ----
          ;

  stmt :
!             AlterDatabaseStmt
!             | AlterDatabaseSetStmt
              | AlterDomainStmt
              | AlterFunctionStmt
              | AlterGroupStmt
***************
*** 688,693 ****
--- 691,700 ----
                  {
                      $$ = makeDefElem("createuser", (Node *)makeInteger(FALSE));
                  }
+             | MAX CONNECTIONS Iconst
+                 {
+                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($3));
+                 }
              | IN_P GROUP_P user_list
                  {
                      $$ = makeDefElem("groupElts", (Node *)$3);
***************
*** 4294,4299 ****
--- 4301,4310 ----
                  {
                      $$ = makeDefElem("encoding", NULL);
                  }
+             | MAX CONNECTIONS opt_equal Iconst
+                 {
+                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
+                 }
              | OWNER opt_equal name
                  {
                      $$ = makeDefElem("owner", (Node *)makeString($3));
***************
*** 4320,4325 ****
--- 4331,4346 ----
   *
   *****************************************************************************/

+ 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
                  {
***************
*** 4340,4345 ****
--- 4361,4379 ----
          ;


+ alterdb_opt_list:
+             alterdb_opt_list alterdb_opt_item        { $$ = lappend($1, $2); }
+             | /* EMPTY */                            { $$ = NIL; }
+         ;
+
+ alterdb_opt_item:
+             MAX CONNECTIONS opt_equal Iconst
+                 {
+                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
+                 }
+         ;
+
+
  /*****************************************************************************
   *
   *        DROP DATABASE
***************
*** 7770,7775 ****
--- 7804,7810 ----
              | COMMENT
              | COMMIT
              | COMMITTED
+             | CONNECTIONS
              | CONSTRAINTS
              | CONVERSION_P
              | COPY
***************
*** 7835,7840 ****
--- 7870,7876 ----
              | LOCATION
              | LOCK_P
              | MATCH
+             | MAX
              | MAXVALUE
              | MINUTE_P
              | MINVALUE
diff -Nacr my-cvs/src/backend/parser/keywords.c my-aproach2/src/backend/parser/keywords.c
*** my-cvs/src/backend/parser/keywords.c    Mon Jun 27 00:05:40 2005
--- my-aproach2/src/backend/parser/keywords.c    Tue Jun 28 06:07:50 2005
***************
*** 82,87 ****
--- 82,88 ----
      {"comment", COMMENT},
      {"commit", COMMIT},
      {"committed", COMMITTED},
+     {"connections", CONNECTIONS},
      {"constraint", CONSTRAINT},
      {"constraints", CONSTRAINTS},
      {"conversion", CONVERSION_P},
***************
*** 198,203 ****
--- 199,205 ----
      {"location", LOCATION},
      {"lock", LOCK_P},
      {"match", MATCH},
+     {"max", MAX},
      {"maxvalue", MAXVALUE},
      {"minute", MINUTE_P},
      {"minvalue", MINVALUE},
diff -Nacr my-cvs/src/backend/storage/ipc/procarray.c my-aproach2/src/backend/storage/ipc/procarray.c
*** my-cvs/src/backend/storage/ipc/procarray.c    Sat Jun 18 00:32:46 2005
--- my-aproach2/src/backend/storage/ipc/procarray.c    Tue Jun 28 06:07:50 2005
***************
*** 734,739 ****
--- 734,790 ----
  }


+ /*
+  * 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 && proc->databaseId == databaseid)
+             count++;
+     }
+
+     LWLockRelease(ProcArrayLock);
+
+     return count;
+ }
+
+ /*
+  * CountUserBackends --- count backends that are used by specified user
+  */
+ int
+ CountUserBackends(AclId userid)
+ {
+     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 && proc->userId == userid)
+             count++;
+     }
+
+     LWLockRelease(ProcArrayLock);
+
+     return count;
+ }
+
+
  #define XidCacheRemove(i) \
      do { \
          MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \
diff -Nacr my-cvs/src/backend/storage/lmgr/proc.c my-aproach2/src/backend/storage/lmgr/proc.c
*** my-cvs/src/backend/storage/lmgr/proc.c    Sat Jun 18 00:32:46 2005
--- my-aproach2/src/backend/storage/lmgr/proc.c    Tue Jun 28 06:39:46 2005
***************
*** 254,259 ****
--- 254,260 ----
      MyProc->xmin = InvalidTransactionId;
      MyProc->pid = MyProcPid;
      MyProc->databaseId = MyDatabaseId;
+     MyProc->userId = GetSessionUserId();
      MyProc->lwWaiting = false;
      MyProc->lwExclusive = false;
      MyProc->lwWaitLink = NULL;
diff -Nacr my-cvs/src/backend/tcop/utility.c my-aproach2/src/backend/tcop/utility.c
*** my-cvs/src/backend/tcop/utility.c    Wed Jun 22 23:14:30 2005
--- my-aproach2/src/backens/tcop/utility.c    Tue Jun 28 06:07:50 2005
***************
*** 276,281 ****
--- 276,282 ----

      switch (nodeTag(parsetree))
      {
+         case T_AlterDatabaseStmt:
          case T_AlterDatabaseSetStmt:
          case T_AlterDomainStmt:
          case T_AlterFunctionStmt:
***************
*** 786,791 ****
--- 787,796 ----

          case T_CreatedbStmt:
              createdb((CreatedbStmt *) parsetree);
+             break;
+
+         case T_AlterDatabaseStmt:
+             AlterDatabase((AlterDatabaseStmt *) parsetree);
              break;

          case T_AlterDatabaseSetStmt:
diff -Nacr my-cvs/src/backend/utils/init/miscinit.c my-aproach2/src/backend/utils/init/miscinit.c
*** my-cvs/src/backend/utils/init/miscinit.c    Mon Jun 20 04:17:30 2005
--- my-aproach2/src/backend/utils/init/miscinit.c    Tue Jun 28 06:41:40 2005
***************
*** 315,320 ****
--- 315,321 ----
      Datum        datum;
      bool        isnull;
      AclId        usesysid;
+     Form_pg_shadow    userform;

      /*
       * Don't do scans if we're bootstrapping, none of the system catalogs
***************
*** 333,344 ****
                  (errcode(ERRCODE_UNDEFINED_OBJECT),
                   errmsg("user \"%s\" does not exist", username)));

!     usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;

      AuthenticatedUserId = usesysid;
!     AuthenticatedUserIsSuperuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;

      SetSessionUserId(usesysid); /* sets CurrentUserId too */

      /* Record username and superuser status as GUC settings too */
      SetConfigOption("session_authorization", username,
--- 334,358 ----
                  (errcode(ERRCODE_UNDEFINED_OBJECT),
                   errmsg("user \"%s\" does not exist", username)));

!     userform = ((Form_pg_shadow) GETSTRUCT(userTup));
!     usesysid = userform->usesysid;

      AuthenticatedUserId = usesysid;
!     AuthenticatedUserIsSuperuser = userform->usesuper;

      SetSessionUserId(usesysid); /* sets CurrentUserId too */
+
+     /*
+      * Check connection limit for user
+      */
+     if (userform->usemaxconn > 0 && !AuthenticatedUserIsSuperuser &&
+             CountUserBackends(AuthenticatedUserId) > userform->usemaxconn)
+     {
+         ereport(FATAL,
+             (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+          errmsg("sorry, too many clients already for user \"%s\"",
+             username)));
+     }

      /* Record username and superuser status as GUC settings too */
      SetConfigOption("session_authorization", username,
diff -Nacr my-cvs/src/backend/utils/init/postinit.c my-aproach2/src/backend/utils/init/postinit.c
*** my-cvs/src/backend/utils/init/postinit.c    Fri Jun 24 03:06:26 2005
--- my-aproach2/src/backend/utils/init/postinit.c    Tue Jun 28 10:11:10 2005
***************
*** 50,55 ****
--- 50,56 ----
  static void InitCommunication(void);
  static void ShutdownPostgres(int code, Datum arg);
  static bool ThereIsAtLeastOneUser(void);
+ static bool FindMyUser(const char *name, AclId *user_id);


  /*** InitPostgres support ***/
***************
*** 100,105 ****
--- 101,137 ----
  }

  /*
+  * Get user id from flatfiles
+  *
+  * We need this because we need to know userid before
+  * InitProcess() is called
+  */
+ static bool
+ FindMyUser(const char *name, AclId *user_id)
+ {
+     List      **line;
+     ListCell   *token;
+     char        thisname[NAMEDATALEN];
+
+     if ((line = get_user_line(name)) == NULL)
+         ereport(FATAL,
+                 (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, /* ? */
+                  errmsg("could not find user \"%s\": %m", name)));
+
+     /* Skip over username */
+     token = list_head(*line);
+     if (token)
+         token = lnext(token);
+     if (token)
+     {
+         *user_id = atoi((char*)lfirst(token));
+         return true;
+     }
+
+     return false;
+ }
+
+ /*
   * ReverifyMyDatabase -- recheck info obtained by FindMyDatabase
   *
   * Since FindMyDatabase cannot lock pg_database, the information it read
***************
*** 165,181 ****
                          name, MyDatabaseId)));
      }

-     /*
-      * 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;")
-      */
      dbform = (Form_pg_database) GETSTRUCT(tup);
!     if (IsUnderPostmaster && !dbform->datallowconn)
!         ereport(FATAL,
!                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!          errmsg("database \"%s\" is not currently accepting connections",
                  name)));

      /*
       * OK, we're golden.  Next to-do item is to save the encoding
--- 197,231 ----
                          name, MyDatabaseId)));
      }

      dbform = (Form_pg_database) GETSTRUCT(tup);
!     if (IsUnderPostmaster)
!     {
!         /*
!          * 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;")
!          */
!         if (!dbform->datallowconn)
!         {
!             ereport(FATAL,
!                     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!              errmsg("database \"%s\" is not currently accepting connections",
!                     name)));
!         }
!
!         /*
!          * Here we check cxonenction limit for this database
!          */
!         if (dbform->datmaxconn > 0 && !superuser() &&
!                 CountDBBackends(MyDatabaseId) > dbform->datmaxconn)
!         {
!             ereport(FATAL,
!                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
!              errmsg("sorry, too many clients already for database \"%s\"",
                  name)));
+         }
+     }
+

      /*
       * OK, we're golden.  Next to-do item is to save the encoding
***************
*** 350,355 ****
--- 400,424 ----
       * Code after this point assumes we are in the proper directory!
       */

+     /*
+      * We need to know userid in InitProcess() so we have read it from
+      * flatfile, real user inicialization is done later
+      */
+     if (IsUnderPostmaster)
+     {
+         AclId userid;
+
+         if (!FindMyUser(username, &userid))
+             ereport(FATAL,
+                     (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, /* ? */
+                      errmsg("user \"%s\" does not exist",
+                             username)));
+
+         SetSessionUserId(userid);
+     }
+     else
+         SetSessionUserId(BOOTSTRAP_USESYSID);
+
      /*
       * Set up my per-backend PGPROC struct in shared memory.    (We need
       * to know MyDatabaseId before we can do this, since it's entered into
diff -Nacr my-cvs/src/include/catalog/pg_database.h my-aproach2/src/include/catalog/pg_database.h
*** my-cvs/src/include/catalog/pg_database.h    Thu Apr 14 03:38:20 2005
--- my-aproach2/src/include/catalog/pg_database.h    Tue Jun 28 06:07:50 2005
***************
*** 40,45 ****
--- 40,46 ----
      int4        encoding;        /* character encoding */
      bool        datistemplate;    /* allowed as CREATE DATABASE template? */
      bool        datallowconn;    /* new connections allowed? */
+     int4        datmaxconn;        /* maximum connections allowed */
      Oid            datlastsysoid;    /* highest OID to consider a system OID */
      TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
      TransactionId datfrozenxid; /* all XIDs before this are frozen */
***************
*** 59,78 ****
   *        compiler constants for pg_database
   * ----------------
   */
! #define Natts_pg_database                11
  #define Anum_pg_database_datname        1
  #define Anum_pg_database_datdba            2
  #define Anum_pg_database_encoding        3
  #define Anum_pg_database_datistemplate    4
  #define Anum_pg_database_datallowconn    5
! #define Anum_pg_database_datlastsysoid    6
! #define Anum_pg_database_datvacuumxid    7
! #define Anum_pg_database_datfrozenxid    8
! #define Anum_pg_database_dattablespace    9
! #define Anum_pg_database_datconfig        10
! #define Anum_pg_database_datacl            11

! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 1663 _null_ _null_ ));
  DESCR("Default template database");
  #define TemplateDbOid            1

--- 60,80 ----
   *        compiler constants for pg_database
   * ----------------
   */
! #define Natts_pg_database                12
  #define Anum_pg_database_datname        1
  #define Anum_pg_database_datdba            2
  #define Anum_pg_database_encoding        3
  #define Anum_pg_database_datistemplate    4
  #define Anum_pg_database_datallowconn    5
! #define Anum_pg_database_datmaxconn        6
! #define Anum_pg_database_datlastsysoid    7
! #define Anum_pg_database_datvacuumxid    8
! #define Anum_pg_database_datfrozenxid    9
! #define Anum_pg_database_dattablespace    10
! #define Anum_pg_database_datconfig        11
! #define Anum_pg_database_datacl            12

! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 0 1663 _null_ _null_ ));
  DESCR("Default template database");
  #define TemplateDbOid            1

diff -Nacr my-cvs/src/include/catalog/pg_shadow.h my-aproach2/src/include/catalog/pg_shadow.h
*** my-cvs/src/include/catalog/pg_shadow.h    Thu Apr 14 03:38:22 2005
--- my-aproach2/src/include/catalog/pg_shadow.h    Tue Jun 28 06:07:50 2005
***************
*** 36,41 ****
--- 36,42 ----
      bool        usecreatedb;
      bool        usesuper;        /* read this field via superuser() only */
      bool        usecatupd;
+     int4        usemaxconn;        /* maximum connections allowed */

      /* remaining fields may be null; use heap_getattr to read them! */
      text        passwd;
***************
*** 54,68 ****
   *        compiler constants for pg_shadow
   * ----------------
   */
! #define Natts_pg_shadow                    8
  #define Anum_pg_shadow_usename            1
  #define Anum_pg_shadow_usesysid            2
  #define Anum_pg_shadow_usecreatedb        3
  #define Anum_pg_shadow_usesuper            4
  #define Anum_pg_shadow_usecatupd        5
! #define Anum_pg_shadow_passwd            6
! #define Anum_pg_shadow_valuntil            7
! #define Anum_pg_shadow_useconfig        8

  /* ----------------
   *        initial contents of pg_shadow
--- 55,70 ----
   *        compiler constants for pg_shadow
   * ----------------
   */
! #define Natts_pg_shadow                    9
  #define Anum_pg_shadow_usename            1
  #define Anum_pg_shadow_usesysid            2
  #define Anum_pg_shadow_usecreatedb        3
  #define Anum_pg_shadow_usesuper            4
  #define Anum_pg_shadow_usecatupd        5
! #define Anum_pg_shadow_usemaxconn        6
! #define Anum_pg_shadow_passwd            7
! #define Anum_pg_shadow_valuntil            8
! #define Anum_pg_shadow_useconfig        9

  /* ----------------
   *        initial contents of pg_shadow
***************
*** 71,77 ****
   * user choices.
   * ----------------
   */
! DATA(insert ( "POSTGRES" PGUID t t t _null_ _null_ _null_ ));

  #define BOOTSTRAP_USESYSID 1

--- 73,79 ----
   * user choices.
   * ----------------
   */
! DATA(insert ( "POSTGRES" PGUID t t t 0 _null_ _null_ _null_ ));

  #define BOOTSTRAP_USESYSID 1

diff -Nacr my-cvs/src/include/commands/dbcommands.h my-aproach2/src/include/commands/dbcommands.h
*** my-cvs/src/include/commands/dbcommands.h    Mon Jun 06 19:01:26 2005
--- my-aproach2/src/include/commands/dbcommands.h    Tue Jun 28 06:07:50 2005
***************
*** 64,69 ****
--- 64,70 ----
  extern void createdb(const CreatedbStmt *stmt);
  extern void dropdb(const char *dbname);
  extern void RenameDatabase(const char *oldname, const char *newname);
+ extern void AlterDatabase(AlterDatabaseStmt *stmt);
  extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
  extern void AlterDatabaseOwner(const char *dbname, AclId newOwnerSysId);

diff -Nacr my-cvs/src/include/nodes/nodes.h my-aproach2/src/include/nodes/nodes.h
*** my-cvs/src/include/nodes/nodes.h    Mon Jun 27 00:05:42 2005
--- my-aproach2/src/include/nodes/nodes.h    Tue Jun 28 06:07:50 2005
***************
*** 272,277 ****
--- 272,278 ----
      T_ReindexStmt,
      T_CheckPointStmt,
      T_CreateSchemaStmt,
+     T_AlterDatabaseStmt,
      T_AlterDatabaseSetStmt,
      T_AlterUserSetStmt,
      T_CreateConversionStmt,
diff -Nacr my-cvs/src/include/nodes/parsenodes.h my-aproach2/src/include/nodes/parsenodes.h
*** my-cvs/src/include/nodes/parsenodes.h    Wed Jun 22 23:14:32 2005
--- my-aproach2/src/include/nodes/parsenodes.h    Tue Jun 28 06:07:50 2005
***************
*** 1620,1625 ****
--- 1620,1632 ----
   *    Alter Database
   * ----------------------
   */
+ typedef struct AlterDatabaseStmt
+ {
+     NodeTag        type;
+     char       *dbname;            /* name of database to create */
+     List       *options;        /* List of DefElem nodes */
+ } AlterDatabaseStmt;
+
  typedef struct AlterDatabaseSetStmt
  {
      NodeTag        type;
diff -Nacr my-cvs/src/include/storage/proc.h my-aproach2/src/include/storage/proc.h
*** my-cvs/src/include/storage/proc.h    Sat Jun 18 00:32:50 2005
--- my-aproach2/src/include/storage/proc.h    Tue Jun 28 06:07:50 2005
***************
*** 71,76 ****
--- 71,77 ----

      int            pid;            /* This backend's process id, or 0 */
      Oid            databaseId;        /* OID of database this backend is using */
+     AclId        userId;            /* user connected to this backend */

      /* Info about LWLock the process is currently waiting for, if any. */
      bool        lwWaiting;        /* true if waiting for an LW lock */
diff -Nacr my-cvs/src/include/storage/procarray.h my-aproach2/src/include/storage/procarray.h
*** my-cvs/src/include/storage/procarray.h    Sat Jun 18 00:32:50 2005
--- my-aproach2/src/include/storage/procarray.h    Tue Jun 28 06:07:50 2005
***************
*** 31,36 ****
--- 31,38 ----
  extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);

  extern int    CountActiveBackends(void);
+ extern int    CountDBBackends(Oid databaseid);
+ extern int    CountUserBackends(AclId userid);

  extern void XidCacheRemoveRunningXids(TransactionId xid,
                            int nxids, TransactionId *xids);
diff -Nacr my-cvs/src/tools/pgindent/pgindent my-aproach2/src/tools/pgindent/pgindent
*** my-cvs/src/tools/pgindent/pgindent    Thu Oct 07 16:15:50 2004
--- my-aproach2/src/tools/pgindent/pgindent    Tue Jun 28 06:07:50 2005
***************
*** 175,180 ****
--- 175,181 ----
  -TAllocSetContext \
  -TAllocateDesc \
  -TAllocateDescKind \
+ -TAlterDatabaseStmt \
  -TAlterDatabaseSetStmt \
  -TAlterDomainStmt \
  -TAlterGroupStmt \

Re: per user/database connections limit again

From
Bruce Momjian
Date:
We will need these:

> Patch includes only changes to backend, I will make pg_dump, ecpg and
> documentation patches once this is completed and accepted by team.

Your patch has been added to the PostgreSQL unapplied patches list at:

    http://momjian.postgresql.org/cgi-bin/pgpatches

It will be applied as soon as one of the PostgreSQL committers reviews
and approves it.

---------------------------------------------------------------------------


Petr Jelinek wrote:
> Hi,
>
> I attached  second try of per-database and per-user connection limit for
> your review.
>
> This time I am using information stored in ProcArray to get number of
> connections - I modified PGPROC struct to also include userid.
>
> Limits for user and database are stored in catalog tables. This aproach
> led to implementation of "universal" ALTER DATABASE query (I followed
> ALTER USER ad ALTER DATABASE ... RENAME implementatons). So queries for
> setting maximum connections look like this: CREATE|ALTER DATABASE|USER
> name MAX CONNECTIONS = 20;
> Maximum connections defaults to zero which means unlimited (limited by
> global maximum only) and isn't enforced for superusers.
>
> The actual check for maximum conenctions is done in ReverifyMyDatabase
> for database and InitializeSessionUser for user because we don't have
> information from system catalog before so we don't know how many
> connections are allowed.
>
> Patch includes only changes to backend, I will make pg_dump, ecpg and
> documentation patches once this is completed and accepted by team.
>
> Diff is made against cvs from today morning GMT (apply with -p1 if you
> want to test it) - cvs is down now so I can't make diff against repository.
>
> --
> Regards
> Petr Jelinek (PJMODOS)
>
>

> diff -Nacr my-cvs/src/backend/commands/dbcommands.c my-aproach2/src/backend/commands/dbcommands.c
> *** my-cvs/src/backend/commands/dbcommands.c    Sun Jun 26 00:47:30 2005
> --- my-aproach2/src/backend/commands/dbcommands.c    Tue Jun 28 11:26:08 2005
> ***************
> *** 53,60 ****
>
>   /* non-export function prototypes */
>   static bool get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
> !             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
> !             Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace);
>   static bool have_createdb_privilege(void);
> --- 53,60 ----
>
>   /* non-export function prototypes */
>   static bool get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
> !             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
> !             bool *dbAllowConnP,    Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace);
>   static bool have_createdb_privilege(void);
> ***************
> *** 74,79 ****
> --- 74,80 ----
>       int            src_encoding;
>       bool        src_istemplate;
>       bool        src_allowconn;
> +     int            src_maxconn;
>       Oid            src_lastsysoid;
>       TransactionId src_vacuumxid;
>       TransactionId src_frozenxid;
> ***************
> *** 91,100 ****
> --- 92,103 ----
>       DefElem    *downer = NULL;
>       DefElem    *dtemplate = NULL;
>       DefElem    *dencoding = NULL;
> +     DefElem    *dmaxconn = NULL;
>       char       *dbname = stmt->dbname;
>       char       *dbowner = NULL;
>       const char *dbtemplate = NULL;
>       int            encoding = -1;
> +     int            dbmaxconn = -1;
>
>   #ifndef WIN32
>       char        buf[2 * MAXPGPATH + 100];
> ***************
> *** 140,145 ****
> --- 143,156 ----
>                            errmsg("conflicting or redundant options")));
>               dencoding = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "location") == 0)
>           {
>               ereport(WARNING,
> ***************
> *** 185,190 ****
> --- 196,203 ----
>               elog(ERROR, "unrecognized node type: %d",
>                    nodeTag(dencoding->arg));
>       }
> +     if (dmaxconn && dmaxconn->arg)
> +         dbmaxconn = intVal(dmaxconn->arg);
>
>       /* obtain sysid of proposed owner */
>       if (dbowner)
> ***************
> *** 218,224 ****
>        * idea, so accept possibility of race to create.  We will check again
>        * after we grab the exclusive lock.
>        */
> !     if (get_db_info(dbname, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_DUPLICATE_DATABASE),
> --- 231,237 ----
>        * idea, so accept possibility of race to create.  We will check again
>        * after we grab the exclusive lock.
>        */
> !     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_DUPLICATE_DATABASE),
> ***************
> *** 231,238 ****
>           dbtemplate = "template1";        /* Default template database name */
>
>       if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
> !                      &src_istemplate, &src_allowconn, &src_lastsysoid,
> !                      &src_vacuumxid, &src_frozenxid, &src_deftablespace))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
>            errmsg("template database \"%s\" does not exist", dbtemplate)));
> --- 244,252 ----
>           dbtemplate = "template1";        /* Default template database name */
>
>       if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
> !                      &src_maxconn, &src_istemplate, &src_allowconn,
> !                      &src_lastsysoid, &src_vacuumxid, &src_frozenxid,
> !                      &src_deftablespace))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
>            errmsg("template database \"%s\" does not exist", dbtemplate)));
> ***************
> *** 266,271 ****
> --- 280,289 ----
>       if (encoding < 0)
>           encoding = src_encoding;
>
> +     /* If dbmaxconn is defaulted, use source's dbmaxconn */
> +     if (dbmaxconn < 0)
> +         dbmaxconn = src_maxconn;
> +
>       /* Some encodings are client only */
>       if (!PG_VALID_BE_ENCODING(encoding))
>           ereport(ERROR,
> ***************
> *** 461,467 ****
>       pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
>
>       /* Check to see if someone else created same DB name meanwhile. */
> !     if (get_db_info(dbname, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>       {
>           /* Don't hold lock while doing recursive remove */
> --- 479,485 ----
>       pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
>
>       /* Check to see if someone else created same DB name meanwhile. */
> !     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>       {
>           /* Don't hold lock while doing recursive remove */
> ***************
> *** 487,492 ****
> --- 505,511 ----
>       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_datmaxconn - 1] = Int32GetDatum(dbmaxconn);
>       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);
> ***************
> *** 588,594 ****
>        */
>       pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
>
> !     if (!get_db_info(dbname, &db_id, &db_owner, NULL,
>                        &db_istemplate, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
> --- 607,613 ----
>        */
>       pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
>
> !     if (!get_db_info(dbname, &db_id, &db_owner, NULL, NULL,
>                        &db_istemplate, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
> ***************
> *** 784,789 ****
> --- 803,894 ----
>
>
>   /*
> +  * ALTER DATABASE name ...
> +  */
> + void
> + AlterDatabase(AlterDatabaseStmt *stmt)
> + {
> +     Datum        new_record[Natts_pg_database];
> +     char        new_record_nulls[Natts_pg_database];
> +     char        new_record_repl[Natts_pg_database];
> +     Relation    rel;
> +     HeapTuple    tuple,
> +                 newtuple;
> +     ScanKeyData scankey;
> +     SysScanDesc scan;
> +     ListCell   *option;
> +     int            maxconn = -1;    /* Maximum connections allowed */
> +
> +     DefElem    *dmaxconn = NULL;
> +
> +     /* Extract options from the statement node tree */
> +     foreach(option, stmt->options)
> +     {
> +         DefElem    *defel = (DefElem *) lfirst(option);
> +
> +         if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
> +     }
> +
> +     if (dmaxconn)
> +         maxconn = intVal(dmaxconn->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 (!(superuser()
> +         || ((Form_pg_database) GETSTRUCT(tuple))->datdba == 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 (maxconn >= 0)
> +     {
> +         new_record[Anum_pg_database_datmaxconn - 1] = Int32GetDatum(maxconn);
> +         new_record_repl[Anum_pg_database_datmaxconn - 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);
> + }
> +
> +
> + /*
>    * ALTER DATABASE name SET ...
>    */
>   void
> ***************
> *** 973,980 ****
>
>   static bool
>   get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
> !             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
> !             Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace)
>   {
> --- 1078,1085 ----
>
>   static bool
>   get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
> !             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
> !             bool *dbAllowConnP,    Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace)
>   {
> ***************
> *** 1019,1024 ****
> --- 1124,1132 ----
>           /* allowing connections? */
>           if (dbAllowConnP)
>               *dbAllowConnP = dbform->datallowconn;
> +         /* maximum connections */
> +         if (dbMaxConnP)
> +             *dbMaxConnP = dbform->datmaxconn;
>           /* last system OID used in database */
>           if (dbLastSysOidP)
>               *dbLastSysOidP = dbform->datlastsysoid;
> diff -Nacr my-cvs/src/backend/commands/user.c my-aproach2/src/backend/commands/user.c
> *** my-cvs/src/backend/commands/user.c    Thu Apr 14 22:03:24 2005
> --- my-aproach2/src/backend/commands/user.c    Tue Jun 28 11:26:18 2005
> ***************
> *** 64,69 ****
> --- 64,70 ----
>       int            sysid = 0;        /* PgSQL system id (valid if havesysid) */
>       bool        createdb = false;        /* Can the user create databases? */
>       bool        createuser = false;        /* Can this user create users? */
> +     int            maxconn = false;        /* maximum connections allowed */
>       List       *groupElts = NIL;    /* The groups the user is a member of */
>       char       *validUntil = NULL;        /* The time the login is valid
>                                            * until */
> ***************
> *** 73,78 ****
> --- 74,80 ----
>       DefElem    *dcreateuser = NULL;
>       DefElem    *dgroupElts = NULL;
>       DefElem    *dvalidUntil = NULL;
> +     DefElem    *dmaxconn = NULL;
>
>       /* Extract options from the statement node tree */
>       foreach(option, stmt->options)
> ***************
> *** 117,122 ****
> --- 119,132 ----
>                            errmsg("conflicting or redundant options")));
>               dcreateuser = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "groupElts") == 0)
>           {
>               if (dgroupElts)
> ***************
> *** 142,147 ****
> --- 152,165 ----
>           createdb = intVal(dcreatedb->arg) != 0;
>       if (dcreateuser)
>           createuser = intVal(dcreateuser->arg) != 0;
> +     if (dmaxconn)
> +     {
> +         maxconn = intVal(dmaxconn->arg);
> +         if (maxconn < 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS must not be negative")));
> +     }
>       if (dsysid)
>       {
>           sysid = intVal(dsysid->arg);
> ***************
> *** 233,238 ****
> --- 251,257 ----
>       new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser);
>       /* superuser gets catupd right by default */
>       new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser);
> +     new_record[Anum_pg_shadow_usemaxconn - 1] = Int32GetDatum(maxconn);
>
>       if (password)
>       {
> ***************
> *** 317,328 ****
> --- 336,349 ----
>       char        encrypted_password[MD5_PASSWD_LEN + 1];
>       int            createdb = -1;    /* Can the user create databases? */
>       int            createuser = -1;    /* Can this user create users? */
> +     int            maxconn = -1;    /* Maximum connections allowed */
>       char       *validUntil = NULL;        /* The time the login is valid
>                                            * until */
>       DefElem    *dpassword = NULL;
>       DefElem    *dcreatedb = NULL;
>       DefElem    *dcreateuser = NULL;
>       DefElem    *dvalidUntil = NULL;
> +     DefElem    *dmaxconn = NULL;
>
>       /* Extract options from the statement node tree */
>       foreach(option, stmt->options)
> ***************
> *** 359,364 ****
> --- 380,393 ----
>                            errmsg("conflicting or redundant options")));
>               dcreateuser = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "validUntil") == 0)
>           {
>               if (dvalidUntil)
> ***************
> *** 376,381 ****
> --- 405,412 ----
>           createdb = intVal(dcreatedb->arg);
>       if (dcreateuser)
>           createuser = intVal(dcreateuser->arg);
> +     if (dmaxconn)
> +         maxconn = intVal(dmaxconn->arg);
>       if (dvalidUntil)
>           validUntil = strVal(dvalidUntil->arg);
>       if (dpassword)
> ***************
> *** 427,432 ****
> --- 458,469 ----
>       {
>           new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0);
>           new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r';
> +     }
> +
> +     if (maxconn >= 0)
> +     {
> +         new_record[Anum_pg_shadow_usemaxconn - 1] = Int32GetDatum(maxconn);
> +         new_record_repl[Anum_pg_shadow_usemaxconn - 1] = 'r';
>       }
>
>       /*
> diff -Nacr my-cvs/src/backend/nodes/copyfuncs.c my-aproach2/src/backend/nodes/copyfuncs.c
> *** my-cvs/src/backend/nodes/copyfuncs.c    Mon Jun 27 00:05:38 2005
> --- my-aproach2/src/backend/nodes/copyfuncs.c    Tue Jun 28 06:07:50 2005
> ***************
> *** 2191,2196 ****
> --- 2191,2207 ----
>       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)
>   {
> diff -Nacr my-cvs/src/backend/nodes/equalfuncs.c my-aproach2/src/backend/nodes/equalfuncs.c
> *** my-cvs/src/backend/nodes/equalfuncs.c    Mon Jun 27 00:05:38 2005
> --- my-aproach2/src/backend/nodes/equalfuncs.c    Tue Jun 28 06:07:50 2005
> ***************
> *** 1141,1146 ****
> --- 1141,1155 ----
>   }
>
>   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);
> diff -Nacr my-cvs/src/backend/parser/gram.y my-aproach2/src/backend/parser/gram.y
> *** my-cvs/src/backend/parser/gram.y    Mon Jun 27 00:05:38 2005
> --- my-aproach2/src/backend/parser/gram.y    Tue Jun 28 11:26:30 2005
> ***************
> *** 131,139 ****
>   }
>
>   %type <node>    stmt schema_stmt
> !         AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
> !         AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
> !         AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
>           ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
>           CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
>           CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
> --- 131,139 ----
>   }
>
>   %type <node>    stmt schema_stmt
> !         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
> !         AlterOwnerStmt AlterSeqStmt AlterTableStmt AlterUserStmt
> !         AlterUserSetStmt AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
>           ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
>           CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
>           CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
> ***************
> *** 164,171 ****
>
>   %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 <ival>    opt_lock lock_type cast_context
>   %type <boolean>    opt_force opt_or_replace
> --- 164,173 ----
>
>   %type <dbehavior>    opt_drop_behavior
>
> ! %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
> ***************
> *** 346,352 ****
>       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
>       CREATEUSER CROSS CSV CURRENT_DATE CURRENT_TIME
>       CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
>
> --- 348,354 ----
>       CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P
>       CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
>       CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
> !     COMMITTED CONNECTIONS CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
>       CREATEUSER CROSS CSV CURRENT_DATE CURRENT_TIME
>       CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
>
> ***************
> *** 377,383 ****
>       LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
>       LOCK_P
>
> !     MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
>
>       NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
>       NOCREATEUSER NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P
> --- 379,385 ----
>       LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
>       LOCK_P
>
> !     MATCH MAX MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
>
>       NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
>       NOCREATEUSER NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P
> ***************
> *** 490,496 ****
>           ;
>
>   stmt :
> !             AlterDatabaseSetStmt
>               | AlterDomainStmt
>               | AlterFunctionStmt
>               | AlterGroupStmt
> --- 492,499 ----
>           ;
>
>   stmt :
> !             AlterDatabaseStmt
> !             | AlterDatabaseSetStmt
>               | AlterDomainStmt
>               | AlterFunctionStmt
>               | AlterGroupStmt
> ***************
> *** 688,693 ****
> --- 691,700 ----
>                   {
>                       $$ = makeDefElem("createuser", (Node *)makeInteger(FALSE));
>                   }
> +             | MAX CONNECTIONS Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($3));
> +                 }
>               | IN_P GROUP_P user_list
>                   {
>                       $$ = makeDefElem("groupElts", (Node *)$3);
> ***************
> *** 4294,4299 ****
> --- 4301,4310 ----
>                   {
>                       $$ = makeDefElem("encoding", NULL);
>                   }
> +             | MAX CONNECTIONS opt_equal Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
> +                 }
>               | OWNER opt_equal name
>                   {
>                       $$ = makeDefElem("owner", (Node *)makeString($3));
> ***************
> *** 4320,4325 ****
> --- 4331,4346 ----
>    *
>    *****************************************************************************/
>
> + 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
>                   {
> ***************
> *** 4340,4345 ****
> --- 4361,4379 ----
>           ;
>
>
> + alterdb_opt_list:
> +             alterdb_opt_list alterdb_opt_item        { $$ = lappend($1, $2); }
> +             | /* EMPTY */                            { $$ = NIL; }
> +         ;
> +
> + alterdb_opt_item:
> +             MAX CONNECTIONS opt_equal Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
> +                 }
> +         ;
> +
> +
>   /*****************************************************************************
>    *
>    *        DROP DATABASE
> ***************
> *** 7770,7775 ****
> --- 7804,7810 ----
>               | COMMENT
>               | COMMIT
>               | COMMITTED
> +             | CONNECTIONS
>               | CONSTRAINTS
>               | CONVERSION_P
>               | COPY
> ***************
> *** 7835,7840 ****
> --- 7870,7876 ----
>               | LOCATION
>               | LOCK_P
>               | MATCH
> +             | MAX
>               | MAXVALUE
>               | MINUTE_P
>               | MINVALUE
> diff -Nacr my-cvs/src/backend/parser/keywords.c my-aproach2/src/backend/parser/keywords.c
> *** my-cvs/src/backend/parser/keywords.c    Mon Jun 27 00:05:40 2005
> --- my-aproach2/src/backend/parser/keywords.c    Tue Jun 28 06:07:50 2005
> ***************
> *** 82,87 ****
> --- 82,88 ----
>       {"comment", COMMENT},
>       {"commit", COMMIT},
>       {"committed", COMMITTED},
> +     {"connections", CONNECTIONS},
>       {"constraint", CONSTRAINT},
>       {"constraints", CONSTRAINTS},
>       {"conversion", CONVERSION_P},
> ***************
> *** 198,203 ****
> --- 199,205 ----
>       {"location", LOCATION},
>       {"lock", LOCK_P},
>       {"match", MATCH},
> +     {"max", MAX},
>       {"maxvalue", MAXVALUE},
>       {"minute", MINUTE_P},
>       {"minvalue", MINVALUE},
> diff -Nacr my-cvs/src/backend/storage/ipc/procarray.c my-aproach2/src/backend/storage/ipc/procarray.c
> *** my-cvs/src/backend/storage/ipc/procarray.c    Sat Jun 18 00:32:46 2005
> --- my-aproach2/src/backend/storage/ipc/procarray.c    Tue Jun 28 06:07:50 2005
> ***************
> *** 734,739 ****
> --- 734,790 ----
>   }
>
>
> + /*
> +  * 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 && proc->databaseId == databaseid)
> +             count++;
> +     }
> +
> +     LWLockRelease(ProcArrayLock);
> +
> +     return count;
> + }
> +
> + /*
> +  * CountUserBackends --- count backends that are used by specified user
> +  */
> + int
> + CountUserBackends(AclId userid)
> + {
> +     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 && proc->userId == userid)
> +             count++;
> +     }
> +
> +     LWLockRelease(ProcArrayLock);
> +
> +     return count;
> + }
> +
> +
>   #define XidCacheRemove(i) \
>       do { \
>           MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \
> diff -Nacr my-cvs/src/backend/storage/lmgr/proc.c my-aproach2/src/backend/storage/lmgr/proc.c
> *** my-cvs/src/backend/storage/lmgr/proc.c    Sat Jun 18 00:32:46 2005
> --- my-aproach2/src/backend/storage/lmgr/proc.c    Tue Jun 28 06:39:46 2005
> ***************
> *** 254,259 ****
> --- 254,260 ----
>       MyProc->xmin = InvalidTransactionId;
>       MyProc->pid = MyProcPid;
>       MyProc->databaseId = MyDatabaseId;
> +     MyProc->userId = GetSessionUserId();
>       MyProc->lwWaiting = false;
>       MyProc->lwExclusive = false;
>       MyProc->lwWaitLink = NULL;
> diff -Nacr my-cvs/src/backend/tcop/utility.c my-aproach2/src/backend/tcop/utility.c
> *** my-cvs/src/backend/tcop/utility.c    Wed Jun 22 23:14:30 2005
> --- my-aproach2/src/backens/tcop/utility.c    Tue Jun 28 06:07:50 2005
> ***************
> *** 276,281 ****
> --- 276,282 ----
>
>       switch (nodeTag(parsetree))
>       {
> +         case T_AlterDatabaseStmt:
>           case T_AlterDatabaseSetStmt:
>           case T_AlterDomainStmt:
>           case T_AlterFunctionStmt:
> ***************
> *** 786,791 ****
> --- 787,796 ----
>
>           case T_CreatedbStmt:
>               createdb((CreatedbStmt *) parsetree);
> +             break;
> +
> +         case T_AlterDatabaseStmt:
> +             AlterDatabase((AlterDatabaseStmt *) parsetree);
>               break;
>
>           case T_AlterDatabaseSetStmt:
> diff -Nacr my-cvs/src/backend/utils/init/miscinit.c my-aproach2/src/backend/utils/init/miscinit.c
> *** my-cvs/src/backend/utils/init/miscinit.c    Mon Jun 20 04:17:30 2005
> --- my-aproach2/src/backend/utils/init/miscinit.c    Tue Jun 28 06:41:40 2005
> ***************
> *** 315,320 ****
> --- 315,321 ----
>       Datum        datum;
>       bool        isnull;
>       AclId        usesysid;
> +     Form_pg_shadow    userform;
>
>       /*
>        * Don't do scans if we're bootstrapping, none of the system catalogs
> ***************
> *** 333,344 ****
>                   (errcode(ERRCODE_UNDEFINED_OBJECT),
>                    errmsg("user \"%s\" does not exist", username)));
>
> !     usesysid = ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
>
>       AuthenticatedUserId = usesysid;
> !     AuthenticatedUserIsSuperuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;
>
>       SetSessionUserId(usesysid); /* sets CurrentUserId too */
>
>       /* Record username and superuser status as GUC settings too */
>       SetConfigOption("session_authorization", username,
> --- 334,358 ----
>                   (errcode(ERRCODE_UNDEFINED_OBJECT),
>                    errmsg("user \"%s\" does not exist", username)));
>
> !     userform = ((Form_pg_shadow) GETSTRUCT(userTup));
> !     usesysid = userform->usesysid;
>
>       AuthenticatedUserId = usesysid;
> !     AuthenticatedUserIsSuperuser = userform->usesuper;
>
>       SetSessionUserId(usesysid); /* sets CurrentUserId too */
> +
> +     /*
> +      * Check connection limit for user
> +      */
> +     if (userform->usemaxconn > 0 && !AuthenticatedUserIsSuperuser &&
> +             CountUserBackends(AuthenticatedUserId) > userform->usemaxconn)
> +     {
> +         ereport(FATAL,
> +             (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
> +          errmsg("sorry, too many clients already for user \"%s\"",
> +             username)));
> +     }
>
>       /* Record username and superuser status as GUC settings too */
>       SetConfigOption("session_authorization", username,
> diff -Nacr my-cvs/src/backend/utils/init/postinit.c my-aproach2/src/backend/utils/init/postinit.c
> *** my-cvs/src/backend/utils/init/postinit.c    Fri Jun 24 03:06:26 2005
> --- my-aproach2/src/backend/utils/init/postinit.c    Tue Jun 28 10:11:10 2005
> ***************
> *** 50,55 ****
> --- 50,56 ----
>   static void InitCommunication(void);
>   static void ShutdownPostgres(int code, Datum arg);
>   static bool ThereIsAtLeastOneUser(void);
> + static bool FindMyUser(const char *name, AclId *user_id);
>
>
>   /*** InitPostgres support ***/
> ***************
> *** 100,105 ****
> --- 101,137 ----
>   }
>
>   /*
> +  * Get user id from flatfiles
> +  *
> +  * We need this because we need to know userid before
> +  * InitProcess() is called
> +  */
> + static bool
> + FindMyUser(const char *name, AclId *user_id)
> + {
> +     List      **line;
> +     ListCell   *token;
> +     char        thisname[NAMEDATALEN];
> +
> +     if ((line = get_user_line(name)) == NULL)
> +         ereport(FATAL,
> +                 (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, /* ? */
> +                  errmsg("could not find user \"%s\": %m", name)));
> +
> +     /* Skip over username */
> +     token = list_head(*line);
> +     if (token)
> +         token = lnext(token);
> +     if (token)
> +     {
> +         *user_id = atoi((char*)lfirst(token));
> +         return true;
> +     }
> +
> +     return false;
> + }
> +
> + /*
>    * ReverifyMyDatabase -- recheck info obtained by FindMyDatabase
>    *
>    * Since FindMyDatabase cannot lock pg_database, the information it read
> ***************
> *** 165,181 ****
>                           name, MyDatabaseId)));
>       }
>
> -     /*
> -      * 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;")
> -      */
>       dbform = (Form_pg_database) GETSTRUCT(tup);
> !     if (IsUnderPostmaster && !dbform->datallowconn)
> !         ereport(FATAL,
> !                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> !          errmsg("database \"%s\" is not currently accepting connections",
>                   name)));
>
>       /*
>        * OK, we're golden.  Next to-do item is to save the encoding
> --- 197,231 ----
>                           name, MyDatabaseId)));
>       }
>
>       dbform = (Form_pg_database) GETSTRUCT(tup);
> !     if (IsUnderPostmaster)
> !     {
> !         /*
> !          * 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;")
> !          */
> !         if (!dbform->datallowconn)
> !         {
> !             ereport(FATAL,
> !                     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> !              errmsg("database \"%s\" is not currently accepting connections",
> !                     name)));
> !         }
> !
> !         /*
> !          * Here we check cxonenction limit for this database
> !          */
> !         if (dbform->datmaxconn > 0 && !superuser() &&
> !                 CountDBBackends(MyDatabaseId) > dbform->datmaxconn)
> !         {
> !             ereport(FATAL,
> !                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
> !              errmsg("sorry, too many clients already for database \"%s\"",
>                   name)));
> +         }
> +     }
> +
>
>       /*
>        * OK, we're golden.  Next to-do item is to save the encoding
> ***************
> *** 350,355 ****
> --- 400,424 ----
>        * Code after this point assumes we are in the proper directory!
>        */
>
> +     /*
> +      * We need to know userid in InitProcess() so we have read it from
> +      * flatfile, real user inicialization is done later
> +      */
> +     if (IsUnderPostmaster)
> +     {
> +         AclId userid;
> +
> +         if (!FindMyUser(username, &userid))
> +             ereport(FATAL,
> +                     (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, /* ? */
> +                      errmsg("user \"%s\" does not exist",
> +                             username)));
> +
> +         SetSessionUserId(userid);
> +     }
> +     else
> +         SetSessionUserId(BOOTSTRAP_USESYSID);
> +
>       /*
>        * Set up my per-backend PGPROC struct in shared memory.    (We need
>        * to know MyDatabaseId before we can do this, since it's entered into
> diff -Nacr my-cvs/src/include/catalog/pg_database.h my-aproach2/src/include/catalog/pg_database.h
> *** my-cvs/src/include/catalog/pg_database.h    Thu Apr 14 03:38:20 2005
> --- my-aproach2/src/include/catalog/pg_database.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 40,45 ****
> --- 40,46 ----
>       int4        encoding;        /* character encoding */
>       bool        datistemplate;    /* allowed as CREATE DATABASE template? */
>       bool        datallowconn;    /* new connections allowed? */
> +     int4        datmaxconn;        /* maximum connections allowed */
>       Oid            datlastsysoid;    /* highest OID to consider a system OID */
>       TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
>       TransactionId datfrozenxid; /* all XIDs before this are frozen */
> ***************
> *** 59,78 ****
>    *        compiler constants for pg_database
>    * ----------------
>    */
> ! #define Natts_pg_database                11
>   #define Anum_pg_database_datname        1
>   #define Anum_pg_database_datdba            2
>   #define Anum_pg_database_encoding        3
>   #define Anum_pg_database_datistemplate    4
>   #define Anum_pg_database_datallowconn    5
> ! #define Anum_pg_database_datlastsysoid    6
> ! #define Anum_pg_database_datvacuumxid    7
> ! #define Anum_pg_database_datfrozenxid    8
> ! #define Anum_pg_database_dattablespace    9
> ! #define Anum_pg_database_datconfig        10
> ! #define Anum_pg_database_datacl            11
>
> ! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 1663 _null_ _null_ ));
>   DESCR("Default template database");
>   #define TemplateDbOid            1
>
> --- 60,80 ----
>    *        compiler constants for pg_database
>    * ----------------
>    */
> ! #define Natts_pg_database                12
>   #define Anum_pg_database_datname        1
>   #define Anum_pg_database_datdba            2
>   #define Anum_pg_database_encoding        3
>   #define Anum_pg_database_datistemplate    4
>   #define Anum_pg_database_datallowconn    5
> ! #define Anum_pg_database_datmaxconn        6
> ! #define Anum_pg_database_datlastsysoid    7
> ! #define Anum_pg_database_datvacuumxid    8
> ! #define Anum_pg_database_datfrozenxid    9
> ! #define Anum_pg_database_dattablespace    10
> ! #define Anum_pg_database_datconfig        11
> ! #define Anum_pg_database_datacl            12
>
> ! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 0 1663 _null_ _null_ ));
>   DESCR("Default template database");
>   #define TemplateDbOid            1
>
> diff -Nacr my-cvs/src/include/catalog/pg_shadow.h my-aproach2/src/include/catalog/pg_shadow.h
> *** my-cvs/src/include/catalog/pg_shadow.h    Thu Apr 14 03:38:22 2005
> --- my-aproach2/src/include/catalog/pg_shadow.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 36,41 ****
> --- 36,42 ----
>       bool        usecreatedb;
>       bool        usesuper;        /* read this field via superuser() only */
>       bool        usecatupd;
> +     int4        usemaxconn;        /* maximum connections allowed */
>
>       /* remaining fields may be null; use heap_getattr to read them! */
>       text        passwd;
> ***************
> *** 54,68 ****
>    *        compiler constants for pg_shadow
>    * ----------------
>    */
> ! #define Natts_pg_shadow                    8
>   #define Anum_pg_shadow_usename            1
>   #define Anum_pg_shadow_usesysid            2
>   #define Anum_pg_shadow_usecreatedb        3
>   #define Anum_pg_shadow_usesuper            4
>   #define Anum_pg_shadow_usecatupd        5
> ! #define Anum_pg_shadow_passwd            6
> ! #define Anum_pg_shadow_valuntil            7
> ! #define Anum_pg_shadow_useconfig        8
>
>   /* ----------------
>    *        initial contents of pg_shadow
> --- 55,70 ----
>    *        compiler constants for pg_shadow
>    * ----------------
>    */
> ! #define Natts_pg_shadow                    9
>   #define Anum_pg_shadow_usename            1
>   #define Anum_pg_shadow_usesysid            2
>   #define Anum_pg_shadow_usecreatedb        3
>   #define Anum_pg_shadow_usesuper            4
>   #define Anum_pg_shadow_usecatupd        5
> ! #define Anum_pg_shadow_usemaxconn        6
> ! #define Anum_pg_shadow_passwd            7
> ! #define Anum_pg_shadow_valuntil            8
> ! #define Anum_pg_shadow_useconfig        9
>
>   /* ----------------
>    *        initial contents of pg_shadow
> ***************
> *** 71,77 ****
>    * user choices.
>    * ----------------
>    */
> ! DATA(insert ( "POSTGRES" PGUID t t t _null_ _null_ _null_ ));
>
>   #define BOOTSTRAP_USESYSID 1
>
> --- 73,79 ----
>    * user choices.
>    * ----------------
>    */
> ! DATA(insert ( "POSTGRES" PGUID t t t 0 _null_ _null_ _null_ ));
>
>   #define BOOTSTRAP_USESYSID 1
>
> diff -Nacr my-cvs/src/include/commands/dbcommands.h my-aproach2/src/include/commands/dbcommands.h
> *** my-cvs/src/include/commands/dbcommands.h    Mon Jun 06 19:01:26 2005
> --- my-aproach2/src/include/commands/dbcommands.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 64,69 ****
> --- 64,70 ----
>   extern void createdb(const CreatedbStmt *stmt);
>   extern void dropdb(const char *dbname);
>   extern void RenameDatabase(const char *oldname, const char *newname);
> + extern void AlterDatabase(AlterDatabaseStmt *stmt);
>   extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
>   extern void AlterDatabaseOwner(const char *dbname, AclId newOwnerSysId);
>
> diff -Nacr my-cvs/src/include/nodes/nodes.h my-aproach2/src/include/nodes/nodes.h
> *** my-cvs/src/include/nodes/nodes.h    Mon Jun 27 00:05:42 2005
> --- my-aproach2/src/include/nodes/nodes.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 272,277 ****
> --- 272,278 ----
>       T_ReindexStmt,
>       T_CheckPointStmt,
>       T_CreateSchemaStmt,
> +     T_AlterDatabaseStmt,
>       T_AlterDatabaseSetStmt,
>       T_AlterUserSetStmt,
>       T_CreateConversionStmt,
> diff -Nacr my-cvs/src/include/nodes/parsenodes.h my-aproach2/src/include/nodes/parsenodes.h
> *** my-cvs/src/include/nodes/parsenodes.h    Wed Jun 22 23:14:32 2005
> --- my-aproach2/src/include/nodes/parsenodes.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 1620,1625 ****
> --- 1620,1632 ----
>    *    Alter Database
>    * ----------------------
>    */
> + typedef struct AlterDatabaseStmt
> + {
> +     NodeTag        type;
> +     char       *dbname;            /* name of database to create */
> +     List       *options;        /* List of DefElem nodes */
> + } AlterDatabaseStmt;
> +
>   typedef struct AlterDatabaseSetStmt
>   {
>       NodeTag        type;
> diff -Nacr my-cvs/src/include/storage/proc.h my-aproach2/src/include/storage/proc.h
> *** my-cvs/src/include/storage/proc.h    Sat Jun 18 00:32:50 2005
> --- my-aproach2/src/include/storage/proc.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 71,76 ****
> --- 71,77 ----
>
>       int            pid;            /* This backend's process id, or 0 */
>       Oid            databaseId;        /* OID of database this backend is using */
> +     AclId        userId;            /* user connected to this backend */
>
>       /* Info about LWLock the process is currently waiting for, if any. */
>       bool        lwWaiting;        /* true if waiting for an LW lock */
> diff -Nacr my-cvs/src/include/storage/procarray.h my-aproach2/src/include/storage/procarray.h
> *** my-cvs/src/include/storage/procarray.h    Sat Jun 18 00:32:50 2005
> --- my-aproach2/src/include/storage/procarray.h    Tue Jun 28 06:07:50 2005
> ***************
> *** 31,36 ****
> --- 31,38 ----
>   extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);
>
>   extern int    CountActiveBackends(void);
> + extern int    CountDBBackends(Oid databaseid);
> + extern int    CountUserBackends(AclId userid);
>
>   extern void XidCacheRemoveRunningXids(TransactionId xid,
>                             int nxids, TransactionId *xids);
> diff -Nacr my-cvs/src/tools/pgindent/pgindent my-aproach2/src/tools/pgindent/pgindent
> *** my-cvs/src/tools/pgindent/pgindent    Thu Oct 07 16:15:50 2004
> --- my-aproach2/src/tools/pgindent/pgindent    Tue Jun 28 06:07:50 2005
> ***************
> *** 175,180 ****
> --- 175,181 ----
>   -TAllocSetContext \
>   -TAllocateDesc \
>   -TAllocateDescKind \
> + -TAlterDatabaseStmt \
>   -TAlterDatabaseSetStmt \
>   -TAlterDomainStmt \
>   -TAlterGroupStmt \

>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: if posting/reading through Usenet, please send an appropriate
>        subscribe-nomail command to majordomo@postgresql.org so that your
>        message can get through to the mailing list cleanly

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Alvaro Herrera
Date:
On Sat, Jul 02, 2005 at 04:28:48PM -0400, Bruce Momjian wrote:

> We will need these:
>
> > Patch includes only changes to backend, I will make pg_dump, ecpg and
> > documentation patches once this is completed and accepted by team.
>
> Your patch has been added to the PostgreSQL unapplied patches list at:
>
>     http://momjian.postgresql.org/cgi-bin/pgpatches
>
> It will be applied as soon as one of the PostgreSQL committers reviews
> and approves it.

It needs to be updated to account for the roles patch.

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"El Maquinismo fue proscrito so pena de cosquilleo hasta la muerte"
(Ijon Tichy en Viajes, Stanislaw Lem)

Re: per user/database connections limit again

From
Bruce Momjian
Date:
Alvaro Herrera wrote:
> On Sat, Jul 02, 2005 at 04:28:48PM -0400, Bruce Momjian wrote:
>
> > We will need these:
> >
> > > Patch includes only changes to backend, I will make pg_dump, ecpg and
> > > documentation patches once this is completed and accepted by team.
> >
> > Your patch has been added to the PostgreSQL unapplied patches list at:
> >
> >     http://momjian.postgresql.org/cgi-bin/pgpatches
> >
> > It will be applied as soon as one of the PostgreSQL committers reviews
> > and approves it.
>
> It needs to be updated to account for the roles patch.

I was afraid of that. :-(  Thanks.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Stephen Frost
Date:
* Petr Jelinek (pjmodos@parba.cz) wrote:
> +     if (!(superuser()
> +         || ((Form_pg_database) GETSTRUCT(tuple))->datdba == GetUserId()))
> +         aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
> +                        stmt->dbname);

This should almost certainly be a pg_database_ownercheck() call instead.

The rest needs to be updated for roles, but looks like it should be
pretty easy to do.  Much of it just needs to be repatched, the parts
that do need to be changed look to be pretty simple changes.

I believe the use of SessionUserId is probably correct in this patch.
This does mean that this patch will only be for canlogin roles, but that
seems like it's probably correct.  Handling roles w/ members would
require much more thought.

    Thanks,

        Stephen

Attachment

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Stephen Frost wrote:

>This should almost certainly be a pg_database_ownercheck() call instead.
>
>
Right there wasn't pg_database_ownercheck at the time I was writing it,
fixed

>The rest needs to be updated for roles, but looks like it should be
>pretty easy to do.  Much of it just needs to be repatched, the parts
>that do need to be changed look to be pretty simple changes.
>
>
Done.

>I believe the use of SessionUserId is probably correct in this patch.
>This does mean that this patch will only be for canlogin roles, but that
>seems like it's probably correct.  Handling roles w/ members would
>require much more thought.
>
>
I don't think that having max connection for roles w/ members is doable
because you can have 5 roles which has 1 user as member and each role
has different number of max conections and there is no right way to
decide what to do.


New version which works with roles is attached (diffed against cvs),
everything else is mostly same.
I also had to readd roleid to flatfiles because I need it in
InitProcess() function.

--
Regards
Petr Jelinek (PJMODOS)


Index: src/backend/commands/dbcommands.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/dbcommands.c,v
retrieving revision 1.164
diff -c -r1.164 dbcommands.c
*** src/backend/commands/dbcommands.c    30 Jun 2005 00:00:50 -0000    1.164
--- src/backend/commands/dbcommands.c    3 Jul 2005 22:47:39 -0000
***************
*** 53,60 ****

  /* non-export function prototypes */
  static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
!             Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace);
  static bool have_createdb_privilege(void);
--- 53,60 ----

  /* non-export function prototypes */
  static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
!             bool *dbAllowConnP,    Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace);
  static bool have_createdb_privilege(void);
***************
*** 74,79 ****
--- 74,80 ----
      int            src_encoding;
      bool        src_istemplate;
      bool        src_allowconn;
+     int            src_maxconn;
      Oid            src_lastsysoid;
      TransactionId src_vacuumxid;
      TransactionId src_frozenxid;
***************
*** 91,100 ****
--- 92,103 ----
      DefElem    *downer = NULL;
      DefElem    *dtemplate = NULL;
      DefElem    *dencoding = NULL;
+     DefElem    *dmaxconn = NULL;
      char       *dbname = stmt->dbname;
      char       *dbowner = NULL;
      const char *dbtemplate = NULL;
      int            encoding = -1;
+     int            dbmaxconn = -1;

  #ifndef WIN32
      char        buf[2 * MAXPGPATH + 100];
***************
*** 140,145 ****
--- 143,156 ----
                           errmsg("conflicting or redundant options")));
              dencoding = defel;
          }
+         else if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
          else if (strcmp(defel->defname, "location") == 0)
          {
              ereport(WARNING,
***************
*** 185,190 ****
--- 196,203 ----
              elog(ERROR, "unrecognized node type: %d",
                   nodeTag(dencoding->arg));
      }
+     if (dmaxconn && dmaxconn->arg)
+         dbmaxconn = intVal(dmaxconn->arg);

      /* obtain OID of proposed owner */
      if (dbowner)
***************
*** 218,224 ****
       * idea, so accept possibility of race to create.  We will check again
       * after we grab the exclusive lock.
       */
!     if (get_db_info(dbname, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_DUPLICATE_DATABASE),
--- 231,237 ----
       * idea, so accept possibility of race to create.  We will check again
       * after we grab the exclusive lock.
       */
!     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_DUPLICATE_DATABASE),
***************
*** 231,238 ****
          dbtemplate = "template1";        /* Default template database name */

      if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
!                      &src_istemplate, &src_allowconn, &src_lastsysoid,
!                      &src_vacuumxid, &src_frozenxid, &src_deftablespace))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
           errmsg("template database \"%s\" does not exist", dbtemplate)));
--- 244,252 ----
          dbtemplate = "template1";        /* Default template database name */

      if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
!                      &src_maxconn, &src_istemplate, &src_allowconn,
!                      &src_lastsysoid, &src_vacuumxid, &src_frozenxid,
!                      &src_deftablespace))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
           errmsg("template database \"%s\" does not exist", dbtemplate)));
***************
*** 266,271 ****
--- 280,289 ----
      if (encoding < 0)
          encoding = src_encoding;

+     /* If dbmaxconn is defaulted, use source's dbmaxconn */
+     if (dbmaxconn < 0)
+         dbmaxconn = src_maxconn;
+
      /* Some encodings are client only */
      if (!PG_VALID_BE_ENCODING(encoding))
          ereport(ERROR,
***************
*** 461,467 ****
      pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);

      /* Check to see if someone else created same DB name meanwhile. */
!     if (get_db_info(dbname, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
      {
          /* Don't hold lock while doing recursive remove */
--- 479,485 ----
      pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);

      /* Check to see if someone else created same DB name meanwhile. */
!     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
      {
          /* Don't hold lock while doing recursive remove */
***************
*** 487,492 ****
--- 505,511 ----
      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_datmaxconn - 1] = Int32GetDatum(dbmaxconn);
      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);
***************
*** 587,593 ****
       */
      pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);

!     if (!get_db_info(dbname, &db_id, NULL, NULL,
                       &db_istemplate, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
--- 606,612 ----
       */
      pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);

!     if (!get_db_info(dbname, &db_id, NULL, NULL, NULL,
                       &db_istemplate, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
***************
*** 783,788 ****
--- 802,892 ----


  /*
+  * ALTER DATABASE name ...
+  */
+ void
+ AlterDatabase(AlterDatabaseStmt *stmt)
+ {
+     Datum        new_record[Natts_pg_database];
+     char        new_record_nulls[Natts_pg_database];
+     char        new_record_repl[Natts_pg_database];
+     Relation    rel;
+     HeapTuple    tuple,
+                 newtuple;
+     ScanKeyData scankey;
+     SysScanDesc scan;
+     ListCell   *option;
+     int            maxconn = -1;    /* Maximum connections allowed */
+
+     DefElem    *dmaxconn = NULL;
+
+     /* Extract options from the statement node tree */
+     foreach(option, stmt->options)
+     {
+         DefElem    *defel = (DefElem *) lfirst(option);
+
+         if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
+     }
+
+     if (dmaxconn)
+         maxconn = intVal(dmaxconn->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 (!have_createdb_privilege())
+         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 (maxconn >= 0)
+     {
+         new_record[Anum_pg_database_datmaxconn - 1] = Int32GetDatum(maxconn);
+         new_record_repl[Anum_pg_database_datmaxconn - 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);
+ }
+
+
+ /*
   * ALTER DATABASE name SET ...
   */
  void
***************
*** 971,978 ****

  static bool
  get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
!             Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace)
  {
--- 1075,1082 ----

  static bool
  get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
!             bool *dbAllowConnP,    Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace)
  {
***************
*** 1017,1022 ****
--- 1121,1129 ----
          /* allowing connections? */
          if (dbAllowConnP)
              *dbAllowConnP = dbform->datallowconn;
+         /* maximum connections */
+         if (dbMaxConnP)
+             *dbMaxConnP = dbform->datmaxconn;
          /* last system OID used in database */
          if (dbLastSysOidP)
              *dbLastSysOidP = dbform->datlastsysoid;
Index: src/backend/commands/user.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/user.c,v
retrieving revision 1.155
diff -c -r1.155 user.c
*** src/backend/commands/user.c    29 Jun 2005 20:34:13 -0000    1.155
--- src/backend/commands/user.c    3 Jul 2005 22:47:54 -0000
***************
*** 85,90 ****
--- 85,91 ----
      bool        createrole = false;        /* Can this user create roles? */
      bool        createdb = false;        /* Can the user create databases? */
      bool        canlogin = false;        /* Can this user login? */
+     int            maxconn = 0;            /* 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 */
***************
*** 94,99 ****
--- 95,101 ----
      DefElem    *dcreaterole = NULL;
      DefElem    *dcreatedb = NULL;
      DefElem    *dcanlogin = NULL;
+     DefElem    *dmaxconn = NULL;
      DefElem    *daddroleto = NULL;
      DefElem    *drolemembers = NULL;
      DefElem    *dadminmembers = NULL;
***************
*** 155,160 ****
--- 157,170 ----
                           errmsg("conflicting or redundant options")));
              dcanlogin = defel;
          }
+         else if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
          else if (strcmp(defel->defname, "addroleto") == 0)
          {
              if (daddroleto)
***************
*** 202,207 ****
--- 212,230 ----
          createdb = intVal(dcreatedb->arg) != 0;
      if (dcanlogin)
          canlogin = intVal(dcanlogin->arg) != 0;
+     if (dmaxconn)
+     {
+         maxconn = intVal(dmaxconn->arg);
+         if (maxconn < 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("MAX CONNECTIONS must not be negative")));
+
+         if (!canlogin && maxconn > 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("MAX CONNECTIONS can be specified only for roles which can login")));
+     }
      if (daddroleto)
          addroleto = (List *) daddroleto->arg;
      if (drolemembers)
***************
*** 265,270 ****
--- 288,294 ----
      /* 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_rolmaxconn - 1] = Int32GetDatum(maxconn);

      if (password)
      {
***************
*** 369,374 ****
--- 393,399 ----
      int            createrole = -1;        /* Can this user create roles? */
      int            createdb = -1;            /* Can the user create databases? */
      int            canlogin = -1;            /* Can this user login? */
+     int            maxconn = -1;            /* maximum connections allowed */
      List       *rolemembers = NIL;        /* roles to be added/removed */
      char       *validUntil = NULL;        /* time the login is valid until */
      DefElem    *dpassword = NULL;
***************
*** 376,381 ****
--- 401,407 ----
      DefElem    *dcreaterole = NULL;
      DefElem    *dcreatedb = NULL;
      DefElem    *dcanlogin = NULL;
+     DefElem    *dmaxconn = NULL;
      DefElem    *drolemembers = NULL;
      DefElem    *dvalidUntil = NULL;
      Oid            roleid;
***************
*** 431,436 ****
--- 457,470 ----
                           errmsg("conflicting or redundant options")));
              dcanlogin = defel;
          }
+         else if (strcmp(defel->defname, "maxconnections") == 0)
+         {
+             if (dmaxconn)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             dmaxconn = defel;
+         }
          else if (strcmp(defel->defname, "rolemembers") == 0 &&
                   stmt->action != 0)
          {
***************
*** 463,468 ****
--- 497,515 ----
          createdb = intVal(dcreatedb->arg);
      if (dcanlogin)
          canlogin = intVal(dcanlogin->arg);
+     if (dmaxconn)
+     {
+         maxconn = intVal(dmaxconn->arg);
+         if (maxconn < 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("MAX CONNECTIONS must not be negative")));
+
+         if (canlogin == 0 && maxconn > 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("MAX CONNECTIONS can be specified only for roles which can login")));
+     }
      if (drolemembers)
          rolemembers = (List *) drolemembers->arg;
      if (dvalidUntil)
***************
*** 502,507 ****
--- 549,555 ----
              !(createrole < 0 &&
                createdb < 0 &&
                canlogin < 0 &&
+               maxconn < 0 &&
                !rolemembers &&
                !validUntil &&
                password &&
***************
*** 553,558 ****
--- 601,612 ----
          new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r';
      }

+     if (maxconn >= 0)
+     {
+         new_record[Anum_pg_authid_rolmaxconn - 1] = Int32GetDatum(maxconn);
+         new_record_repl[Anum_pg_authid_rolmaxconn - 1] = 'r';
+     }
+
      /* password */
      if (password)
      {
Index: src/backend/libpq/crypt.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/libpq/crypt.c,v
retrieving revision 1.64
diff -c -r1.64 crypt.c
*** src/backend/libpq/crypt.c    29 Jun 2005 22:51:54 -0000    1.64
--- src/backend/libpq/crypt.c    3 Jul 2005 22:47:57 -0000
***************
*** 42,52 ****
      if ((line = get_role_line(role)) == NULL)
          return STATUS_ERROR;

!     /* Skip over rolename */
      token = list_head(*line);
      if (token)
          token = lnext(token);
      if (token)
      {
          shadow_pass = (char *) lfirst(token);
          token = lnext(token);
--- 42,54 ----
      if ((line = get_role_line(role)) == NULL)
          return STATUS_ERROR;

!     /* Skip over rolename and roleid */
      token = list_head(*line);
      if (token)
          token = lnext(token);
      if (token)
+         token = lnext(token);
+     if (token)
      {
          shadow_pass = (char *) lfirst(token);
          token = lnext(token);
Index: src/backend/libpq/hba.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/libpq/hba.c,v
retrieving revision 1.144
diff -c -r1.144 hba.c
*** src/backend/libpq/hba.c    28 Jun 2005 22:16:45 -0000    1.144
--- src/backend/libpq/hba.c    3 Jul 2005 22:48:12 -0000
***************
*** 494,505 ****
          return true;

      /*
!      * skip over the role name, password, valuntil, examine all the
       * membership entries
       */
!     if (list_length(*line) < 4)
          return false;
!     for_each_cell(line_item, lnext(lnext(lnext(list_head(*line)))))
      {
          if (strcmp((char *) lfirst(line_item), role) == 0)
              return true;
--- 494,505 ----
          return true;

      /*
!      * skip over the role name, id, password, valuntil, examine all the
       * membership entries
       */
!     if (list_length(*line) < 5)
          return false;
!     for_each_cell(line_item, lnext(lnext(lnext(lnext(list_head(*line))))))
      {
          if (strcmp((char *) lfirst(line_item), role) == 0)
              return true;
Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.311
diff -c -r1.311 copyfuncs.c
*** src/backend/nodes/copyfuncs.c    2 Jul 2005 23:00:39 -0000    1.311
--- src/backend/nodes/copyfuncs.c    3 Jul 2005 22:48:36 -0000
***************
*** 2204,2209 ****
--- 2204,2220 ----
      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)
  {
***************
*** 3010,3015 ****
--- 3021,3029 ----
          case T_CreatedbStmt:
              retval = _copyCreatedbStmt(from);
              break;
+         case T_AlterDatabaseStmt:
+             retval = _copyAlterDatabaseStmt(from);
+             break;
          case T_AlterDatabaseSetStmt:
              retval = _copyAlterDatabaseSetStmt(from);
              break;
Index: src/backend/nodes/equalfuncs.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
retrieving revision 1.248
diff -c -r1.248 equalfuncs.c
*** src/backend/nodes/equalfuncs.c    2 Jul 2005 23:00:39 -0000    1.248
--- src/backend/nodes/equalfuncs.c    3 Jul 2005 22:48:53 -0000
***************
*** 1152,1157 ****
--- 1152,1166 ----
  }

  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);
***************
*** 2058,2063 ****
--- 2067,2075 ----
          case T_CreatedbStmt:
              retval = _equalCreatedbStmt(a, b);
              break;
+         case T_AlterDatabaseStmt:
+             retval = _equalAlterDatabaseStmt(a, b);
+             break;
          case T_AlterDatabaseSetStmt:
              retval = _equalAlterDatabaseSetStmt(a, b);
              break;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.501
diff -c -r2.501 gram.y
*** src/backend/parser/gram.y    29 Jun 2005 20:34:13 -0000    2.501
--- src/backend/parser/gram.y    3 Jul 2005 22:50:20 -0000
***************
*** 131,139 ****
  }

  %type <node>    stmt schema_stmt
!         AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
!         AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
!         AlterRoleStmt AlterRoleSetStmt
          AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
          ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
          CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
--- 131,139 ----
  }

  %type <node>    stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
!         AlterOwnerStmt AlterSeqStmt AlterTableStmt
!         AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
          AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
          ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
          CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
***************
*** 165,172 ****

  %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 <ival>    opt_lock lock_type cast_context
  %type <boolean>    opt_force opt_or_replace
--- 165,174 ----

  %type <dbehavior>    opt_drop_behavior

! %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
***************
*** 342,348 ****
      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
      CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
      CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE

--- 344,350 ----
      CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P
      CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
      CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
!     COMMITTED CONNECTIONS CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
      CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
      CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE

***************
*** 373,379 ****
      LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
      LOCK_P LOGIN_P

!     MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE

      NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
      NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
--- 375,381 ----
      LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
      LOCK_P LOGIN_P

!     MATCH MAX MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE

      NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
      NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
***************
*** 486,492 ****
          ;

  stmt :
!             AlterDatabaseSetStmt
              | AlterDomainStmt
              | AlterFunctionStmt
              | AlterGroupStmt
--- 488,495 ----
          ;

  stmt :
!             AlterDatabaseStmt
!             | AlterDatabaseSetStmt
              | AlterDomainStmt
              | AlterFunctionStmt
              | AlterGroupStmt
***************
*** 663,668 ****
--- 666,675 ----
                  {
                      $$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
                  }
+             | MAX CONNECTIONS Iconst
+                 {
+                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($3));
+                 }
              | IN_P ROLE name_list
                  {
                      $$ = makeDefElem("addroleto", (Node *)$3);
***************
*** 4455,4460 ****
--- 4462,4471 ----
                  {
                      $$ = makeDefElem("encoding", NULL);
                  }
+             | MAX CONNECTIONS opt_equal Iconst
+                 {
+                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
+                 }
              | OWNER opt_equal name
                  {
                      $$ = makeDefElem("owner", (Node *)makeString($3));
***************
*** 4481,4486 ****
--- 4492,4507 ----
   *
   *****************************************************************************/

+ 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
                  {
***************
*** 4501,4506 ****
--- 4522,4540 ----
          ;


+ alterdb_opt_list:
+             alterdb_opt_list alterdb_opt_item        { $$ = lappend($1, $2); }
+             | /* EMPTY */                            { $$ = NIL; }
+         ;
+
+ alterdb_opt_item:
+             MAX CONNECTIONS opt_equal Iconst
+                 {
+                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
+                 }
+         ;
+
+
  /*****************************************************************************
   *
   *        DROP DATABASE
***************
*** 7941,7946 ****
--- 7975,7981 ----
              | COMMENT
              | COMMIT
              | COMMITTED
+             | CONNECTIONS
              | CONSTRAINTS
              | CONVERSION_P
              | COPY
***************
*** 8009,8014 ****
--- 8044,8050 ----
              | LOCK_P
              | LOGIN_P
              | MATCH
+             | MAX
              | MAXVALUE
              | MINUTE_P
              | MINVALUE
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.162
diff -c -r1.162 keywords.c
*** src/backend/parser/keywords.c    29 Jun 2005 20:34:14 -0000    1.162
--- src/backend/parser/keywords.c    3 Jul 2005 22:50:24 -0000
***************
*** 83,88 ****
--- 83,89 ----
      {"comment", COMMENT},
      {"commit", COMMIT},
      {"committed", COMMITTED},
+     {"connections", CONNECTIONS},
      {"constraint", CONSTRAINT},
      {"constraints", CONSTRAINTS},
      {"conversion", CONVERSION_P},
***************
*** 203,208 ****
--- 204,210 ----
      {"lock", LOCK_P},
      {"login", LOGIN_P},
      {"match", MATCH},
+     {"max", MAX},
      {"maxvalue", MAXVALUE},
      {"minute", MINUTE_P},
      {"minvalue", MINVALUE},
Index: src/backend/storage/ipc/procarray.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/storage/ipc/procarray.c,v
retrieving revision 1.3
diff -c -r1.3 procarray.c
*** src/backend/storage/ipc/procarray.c    17 Jun 2005 22:32:45 -0000    1.3
--- src/backend/storage/ipc/procarray.c    3 Jul 2005 22:50:36 -0000
***************
*** 734,739 ****
--- 734,790 ----
  }


+ /*
+  * 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 && 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 && proc->roleId == roleid)
+             count++;
+     }
+
+     LWLockRelease(ProcArrayLock);
+
+     return count;
+ }
+
+
  #define XidCacheRemove(i) \
      do { \
          MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \
Index: src/backend/storage/lmgr/proc.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
retrieving revision 1.160
diff -c -r1.160 proc.c
*** src/backend/storage/lmgr/proc.c    17 Jun 2005 22:32:45 -0000    1.160
--- src/backend/storage/lmgr/proc.c    3 Jul 2005 22:50:51 -0000
***************
*** 254,259 ****
--- 254,260 ----
      MyProc->xmin = InvalidTransactionId;
      MyProc->pid = MyProcPid;
      MyProc->databaseId = MyDatabaseId;
+     MyProc->roleId = GetSessionUserId();
      MyProc->lwWaiting = false;
      MyProc->lwExclusive = false;
      MyProc->lwWaitLink = NULL;
Index: src/backend/tcop/utility.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/tcop/utility.c,v
retrieving revision 1.240
diff -c -r1.240 utility.c
*** src/backend/tcop/utility.c    30 Jun 2005 00:00:51 -0000    1.240
--- src/backend/tcop/utility.c    3 Jul 2005 22:51:06 -0000
***************
*** 275,280 ****
--- 275,281 ----

      switch (nodeTag(parsetree))
      {
+         case T_AlterDatabaseStmt:
          case T_AlterDatabaseSetStmt:
          case T_AlterDomainStmt:
          case T_AlterFunctionStmt:
***************
*** 788,793 ****
--- 789,798 ----
              createdb((CreatedbStmt *) parsetree);
              break;

+         case T_AlterDatabaseStmt:
+             AlterDatabase((AlterDatabaseStmt *) parsetree);
+             break;
+
          case T_AlterDatabaseSetStmt:
              AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
              break;
***************
*** 1504,1509 ****
--- 1509,1518 ----
              tag = "CREATE DATABASE";
              break;

+         case T_AlterDatabaseStmt:
+             tag = "ALTER DATABASE";
+             break;
+
          case T_AlterDatabaseSetStmt:
              tag = "ALTER DATABASE";
              break;
Index: src/backend/utils/init/flatfiles.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/flatfiles.c,v
retrieving revision 1.11
diff -c -r1.11 flatfiles.c
*** src/backend/utils/init/flatfiles.c    29 Jun 2005 20:34:15 -0000    1.11
--- src/backend/utils/init/flatfiles.c    3 Jul 2005 22:51:18 -0000
***************
*** 629,635 ****
              ListCell *mem;

              fputs_quote(arole->rolname, fp);
!             fputs(" ", fp);
              fputs_quote(arole->rolpassword, fp);
              fputs(" ", fp);
              fputs_quote(arole->rolvaliduntil, fp);
--- 629,635 ----
              ListCell *mem;

              fputs_quote(arole->rolname, fp);
!             fprintf(fp, " %u ", arole->roleid);
              fputs_quote(arole->rolpassword, fp);
              fputs(" ", fp);
              fputs_quote(arole->rolvaliduntil, fp);
Index: src/backend/utils/init/miscinit.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/miscinit.c,v
retrieving revision 1.144
diff -c -r1.144 miscinit.c
*** src/backend/utils/init/miscinit.c    28 Jun 2005 22:16:45 -0000    1.144
--- src/backend/utils/init/miscinit.c    3 Jul 2005 22:51:29 -0000
***************
*** 39,44 ****
--- 39,45 ----
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
+ #include "storage/procarray.h"


  ProcessingMode Mode = InitProcessing;
***************
*** 347,352 ****
--- 348,365 ----

      SetSessionUserId(roleid);    /* sets CurrentUserId too */

+     /*
+      * Check connection limit for user
+      */
+     if (rform->rolmaxconn > 0 && !AuthenticatedUserIsSuperuser &&
+             CountUserBackends(AuthenticatedUserId) > rform->rolmaxconn)
+     {
+         ereport(FATAL,
+             (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+          errmsg("sorry, too many clients already for role \"%s\"",
+             rolename)));
+     }
+
      /* Record username and superuser status as GUC settings too */
      SetConfigOption("session_authorization", rolename,
                      PGC_BACKEND, PGC_S_OVERRIDE);
Index: src/backend/utils/init/postinit.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/postinit.c,v
retrieving revision 1.151
diff -c -r1.151 postinit.c
*** src/backend/utils/init/postinit.c    28 Jun 2005 19:51:23 -0000    1.151
--- src/backend/utils/init/postinit.c    3 Jul 2005 22:51:37 -0000
***************
*** 47,52 ****
--- 47,53 ----


  static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace);
+ static bool FindMyRole(const char *name, Oid *role_id);
  static void ReverifyMyDatabase(const char *name);
  static void InitCommunication(void);
  static void ShutdownPostgres(int code, Datum arg);
***************
*** 101,106 ****
--- 102,136 ----
  }

  /*
+  * Get roleid from flatfiles
+  *
+  * We need this because we need to know userid before
+  * InitProcess() is called
+  */
+ static bool
+ FindMyRole(const char *name, Oid *role_id)
+ {
+     List      **line;
+     ListCell   *token;
+
+     if ((line = get_role_line(name)) == NULL)
+         ereport(FATAL,
+                 (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
+                  errmsg("could not find role \"%s\"", name)));
+
+     token = list_head(*line);
+     if (token)
+         token = lnext(token);
+     if (token)
+     {
+         *role_id = atoi((char*)lfirst(token));
+         return true;
+     }
+
+     return false;
+ }
+
+ /*
   * ReverifyMyDatabase -- recheck info obtained by FindMyDatabase
   *
   * Since FindMyDatabase cannot lock pg_database, the information it read
***************
*** 166,182 ****
                          name, MyDatabaseId)));
      }

-     /*
-      * 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;")
-      */
      dbform = (Form_pg_database) GETSTRUCT(tup);
!     if (IsUnderPostmaster && !dbform->datallowconn)
!         ereport(FATAL,
!                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!          errmsg("database \"%s\" is not currently accepting connections",
                  name)));

      /*
       * OK, we're golden.  Next to-do item is to save the encoding
--- 196,230 ----
                          name, MyDatabaseId)));
      }

      dbform = (Form_pg_database) GETSTRUCT(tup);
!     if (IsUnderPostmaster)
!     {
!         /*
!          * 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;")
!          */
!         if (!dbform->datallowconn)
!         {
!             ereport(FATAL,
!                     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!              errmsg("database \"%s\" is not currently accepting connections",
!                     name)));
!         }
!
!         /*
!          * Here we check cxonenction limit for this database
!          */
!         if (dbform->datmaxconn > 0 && !superuser() &&
!                 CountDBBackends(MyDatabaseId) > dbform->datmaxconn)
!         {
!             ereport(FATAL,
!                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
!              errmsg("sorry, too many clients already for database \"%s\"",
                  name)));
+         }
+     }
+

      /*
       * OK, we're golden.  Next to-do item is to save the encoding
***************
*** 352,357 ****
--- 400,424 ----
       */

      /*
+      * We need to know roleid in InitProcess() so we have read it from
+      * flatfile, real user inicialization is done later
+      */
+     if (IsUnderPostmaster)
+     {
+         Oid roleid;
+
+         if (!FindMyRole(username, &roleid))
+             ereport(FATAL,
+                     (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
+                      errmsg("role \"%s\" does not exist",
+                             username)));
+
+         SetSessionUserId(roleid);
+     }
+     else
+         SetSessionUserId(BOOTSTRAP_SUPERUSERID);
+
+     /*
       * Set up my per-backend PGPROC struct in shared memory.    (We need
       * to know MyDatabaseId before we can do this, since it's entered into
       * the PGPROC struct.)
Index: src/include/catalog/pg_authid.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_authid.h,v
retrieving revision 1.1
diff -c -r1.1 pg_authid.h
*** src/include/catalog/pg_authid.h    28 Jun 2005 05:09:05 -0000    1.1
--- src/include/catalog/pg_authid.h    3 Jul 2005 22:51:50 -0000
***************
*** 48,53 ****
--- 48,54 ----
      bool        rolcreatedb;    /* allowed to create databases? */
      bool        rolcatupdate;    /* allowed to alter catalogs manually? */
      bool        rolcanlogin;    /* allowed to log in as session user? */
+     int4        rolmaxconn;        /* maximum connections allowed */

      /* remaining fields may be null; use heap_getattr to read them! */
      text        rolpassword;    /* password, if any */
***************
*** 69,84 ****
   *        compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid                    9
  #define Anum_pg_authid_rolname            1
  #define Anum_pg_authid_rolsuper            2
  #define Anum_pg_authid_rolcreaterole    3
  #define Anum_pg_authid_rolcreatedb        4
  #define Anum_pg_authid_rolcatupdate        5
  #define Anum_pg_authid_rolcanlogin        6
! #define Anum_pg_authid_rolpassword        7
! #define Anum_pg_authid_rolvaliduntil    8
! #define Anum_pg_authid_rolconfig        9

  /* ----------------
   *        initial contents of pg_authid
--- 70,86 ----
   *        compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid                    10
  #define Anum_pg_authid_rolname            1
  #define Anum_pg_authid_rolsuper            2
  #define Anum_pg_authid_rolcreaterole    3
  #define Anum_pg_authid_rolcreatedb        4
  #define Anum_pg_authid_rolcatupdate        5
  #define Anum_pg_authid_rolcanlogin        6
! #define Anum_pg_authid_rolmaxconn        7
! #define Anum_pg_authid_rolpassword        8
! #define Anum_pg_authid_rolvaliduntil    9
! #define Anum_pg_authid_rolconfig        10

  /* ----------------
   *        initial contents of pg_authid
***************
*** 87,93 ****
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));

  #define BOOTSTRAP_SUPERUSERID 10

--- 89,95 ----
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t 0 _null_ _null_ _null_ ));

  #define BOOTSTRAP_SUPERUSERID 10

Index: src/include/catalog/pg_database.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_database.h,v
retrieving revision 1.36
diff -c -r1.36 pg_database.h
*** src/include/catalog/pg_database.h    28 Jun 2005 05:09:06 -0000    1.36
--- src/include/catalog/pg_database.h    3 Jul 2005 22:51:51 -0000
***************
*** 40,45 ****
--- 40,46 ----
      int4        encoding;        /* character encoding */
      bool        datistemplate;    /* allowed as CREATE DATABASE template? */
      bool        datallowconn;    /* new connections allowed? */
+     int4        datmaxconn;        /* maximum connections allowed */
      Oid            datlastsysoid;    /* highest OID to consider a system OID */
      TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
      TransactionId datfrozenxid; /* all XIDs before this are frozen */
***************
*** 59,78 ****
   *        compiler constants for pg_database
   * ----------------
   */
! #define Natts_pg_database                11
  #define Anum_pg_database_datname        1
  #define Anum_pg_database_datdba            2
  #define Anum_pg_database_encoding        3
  #define Anum_pg_database_datistemplate    4
  #define Anum_pg_database_datallowconn    5
! #define Anum_pg_database_datlastsysoid    6
! #define Anum_pg_database_datvacuumxid    7
! #define Anum_pg_database_datfrozenxid    8
! #define Anum_pg_database_dattablespace    9
! #define Anum_pg_database_datconfig        10
! #define Anum_pg_database_datacl            11

! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 1663 _null_ _null_ ));
  DESCR("Default template database");
  #define TemplateDbOid            1

--- 60,80 ----
   *        compiler constants for pg_database
   * ----------------
   */
! #define Natts_pg_database                12
  #define Anum_pg_database_datname        1
  #define Anum_pg_database_datdba            2
  #define Anum_pg_database_encoding        3
  #define Anum_pg_database_datistemplate    4
  #define Anum_pg_database_datallowconn    5
! #define Anum_pg_database_datmaxconn        6
! #define Anum_pg_database_datlastsysoid    7
! #define Anum_pg_database_datvacuumxid    8
! #define Anum_pg_database_datfrozenxid    9
! #define Anum_pg_database_dattablespace    10
! #define Anum_pg_database_datconfig        11
! #define Anum_pg_database_datacl            12

! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 0 1663 _null_ _null_ ));
  DESCR("Default template database");
  #define TemplateDbOid            1

Index: src/include/commands/dbcommands.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/commands/dbcommands.h,v
retrieving revision 1.39
diff -c -r1.39 dbcommands.h
*** src/include/commands/dbcommands.h    28 Jun 2005 05:09:12 -0000    1.39
--- src/include/commands/dbcommands.h    3 Jul 2005 22:51:53 -0000
***************
*** 64,69 ****
--- 64,70 ----
  extern void createdb(const CreatedbStmt *stmt);
  extern void dropdb(const char *dbname);
  extern void RenameDatabase(const char *oldname, const char *newname);
+ extern void AlterDatabase(AlterDatabaseStmt *stmt);
  extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
  extern void AlterDatabaseOwner(const char *dbname, Oid newOwnerId);

Index: src/include/nodes/nodes.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/nodes/nodes.h,v
retrieving revision 1.172
diff -c -r1.172 nodes.h
*** src/include/nodes/nodes.h    28 Jun 2005 05:09:13 -0000    1.172
--- src/include/nodes/nodes.h    3 Jul 2005 22:52:00 -0000
***************
*** 270,275 ****
--- 270,276 ----
      T_ReindexStmt,
      T_CheckPointStmt,
      T_CreateSchemaStmt,
+     T_AlterDatabaseStmt,
      T_AlterDatabaseSetStmt,
      T_AlterRoleSetStmt,
      T_CreateConversionStmt,
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.285
diff -c -r1.285 parsenodes.h
*** src/include/nodes/parsenodes.h    28 Jun 2005 19:51:24 -0000    1.285
--- src/include/nodes/parsenodes.h    3 Jul 2005 22:52:25 -0000
***************
*** 1611,1616 ****
--- 1611,1623 ----
   *    Alter Database
   * ----------------------
   */
+ typedef struct AlterDatabaseStmt
+ {
+     NodeTag        type;
+     char       *dbname;            /* name of database to alter */
+     List       *options;        /* List of DefElem nodes */
+ } AlterDatabaseStmt;
+
  typedef struct AlterDatabaseSetStmt
  {
      NodeTag        type;
Index: src/include/storage/proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/storage/proc.h,v
retrieving revision 1.79
diff -c -r1.79 proc.h
*** src/include/storage/proc.h    17 Jun 2005 22:32:50 -0000    1.79
--- src/include/storage/proc.h    3 Jul 2005 22:52:29 -0000
***************
*** 71,76 ****
--- 71,77 ----

      int            pid;            /* This backend's process id, or 0 */
      Oid            databaseId;        /* OID of database this backend is using */
+     Oid            roleId;            /* OID of role using conencted to backend */

      /* Info about LWLock the process is currently waiting for, if any. */
      bool        lwWaiting;        /* true if waiting for an LW lock */
Index: src/include/storage/procarray.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/storage/procarray.h,v
retrieving revision 1.2
diff -c -r1.2 procarray.h
*** src/include/storage/procarray.h    17 Jun 2005 22:32:50 -0000    1.2
--- src/include/storage/procarray.h    3 Jul 2005 22:52:30 -0000
***************
*** 31,36 ****
--- 31,38 ----
  extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);

  extern int    CountActiveBackends(void);
+ extern int    CountDBBackends(Oid databaseid);
+ extern int    CountUserBackends(Oid roleid);

  extern void XidCacheRemoveRunningXids(TransactionId xid,
                            int nxids, TransactionId *xids);
Index: src/tools/pgindent/pgindent
===================================================================
RCS file: /projects/cvsroot/pgsql/src/tools/pgindent/pgindent,v
retrieving revision 1.75
diff -c -r1.75 pgindent
*** src/tools/pgindent/pgindent    28 Jun 2005 23:55:30 -0000    1.75
--- src/tools/pgindent/pgindent    3 Jul 2005 22:53:03 -0000
***************
*** 177,182 ****
--- 177,183 ----
  -TAllocSetContext \
  -TAllocateDesc \
  -TAllocateDescKind \
+ -TAlterDatabaseStmt \
  -TAlterDatabaseSetStmt \
  -TAlterDomainStmt \
  -TAlterFunctionStmt \

Re: per user/database connections limit again

From
Alvaro Herrera
Date:
On Mon, Jul 04, 2005 at 01:08:05AM +0200, Petr Jelinek wrote:
> Stephen Frost wrote:

> New version which works with roles is attached (diffed against cvs),
> everything else is mostly same.
> I also had to readd roleid to flatfiles because I need it in
> InitProcess() function.

I was wondering if there was some way to defer the user check till a
later time, when the pg_authid relation could be checked?  Not sure if
that's a good idea, but it may help reduce the impact of the change, and
thus chances that it'd be rejected.

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"La espina, desde que nace, ya pincha" (Proverbio africano)

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Alvaro Herrera wrote:

>I was wondering if there was some way to defer the user check till a
>later time, when the pg_authid relation could be checked?  Not sure if
>that's a good idea, but it may help reduce the impact of the change, and
>thus chances that it'd be rejected.
>
>
Well It can, but it would mean one more lock on procarray and I didn't
want that and like I said, MyDatabaseId is read from flatfile too.
Auth flatfile is used only on two other places which I also patched so I
don't see this as a problem (it's used in hba.c to check role membership
and in crypt.c for password verification)

--
Regards
Petr Jelinek (PJMODOS)


Re: per user/database connections limit again

From
Bruce Momjian
Date:
> Patch includes only changes to backend, I will make pg_dump, ecpg and
> documentation patches once this is completed and accepted by team.

I am ready to apply this patch.  Would you make the additional changes
you suggested?  Is there any way to see the limits except to query
pg_authid?

---------------------------------------------------------------------------

Petr Jelinek wrote:
> Stephen Frost wrote:
>
> >This should almost certainly be a pg_database_ownercheck() call instead.
> >
> >
> Right there wasn't pg_database_ownercheck at the time I was writing it,
> fixed
>
> >The rest needs to be updated for roles, but looks like it should be
> >pretty easy to do.  Much of it just needs to be repatched, the parts
> >that do need to be changed look to be pretty simple changes.
> >
> >
> Done.
>
> >I believe the use of SessionUserId is probably correct in this patch.
> >This does mean that this patch will only be for canlogin roles, but that
> >seems like it's probably correct.  Handling roles w/ members would
> >require much more thought.
> >
> >
> I don't think that having max connection for roles w/ members is doable
> because you can have 5 roles which has 1 user as member and each role
> has different number of max conections and there is no right way to
> decide what to do.
>
>
> New version which works with roles is attached (diffed against cvs),
> everything else is mostly same.
> I also had to readd roleid to flatfiles because I need it in
> InitProcess() function.
>
> --
> Regards
> Petr Jelinek (PJMODOS)
>
>

> Index: src/backend/commands/dbcommands.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/commands/dbcommands.c,v
> retrieving revision 1.164
> diff -c -r1.164 dbcommands.c
> *** src/backend/commands/dbcommands.c    30 Jun 2005 00:00:50 -0000    1.164
> --- src/backend/commands/dbcommands.c    3 Jul 2005 22:47:39 -0000
> ***************
> *** 53,60 ****
>
>   /* non-export function prototypes */
>   static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
> !             Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace);
>   static bool have_createdb_privilege(void);
> --- 53,60 ----
>
>   /* non-export function prototypes */
>   static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
> !             bool *dbAllowConnP,    Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace);
>   static bool have_createdb_privilege(void);
> ***************
> *** 74,79 ****
> --- 74,80 ----
>       int            src_encoding;
>       bool        src_istemplate;
>       bool        src_allowconn;
> +     int            src_maxconn;
>       Oid            src_lastsysoid;
>       TransactionId src_vacuumxid;
>       TransactionId src_frozenxid;
> ***************
> *** 91,100 ****
> --- 92,103 ----
>       DefElem    *downer = NULL;
>       DefElem    *dtemplate = NULL;
>       DefElem    *dencoding = NULL;
> +     DefElem    *dmaxconn = NULL;
>       char       *dbname = stmt->dbname;
>       char       *dbowner = NULL;
>       const char *dbtemplate = NULL;
>       int            encoding = -1;
> +     int            dbmaxconn = -1;
>
>   #ifndef WIN32
>       char        buf[2 * MAXPGPATH + 100];
> ***************
> *** 140,145 ****
> --- 143,156 ----
>                            errmsg("conflicting or redundant options")));
>               dencoding = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "location") == 0)
>           {
>               ereport(WARNING,
> ***************
> *** 185,190 ****
> --- 196,203 ----
>               elog(ERROR, "unrecognized node type: %d",
>                    nodeTag(dencoding->arg));
>       }
> +     if (dmaxconn && dmaxconn->arg)
> +         dbmaxconn = intVal(dmaxconn->arg);
>
>       /* obtain OID of proposed owner */
>       if (dbowner)
> ***************
> *** 218,224 ****
>        * idea, so accept possibility of race to create.  We will check again
>        * after we grab the exclusive lock.
>        */
> !     if (get_db_info(dbname, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_DUPLICATE_DATABASE),
> --- 231,237 ----
>        * idea, so accept possibility of race to create.  We will check again
>        * after we grab the exclusive lock.
>        */
> !     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_DUPLICATE_DATABASE),
> ***************
> *** 231,238 ****
>           dbtemplate = "template1";        /* Default template database name */
>
>       if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
> !                      &src_istemplate, &src_allowconn, &src_lastsysoid,
> !                      &src_vacuumxid, &src_frozenxid, &src_deftablespace))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
>            errmsg("template database \"%s\" does not exist", dbtemplate)));
> --- 244,252 ----
>           dbtemplate = "template1";        /* Default template database name */
>
>       if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
> !                      &src_maxconn, &src_istemplate, &src_allowconn,
> !                      &src_lastsysoid, &src_vacuumxid, &src_frozenxid,
> !                      &src_deftablespace))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
>            errmsg("template database \"%s\" does not exist", dbtemplate)));
> ***************
> *** 266,271 ****
> --- 280,289 ----
>       if (encoding < 0)
>           encoding = src_encoding;
>
> +     /* If dbmaxconn is defaulted, use source's dbmaxconn */
> +     if (dbmaxconn < 0)
> +         dbmaxconn = src_maxconn;
> +
>       /* Some encodings are client only */
>       if (!PG_VALID_BE_ENCODING(encoding))
>           ereport(ERROR,
> ***************
> *** 461,467 ****
>       pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
>
>       /* Check to see if someone else created same DB name meanwhile. */
> !     if (get_db_info(dbname, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>       {
>           /* Don't hold lock while doing recursive remove */
> --- 479,485 ----
>       pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
>
>       /* Check to see if someone else created same DB name meanwhile. */
> !     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>       {
>           /* Don't hold lock while doing recursive remove */
> ***************
> *** 487,492 ****
> --- 505,511 ----
>       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_datmaxconn - 1] = Int32GetDatum(dbmaxconn);
>       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);
> ***************
> *** 587,593 ****
>        */
>       pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
>
> !     if (!get_db_info(dbname, &db_id, NULL, NULL,
>                        &db_istemplate, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
> --- 606,612 ----
>        */
>       pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
>
> !     if (!get_db_info(dbname, &db_id, NULL, NULL, NULL,
>                        &db_istemplate, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
> ***************
> *** 783,788 ****
> --- 802,892 ----
>
>
>   /*
> +  * ALTER DATABASE name ...
> +  */
> + void
> + AlterDatabase(AlterDatabaseStmt *stmt)
> + {
> +     Datum        new_record[Natts_pg_database];
> +     char        new_record_nulls[Natts_pg_database];
> +     char        new_record_repl[Natts_pg_database];
> +     Relation    rel;
> +     HeapTuple    tuple,
> +                 newtuple;
> +     ScanKeyData scankey;
> +     SysScanDesc scan;
> +     ListCell   *option;
> +     int            maxconn = -1;    /* Maximum connections allowed */
> +
> +     DefElem    *dmaxconn = NULL;
> +
> +     /* Extract options from the statement node tree */
> +     foreach(option, stmt->options)
> +     {
> +         DefElem    *defel = (DefElem *) lfirst(option);
> +
> +         if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
> +     }
> +
> +     if (dmaxconn)
> +         maxconn = intVal(dmaxconn->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 (!have_createdb_privilege())
> +         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 (maxconn >= 0)
> +     {
> +         new_record[Anum_pg_database_datmaxconn - 1] = Int32GetDatum(maxconn);
> +         new_record_repl[Anum_pg_database_datmaxconn - 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);
> + }
> +
> +
> + /*
>    * ALTER DATABASE name SET ...
>    */
>   void
> ***************
> *** 971,978 ****
>
>   static bool
>   get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
> !             Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace)
>   {
> --- 1075,1082 ----
>
>   static bool
>   get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
> !             bool *dbAllowConnP,    Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace)
>   {
> ***************
> *** 1017,1022 ****
> --- 1121,1129 ----
>           /* allowing connections? */
>           if (dbAllowConnP)
>               *dbAllowConnP = dbform->datallowconn;
> +         /* maximum connections */
> +         if (dbMaxConnP)
> +             *dbMaxConnP = dbform->datmaxconn;
>           /* last system OID used in database */
>           if (dbLastSysOidP)
>               *dbLastSysOidP = dbform->datlastsysoid;
> Index: src/backend/commands/user.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/commands/user.c,v
> retrieving revision 1.155
> diff -c -r1.155 user.c
> *** src/backend/commands/user.c    29 Jun 2005 20:34:13 -0000    1.155
> --- src/backend/commands/user.c    3 Jul 2005 22:47:54 -0000
> ***************
> *** 85,90 ****
> --- 85,91 ----
>       bool        createrole = false;        /* Can this user create roles? */
>       bool        createdb = false;        /* Can the user create databases? */
>       bool        canlogin = false;        /* Can this user login? */
> +     int            maxconn = 0;            /* 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 */
> ***************
> *** 94,99 ****
> --- 95,101 ----
>       DefElem    *dcreaterole = NULL;
>       DefElem    *dcreatedb = NULL;
>       DefElem    *dcanlogin = NULL;
> +     DefElem    *dmaxconn = NULL;
>       DefElem    *daddroleto = NULL;
>       DefElem    *drolemembers = NULL;
>       DefElem    *dadminmembers = NULL;
> ***************
> *** 155,160 ****
> --- 157,170 ----
>                            errmsg("conflicting or redundant options")));
>               dcanlogin = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "addroleto") == 0)
>           {
>               if (daddroleto)
> ***************
> *** 202,207 ****
> --- 212,230 ----
>           createdb = intVal(dcreatedb->arg) != 0;
>       if (dcanlogin)
>           canlogin = intVal(dcanlogin->arg) != 0;
> +     if (dmaxconn)
> +     {
> +         maxconn = intVal(dmaxconn->arg);
> +         if (maxconn < 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS must not be negative")));
> +
> +         if (!canlogin && maxconn > 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS can be specified only for roles which can login")));
> +     }
>       if (daddroleto)
>           addroleto = (List *) daddroleto->arg;
>       if (drolemembers)
> ***************
> *** 265,270 ****
> --- 288,294 ----
>       /* 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_rolmaxconn - 1] = Int32GetDatum(maxconn);
>
>       if (password)
>       {
> ***************
> *** 369,374 ****
> --- 393,399 ----
>       int            createrole = -1;        /* Can this user create roles? */
>       int            createdb = -1;            /* Can the user create databases? */
>       int            canlogin = -1;            /* Can this user login? */
> +     int            maxconn = -1;            /* maximum connections allowed */
>       List       *rolemembers = NIL;        /* roles to be added/removed */
>       char       *validUntil = NULL;        /* time the login is valid until */
>       DefElem    *dpassword = NULL;
> ***************
> *** 376,381 ****
> --- 401,407 ----
>       DefElem    *dcreaterole = NULL;
>       DefElem    *dcreatedb = NULL;
>       DefElem    *dcanlogin = NULL;
> +     DefElem    *dmaxconn = NULL;
>       DefElem    *drolemembers = NULL;
>       DefElem    *dvalidUntil = NULL;
>       Oid            roleid;
> ***************
> *** 431,436 ****
> --- 457,470 ----
>                            errmsg("conflicting or redundant options")));
>               dcanlogin = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "rolemembers") == 0 &&
>                    stmt->action != 0)
>           {
> ***************
> *** 463,468 ****
> --- 497,515 ----
>           createdb = intVal(dcreatedb->arg);
>       if (dcanlogin)
>           canlogin = intVal(dcanlogin->arg);
> +     if (dmaxconn)
> +     {
> +         maxconn = intVal(dmaxconn->arg);
> +         if (maxconn < 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS must not be negative")));
> +
> +         if (canlogin == 0 && maxconn > 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS can be specified only for roles which can login")));
> +     }
>       if (drolemembers)
>           rolemembers = (List *) drolemembers->arg;
>       if (dvalidUntil)
> ***************
> *** 502,507 ****
> --- 549,555 ----
>               !(createrole < 0 &&
>                 createdb < 0 &&
>                 canlogin < 0 &&
> +               maxconn < 0 &&
>                 !rolemembers &&
>                 !validUntil &&
>                 password &&
> ***************
> *** 553,558 ****
> --- 601,612 ----
>           new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r';
>       }
>
> +     if (maxconn >= 0)
> +     {
> +         new_record[Anum_pg_authid_rolmaxconn - 1] = Int32GetDatum(maxconn);
> +         new_record_repl[Anum_pg_authid_rolmaxconn - 1] = 'r';
> +     }
> +
>       /* password */
>       if (password)
>       {
> Index: src/backend/libpq/crypt.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/libpq/crypt.c,v
> retrieving revision 1.64
> diff -c -r1.64 crypt.c
> *** src/backend/libpq/crypt.c    29 Jun 2005 22:51:54 -0000    1.64
> --- src/backend/libpq/crypt.c    3 Jul 2005 22:47:57 -0000
> ***************
> *** 42,52 ****
>       if ((line = get_role_line(role)) == NULL)
>           return STATUS_ERROR;
>
> !     /* Skip over rolename */
>       token = list_head(*line);
>       if (token)
>           token = lnext(token);
>       if (token)
>       {
>           shadow_pass = (char *) lfirst(token);
>           token = lnext(token);
> --- 42,54 ----
>       if ((line = get_role_line(role)) == NULL)
>           return STATUS_ERROR;
>
> !     /* Skip over rolename and roleid */
>       token = list_head(*line);
>       if (token)
>           token = lnext(token);
>       if (token)
> +         token = lnext(token);
> +     if (token)
>       {
>           shadow_pass = (char *) lfirst(token);
>           token = lnext(token);
> Index: src/backend/libpq/hba.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/libpq/hba.c,v
> retrieving revision 1.144
> diff -c -r1.144 hba.c
> *** src/backend/libpq/hba.c    28 Jun 2005 22:16:45 -0000    1.144
> --- src/backend/libpq/hba.c    3 Jul 2005 22:48:12 -0000
> ***************
> *** 494,505 ****
>           return true;
>
>       /*
> !      * skip over the role name, password, valuntil, examine all the
>        * membership entries
>        */
> !     if (list_length(*line) < 4)
>           return false;
> !     for_each_cell(line_item, lnext(lnext(lnext(list_head(*line)))))
>       {
>           if (strcmp((char *) lfirst(line_item), role) == 0)
>               return true;
> --- 494,505 ----
>           return true;
>
>       /*
> !      * skip over the role name, id, password, valuntil, examine all the
>        * membership entries
>        */
> !     if (list_length(*line) < 5)
>           return false;
> !     for_each_cell(line_item, lnext(lnext(lnext(lnext(list_head(*line))))))
>       {
>           if (strcmp((char *) lfirst(line_item), role) == 0)
>               return true;
> Index: src/backend/nodes/copyfuncs.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
> retrieving revision 1.311
> diff -c -r1.311 copyfuncs.c
> *** src/backend/nodes/copyfuncs.c    2 Jul 2005 23:00:39 -0000    1.311
> --- src/backend/nodes/copyfuncs.c    3 Jul 2005 22:48:36 -0000
> ***************
> *** 2204,2209 ****
> --- 2204,2220 ----
>       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)
>   {
> ***************
> *** 3010,3015 ****
> --- 3021,3029 ----
>           case T_CreatedbStmt:
>               retval = _copyCreatedbStmt(from);
>               break;
> +         case T_AlterDatabaseStmt:
> +             retval = _copyAlterDatabaseStmt(from);
> +             break;
>           case T_AlterDatabaseSetStmt:
>               retval = _copyAlterDatabaseSetStmt(from);
>               break;
> Index: src/backend/nodes/equalfuncs.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
> retrieving revision 1.248
> diff -c -r1.248 equalfuncs.c
> *** src/backend/nodes/equalfuncs.c    2 Jul 2005 23:00:39 -0000    1.248
> --- src/backend/nodes/equalfuncs.c    3 Jul 2005 22:48:53 -0000
> ***************
> *** 1152,1157 ****
> --- 1152,1166 ----
>   }
>
>   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);
> ***************
> *** 2058,2063 ****
> --- 2067,2075 ----
>           case T_CreatedbStmt:
>               retval = _equalCreatedbStmt(a, b);
>               break;
> +         case T_AlterDatabaseStmt:
> +             retval = _equalAlterDatabaseStmt(a, b);
> +             break;
>           case T_AlterDatabaseSetStmt:
>               retval = _equalAlterDatabaseSetStmt(a, b);
>               break;
> Index: src/backend/parser/gram.y
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/parser/gram.y,v
> retrieving revision 2.501
> diff -c -r2.501 gram.y
> *** src/backend/parser/gram.y    29 Jun 2005 20:34:13 -0000    2.501
> --- src/backend/parser/gram.y    3 Jul 2005 22:50:20 -0000
> ***************
> *** 131,139 ****
>   }
>
>   %type <node>    stmt schema_stmt
> !         AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
> !         AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
> !         AlterRoleStmt AlterRoleSetStmt
>           AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
>           ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
>           CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
> --- 131,139 ----
>   }
>
>   %type <node>    stmt schema_stmt
> !         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
> !         AlterOwnerStmt AlterSeqStmt AlterTableStmt
> !         AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
>           AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
>           ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
>           CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
> ***************
> *** 165,172 ****
>
>   %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 <ival>    opt_lock lock_type cast_context
>   %type <boolean>    opt_force opt_or_replace
> --- 165,174 ----
>
>   %type <dbehavior>    opt_drop_behavior
>
> ! %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
> ***************
> *** 342,348 ****
>       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
>       CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
>       CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
>
> --- 344,350 ----
>       CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P
>       CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
>       CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
> !     COMMITTED CONNECTIONS CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
>       CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
>       CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
>
> ***************
> *** 373,379 ****
>       LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
>       LOCK_P LOGIN_P
>
> !     MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
>
>       NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
>       NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
> --- 375,381 ----
>       LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
>       LOCK_P LOGIN_P
>
> !     MATCH MAX MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
>
>       NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
>       NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
> ***************
> *** 486,492 ****
>           ;
>
>   stmt :
> !             AlterDatabaseSetStmt
>               | AlterDomainStmt
>               | AlterFunctionStmt
>               | AlterGroupStmt
> --- 488,495 ----
>           ;
>
>   stmt :
> !             AlterDatabaseStmt
> !             | AlterDatabaseSetStmt
>               | AlterDomainStmt
>               | AlterFunctionStmt
>               | AlterGroupStmt
> ***************
> *** 663,668 ****
> --- 666,675 ----
>                   {
>                       $$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
>                   }
> +             | MAX CONNECTIONS Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($3));
> +                 }
>               | IN_P ROLE name_list
>                   {
>                       $$ = makeDefElem("addroleto", (Node *)$3);
> ***************
> *** 4455,4460 ****
> --- 4462,4471 ----
>                   {
>                       $$ = makeDefElem("encoding", NULL);
>                   }
> +             | MAX CONNECTIONS opt_equal Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
> +                 }
>               | OWNER opt_equal name
>                   {
>                       $$ = makeDefElem("owner", (Node *)makeString($3));
> ***************
> *** 4481,4486 ****
> --- 4492,4507 ----
>    *
>    *****************************************************************************/
>
> + 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
>                   {
> ***************
> *** 4501,4506 ****
> --- 4522,4540 ----
>           ;
>
>
> + alterdb_opt_list:
> +             alterdb_opt_list alterdb_opt_item        { $$ = lappend($1, $2); }
> +             | /* EMPTY */                            { $$ = NIL; }
> +         ;
> +
> + alterdb_opt_item:
> +             MAX CONNECTIONS opt_equal Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
> +                 }
> +         ;
> +
> +
>   /*****************************************************************************
>    *
>    *        DROP DATABASE
> ***************
> *** 7941,7946 ****
> --- 7975,7981 ----
>               | COMMENT
>               | COMMIT
>               | COMMITTED
> +             | CONNECTIONS
>               | CONSTRAINTS
>               | CONVERSION_P
>               | COPY
> ***************
> *** 8009,8014 ****
> --- 8044,8050 ----
>               | LOCK_P
>               | LOGIN_P
>               | MATCH
> +             | MAX
>               | MAXVALUE
>               | MINUTE_P
>               | MINVALUE
> Index: src/backend/parser/keywords.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/parser/keywords.c,v
> retrieving revision 1.162
> diff -c -r1.162 keywords.c
> *** src/backend/parser/keywords.c    29 Jun 2005 20:34:14 -0000    1.162
> --- src/backend/parser/keywords.c    3 Jul 2005 22:50:24 -0000
> ***************
> *** 83,88 ****
> --- 83,89 ----
>       {"comment", COMMENT},
>       {"commit", COMMIT},
>       {"committed", COMMITTED},
> +     {"connections", CONNECTIONS},
>       {"constraint", CONSTRAINT},
>       {"constraints", CONSTRAINTS},
>       {"conversion", CONVERSION_P},
> ***************
> *** 203,208 ****
> --- 204,210 ----
>       {"lock", LOCK_P},
>       {"login", LOGIN_P},
>       {"match", MATCH},
> +     {"max", MAX},
>       {"maxvalue", MAXVALUE},
>       {"minute", MINUTE_P},
>       {"minvalue", MINVALUE},
> Index: src/backend/storage/ipc/procarray.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/storage/ipc/procarray.c,v
> retrieving revision 1.3
> diff -c -r1.3 procarray.c
> *** src/backend/storage/ipc/procarray.c    17 Jun 2005 22:32:45 -0000    1.3
> --- src/backend/storage/ipc/procarray.c    3 Jul 2005 22:50:36 -0000
> ***************
> *** 734,739 ****
> --- 734,790 ----
>   }
>
>
> + /*
> +  * 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 && 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 && proc->roleId == roleid)
> +             count++;
> +     }
> +
> +     LWLockRelease(ProcArrayLock);
> +
> +     return count;
> + }
> +
> +
>   #define XidCacheRemove(i) \
>       do { \
>           MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \
> Index: src/backend/storage/lmgr/proc.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
> retrieving revision 1.160
> diff -c -r1.160 proc.c
> *** src/backend/storage/lmgr/proc.c    17 Jun 2005 22:32:45 -0000    1.160
> --- src/backend/storage/lmgr/proc.c    3 Jul 2005 22:50:51 -0000
> ***************
> *** 254,259 ****
> --- 254,260 ----
>       MyProc->xmin = InvalidTransactionId;
>       MyProc->pid = MyProcPid;
>       MyProc->databaseId = MyDatabaseId;
> +     MyProc->roleId = GetSessionUserId();
>       MyProc->lwWaiting = false;
>       MyProc->lwExclusive = false;
>       MyProc->lwWaitLink = NULL;
> Index: src/backend/tcop/utility.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/tcop/utility.c,v
> retrieving revision 1.240
> diff -c -r1.240 utility.c
> *** src/backend/tcop/utility.c    30 Jun 2005 00:00:51 -0000    1.240
> --- src/backend/tcop/utility.c    3 Jul 2005 22:51:06 -0000
> ***************
> *** 275,280 ****
> --- 275,281 ----
>
>       switch (nodeTag(parsetree))
>       {
> +         case T_AlterDatabaseStmt:
>           case T_AlterDatabaseSetStmt:
>           case T_AlterDomainStmt:
>           case T_AlterFunctionStmt:
> ***************
> *** 788,793 ****
> --- 789,798 ----
>               createdb((CreatedbStmt *) parsetree);
>               break;
>
> +         case T_AlterDatabaseStmt:
> +             AlterDatabase((AlterDatabaseStmt *) parsetree);
> +             break;
> +
>           case T_AlterDatabaseSetStmt:
>               AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
>               break;
> ***************
> *** 1504,1509 ****
> --- 1509,1518 ----
>               tag = "CREATE DATABASE";
>               break;
>
> +         case T_AlterDatabaseStmt:
> +             tag = "ALTER DATABASE";
> +             break;
> +
>           case T_AlterDatabaseSetStmt:
>               tag = "ALTER DATABASE";
>               break;
> Index: src/backend/utils/init/flatfiles.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/flatfiles.c,v
> retrieving revision 1.11
> diff -c -r1.11 flatfiles.c
> *** src/backend/utils/init/flatfiles.c    29 Jun 2005 20:34:15 -0000    1.11
> --- src/backend/utils/init/flatfiles.c    3 Jul 2005 22:51:18 -0000
> ***************
> *** 629,635 ****
>               ListCell *mem;
>
>               fputs_quote(arole->rolname, fp);
> !             fputs(" ", fp);
>               fputs_quote(arole->rolpassword, fp);
>               fputs(" ", fp);
>               fputs_quote(arole->rolvaliduntil, fp);
> --- 629,635 ----
>               ListCell *mem;
>
>               fputs_quote(arole->rolname, fp);
> !             fprintf(fp, " %u ", arole->roleid);
>               fputs_quote(arole->rolpassword, fp);
>               fputs(" ", fp);
>               fputs_quote(arole->rolvaliduntil, fp);
> Index: src/backend/utils/init/miscinit.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/miscinit.c,v
> retrieving revision 1.144
> diff -c -r1.144 miscinit.c
> *** src/backend/utils/init/miscinit.c    28 Jun 2005 22:16:45 -0000    1.144
> --- src/backend/utils/init/miscinit.c    3 Jul 2005 22:51:29 -0000
> ***************
> *** 39,44 ****
> --- 39,45 ----
>   #include "utils/guc.h"
>   #include "utils/lsyscache.h"
>   #include "utils/syscache.h"
> + #include "storage/procarray.h"
>
>
>   ProcessingMode Mode = InitProcessing;
> ***************
> *** 347,352 ****
> --- 348,365 ----
>
>       SetSessionUserId(roleid);    /* sets CurrentUserId too */
>
> +     /*
> +      * Check connection limit for user
> +      */
> +     if (rform->rolmaxconn > 0 && !AuthenticatedUserIsSuperuser &&
> +             CountUserBackends(AuthenticatedUserId) > rform->rolmaxconn)
> +     {
> +         ereport(FATAL,
> +             (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
> +          errmsg("sorry, too many clients already for role \"%s\"",
> +             rolename)));
> +     }
> +
>       /* Record username and superuser status as GUC settings too */
>       SetConfigOption("session_authorization", rolename,
>                       PGC_BACKEND, PGC_S_OVERRIDE);
> Index: src/backend/utils/init/postinit.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/postinit.c,v
> retrieving revision 1.151
> diff -c -r1.151 postinit.c
> *** src/backend/utils/init/postinit.c    28 Jun 2005 19:51:23 -0000    1.151
> --- src/backend/utils/init/postinit.c    3 Jul 2005 22:51:37 -0000
> ***************
> *** 47,52 ****
> --- 47,53 ----
>
>
>   static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace);
> + static bool FindMyRole(const char *name, Oid *role_id);
>   static void ReverifyMyDatabase(const char *name);
>   static void InitCommunication(void);
>   static void ShutdownPostgres(int code, Datum arg);
> ***************
> *** 101,106 ****
> --- 102,136 ----
>   }
>
>   /*
> +  * Get roleid from flatfiles
> +  *
> +  * We need this because we need to know userid before
> +  * InitProcess() is called
> +  */
> + static bool
> + FindMyRole(const char *name, Oid *role_id)
> + {
> +     List      **line;
> +     ListCell   *token;
> +
> +     if ((line = get_role_line(name)) == NULL)
> +         ereport(FATAL,
> +                 (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
> +                  errmsg("could not find role \"%s\"", name)));
> +
> +     token = list_head(*line);
> +     if (token)
> +         token = lnext(token);
> +     if (token)
> +     {
> +         *role_id = atoi((char*)lfirst(token));
> +         return true;
> +     }
> +
> +     return false;
> + }
> +
> + /*
>    * ReverifyMyDatabase -- recheck info obtained by FindMyDatabase
>    *
>    * Since FindMyDatabase cannot lock pg_database, the information it read
> ***************
> *** 166,182 ****
>                           name, MyDatabaseId)));
>       }
>
> -     /*
> -      * 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;")
> -      */
>       dbform = (Form_pg_database) GETSTRUCT(tup);
> !     if (IsUnderPostmaster && !dbform->datallowconn)
> !         ereport(FATAL,
> !                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> !          errmsg("database \"%s\" is not currently accepting connections",
>                   name)));
>
>       /*
>        * OK, we're golden.  Next to-do item is to save the encoding
> --- 196,230 ----
>                           name, MyDatabaseId)));
>       }
>
>       dbform = (Form_pg_database) GETSTRUCT(tup);
> !     if (IsUnderPostmaster)
> !     {
> !         /*
> !          * 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;")
> !          */
> !         if (!dbform->datallowconn)
> !         {
> !             ereport(FATAL,
> !                     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> !              errmsg("database \"%s\" is not currently accepting connections",
> !                     name)));
> !         }
> !
> !         /*
> !          * Here we check cxonenction limit for this database
> !          */
> !         if (dbform->datmaxconn > 0 && !superuser() &&
> !                 CountDBBackends(MyDatabaseId) > dbform->datmaxconn)
> !         {
> !             ereport(FATAL,
> !                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
> !              errmsg("sorry, too many clients already for database \"%s\"",
>                   name)));
> +         }
> +     }
> +
>
>       /*
>        * OK, we're golden.  Next to-do item is to save the encoding
> ***************
> *** 352,357 ****
> --- 400,424 ----
>        */
>
>       /*
> +      * We need to know roleid in InitProcess() so we have read it from
> +      * flatfile, real user inicialization is done later
> +      */
> +     if (IsUnderPostmaster)
> +     {
> +         Oid roleid;
> +
> +         if (!FindMyRole(username, &roleid))
> +             ereport(FATAL,
> +                     (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
> +                      errmsg("role \"%s\" does not exist",
> +                             username)));
> +
> +         SetSessionUserId(roleid);
> +     }
> +     else
> +         SetSessionUserId(BOOTSTRAP_SUPERUSERID);
> +
> +     /*
>        * Set up my per-backend PGPROC struct in shared memory.    (We need
>        * to know MyDatabaseId before we can do this, since it's entered into
>        * the PGPROC struct.)
> Index: src/include/catalog/pg_authid.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_authid.h,v
> retrieving revision 1.1
> diff -c -r1.1 pg_authid.h
> *** src/include/catalog/pg_authid.h    28 Jun 2005 05:09:05 -0000    1.1
> --- src/include/catalog/pg_authid.h    3 Jul 2005 22:51:50 -0000
> ***************
> *** 48,53 ****
> --- 48,54 ----
>       bool        rolcreatedb;    /* allowed to create databases? */
>       bool        rolcatupdate;    /* allowed to alter catalogs manually? */
>       bool        rolcanlogin;    /* allowed to log in as session user? */
> +     int4        rolmaxconn;        /* maximum connections allowed */
>
>       /* remaining fields may be null; use heap_getattr to read them! */
>       text        rolpassword;    /* password, if any */
> ***************
> *** 69,84 ****
>    *        compiler constants for pg_authid
>    * ----------------
>    */
> ! #define Natts_pg_authid                    9
>   #define Anum_pg_authid_rolname            1
>   #define Anum_pg_authid_rolsuper            2
>   #define Anum_pg_authid_rolcreaterole    3
>   #define Anum_pg_authid_rolcreatedb        4
>   #define Anum_pg_authid_rolcatupdate        5
>   #define Anum_pg_authid_rolcanlogin        6
> ! #define Anum_pg_authid_rolpassword        7
> ! #define Anum_pg_authid_rolvaliduntil    8
> ! #define Anum_pg_authid_rolconfig        9
>
>   /* ----------------
>    *        initial contents of pg_authid
> --- 70,86 ----
>    *        compiler constants for pg_authid
>    * ----------------
>    */
> ! #define Natts_pg_authid                    10
>   #define Anum_pg_authid_rolname            1
>   #define Anum_pg_authid_rolsuper            2
>   #define Anum_pg_authid_rolcreaterole    3
>   #define Anum_pg_authid_rolcreatedb        4
>   #define Anum_pg_authid_rolcatupdate        5
>   #define Anum_pg_authid_rolcanlogin        6
> ! #define Anum_pg_authid_rolmaxconn        7
> ! #define Anum_pg_authid_rolpassword        8
> ! #define Anum_pg_authid_rolvaliduntil    9
> ! #define Anum_pg_authid_rolconfig        10
>
>   /* ----------------
>    *        initial contents of pg_authid
> ***************
> *** 87,93 ****
>    * user choices.
>    * ----------------
>    */
> ! DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
>
>   #define BOOTSTRAP_SUPERUSERID 10
>
> --- 89,95 ----
>    * user choices.
>    * ----------------
>    */
> ! DATA(insert OID = 10 ( "POSTGRES" t t t t t 0 _null_ _null_ _null_ ));
>
>   #define BOOTSTRAP_SUPERUSERID 10
>
> Index: src/include/catalog/pg_database.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_database.h,v
> retrieving revision 1.36
> diff -c -r1.36 pg_database.h
> *** src/include/catalog/pg_database.h    28 Jun 2005 05:09:06 -0000    1.36
> --- src/include/catalog/pg_database.h    3 Jul 2005 22:51:51 -0000
> ***************
> *** 40,45 ****
> --- 40,46 ----
>       int4        encoding;        /* character encoding */
>       bool        datistemplate;    /* allowed as CREATE DATABASE template? */
>       bool        datallowconn;    /* new connections allowed? */
> +     int4        datmaxconn;        /* maximum connections allowed */
>       Oid            datlastsysoid;    /* highest OID to consider a system OID */
>       TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
>       TransactionId datfrozenxid; /* all XIDs before this are frozen */
> ***************
> *** 59,78 ****
>    *        compiler constants for pg_database
>    * ----------------
>    */
> ! #define Natts_pg_database                11
>   #define Anum_pg_database_datname        1
>   #define Anum_pg_database_datdba            2
>   #define Anum_pg_database_encoding        3
>   #define Anum_pg_database_datistemplate    4
>   #define Anum_pg_database_datallowconn    5
> ! #define Anum_pg_database_datlastsysoid    6
> ! #define Anum_pg_database_datvacuumxid    7
> ! #define Anum_pg_database_datfrozenxid    8
> ! #define Anum_pg_database_dattablespace    9
> ! #define Anum_pg_database_datconfig        10
> ! #define Anum_pg_database_datacl            11
>
> ! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 1663 _null_ _null_ ));
>   DESCR("Default template database");
>   #define TemplateDbOid            1
>
> --- 60,80 ----
>    *        compiler constants for pg_database
>    * ----------------
>    */
> ! #define Natts_pg_database                12
>   #define Anum_pg_database_datname        1
>   #define Anum_pg_database_datdba            2
>   #define Anum_pg_database_encoding        3
>   #define Anum_pg_database_datistemplate    4
>   #define Anum_pg_database_datallowconn    5
> ! #define Anum_pg_database_datmaxconn        6
> ! #define Anum_pg_database_datlastsysoid    7
> ! #define Anum_pg_database_datvacuumxid    8
> ! #define Anum_pg_database_datfrozenxid    9
> ! #define Anum_pg_database_dattablespace    10
> ! #define Anum_pg_database_datconfig        11
> ! #define Anum_pg_database_datacl            12
>
> ! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 0 1663 _null_ _null_ ));
>   DESCR("Default template database");
>   #define TemplateDbOid            1
>
> Index: src/include/commands/dbcommands.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/commands/dbcommands.h,v
> retrieving revision 1.39
> diff -c -r1.39 dbcommands.h
> *** src/include/commands/dbcommands.h    28 Jun 2005 05:09:12 -0000    1.39
> --- src/include/commands/dbcommands.h    3 Jul 2005 22:51:53 -0000
> ***************
> *** 64,69 ****
> --- 64,70 ----
>   extern void createdb(const CreatedbStmt *stmt);
>   extern void dropdb(const char *dbname);
>   extern void RenameDatabase(const char *oldname, const char *newname);
> + extern void AlterDatabase(AlterDatabaseStmt *stmt);
>   extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
>   extern void AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
>
> Index: src/include/nodes/nodes.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/nodes/nodes.h,v
> retrieving revision 1.172
> diff -c -r1.172 nodes.h
> *** src/include/nodes/nodes.h    28 Jun 2005 05:09:13 -0000    1.172
> --- src/include/nodes/nodes.h    3 Jul 2005 22:52:00 -0000
> ***************
> *** 270,275 ****
> --- 270,276 ----
>       T_ReindexStmt,
>       T_CheckPointStmt,
>       T_CreateSchemaStmt,
> +     T_AlterDatabaseStmt,
>       T_AlterDatabaseSetStmt,
>       T_AlterRoleSetStmt,
>       T_CreateConversionStmt,
> Index: src/include/nodes/parsenodes.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
> retrieving revision 1.285
> diff -c -r1.285 parsenodes.h
> *** src/include/nodes/parsenodes.h    28 Jun 2005 19:51:24 -0000    1.285
> --- src/include/nodes/parsenodes.h    3 Jul 2005 22:52:25 -0000
> ***************
> *** 1611,1616 ****
> --- 1611,1623 ----
>    *    Alter Database
>    * ----------------------
>    */
> + typedef struct AlterDatabaseStmt
> + {
> +     NodeTag        type;
> +     char       *dbname;            /* name of database to alter */
> +     List       *options;        /* List of DefElem nodes */
> + } AlterDatabaseStmt;
> +
>   typedef struct AlterDatabaseSetStmt
>   {
>       NodeTag        type;
> Index: src/include/storage/proc.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/storage/proc.h,v
> retrieving revision 1.79
> diff -c -r1.79 proc.h
> *** src/include/storage/proc.h    17 Jun 2005 22:32:50 -0000    1.79
> --- src/include/storage/proc.h    3 Jul 2005 22:52:29 -0000
> ***************
> *** 71,76 ****
> --- 71,77 ----
>
>       int            pid;            /* This backend's process id, or 0 */
>       Oid            databaseId;        /* OID of database this backend is using */
> +     Oid            roleId;            /* OID of role using conencted to backend */
>
>       /* Info about LWLock the process is currently waiting for, if any. */
>       bool        lwWaiting;        /* true if waiting for an LW lock */
> Index: src/include/storage/procarray.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/storage/procarray.h,v
> retrieving revision 1.2
> diff -c -r1.2 procarray.h
> *** src/include/storage/procarray.h    17 Jun 2005 22:32:50 -0000    1.2
> --- src/include/storage/procarray.h    3 Jul 2005 22:52:30 -0000
> ***************
> *** 31,36 ****
> --- 31,38 ----
>   extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);
>
>   extern int    CountActiveBackends(void);
> + extern int    CountDBBackends(Oid databaseid);
> + extern int    CountUserBackends(Oid roleid);
>
>   extern void XidCacheRemoveRunningXids(TransactionId xid,
>                             int nxids, TransactionId *xids);
> Index: src/tools/pgindent/pgindent
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/tools/pgindent/pgindent,v
> retrieving revision 1.75
> diff -c -r1.75 pgindent
> *** src/tools/pgindent/pgindent    28 Jun 2005 23:55:30 -0000    1.75
> --- src/tools/pgindent/pgindent    3 Jul 2005 22:53:03 -0000
> ***************
> *** 177,182 ****
> --- 177,183 ----
>   -TAllocSetContext \
>   -TAllocateDesc \
>   -TAllocateDescKind \
> + -TAlterDatabaseStmt \
>   -TAlterDatabaseSetStmt \
>   -TAlterDomainStmt \
>   -TAlterFunctionStmt \

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Bruce Momjian napsal(a):

>I am ready to apply this patch.  Would you make the additional changes
>you suggested?  Is there any way to see the limits except to query
>pg_authid?
>
Yes I will - pg_dump is already done (I attached it because it should be
aplied with orginal patch), documentation depends partly on roles doc so
it will prolly have to wait.

I also added limit to pg_roles and pg_shadow views when I was patching
pg_dump so you can get it from them.

--
Regards
Petr Jelinek (PJMODOS)

Index: src/backend/catalog/system_views.sql
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/catalog/system_views.sql,v
retrieving revision 1.16
diff -c -r1.16 system_views.sql
*** src/backend/catalog/system_views.sql    28 Jun 2005 05:08:52 -0000    1.16
--- src/backend/catalog/system_views.sql    24 Jul 2005 12:22:08 -0000
***************
*** 14,19 ****
--- 14,20 ----
          rolcreatedb,
          rolcatupdate,
          rolcanlogin,
+         rolmaxconn,
          '********'::text as rolpassword,
          rolvaliduntil,
          rolconfig
***************
*** 26,31 ****
--- 27,33 ----
          rolcreatedb AS usecreatedb,
          rolsuper AS usesuper,
          rolcatupdate AS usecatupd,
+         rolmaxconn AS usemaxconn,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          rolconfig AS useconfig
Index: src/bin/pg_dump/pg_dumpall.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/bin/pg_dump/pg_dumpall.c,v
retrieving revision 1.64
diff -c -r1.64 pg_dumpall.c
*** src/bin/pg_dump/pg_dumpall.c    18 Jul 2005 19:12:09 -0000    1.64
--- src/bin/pg_dump/pg_dumpall.c    24 Jul 2005 12:22:35 -0000
***************
*** 394,409 ****
      PGresult   *res;
      int            i;

!     if (server_version >= 70100)
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template0')) AS clusterowner
"
                             "FROM pg_shadow");
      else
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template1')) AS clusterowner
"
                             "FROM pg_shadow");

--- 394,415 ----
      PGresult   *res;
      int            i;

!     if (server_version >= 80100)
!         res = executeQuery(conn,
!                         "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, usemaxconn, "
!                            "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template0')) AS clusterowner
"
!                            "FROM pg_shadow");
!     else if (server_version >= 70100)
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, '0' AS usemaxconn, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template0')) AS clusterowner
"
                             "FROM pg_shadow");
      else
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, '0' AS usemaxconn, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template1')) AS clusterowner
"
                             "FROM pg_shadow");

***************
*** 453,458 ****
--- 459,468 ----
              appendPQExpBuffer(buf, " VALID UNTIL '%s'",
                                PQgetvalue(res, i, 5));

+         if (strcmp(PQgetvalue(res, i, 6), "0") != 0)
+             appendPQExpBuffer(buf, " MAX CONNECTIONS '%s'",
+                               PQgetvalue(res, i, 6));
+
          appendPQExpBuffer(buf, ";\n");

          printf("%s", buf->data);
***************
*** 612,623 ****

      printf("--\n-- Database creation\n--\n\n");

!     if (server_version >= 80000)
          res = executeQuery(conn,
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, "
                             "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
--- 622,642 ----

      printf("--\n-- Database creation\n--\n\n");

!     if (server_version >= 80100)
          res = executeQuery(conn,
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, datmaxconn, "
!                            "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
!         "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
!                            "WHERE datallowconn ORDER BY 1");
!     else if (server_version >= 80000)
!         res = executeQuery(conn,
!                            "SELECT datname, "
!                            "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
!                            "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, '0' as datmaxconn, "
                             "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
***************
*** 626,632 ****
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, "
                             "'pg_default' AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
--- 645,651 ----
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, '0' as datmaxconn, "
                             "'pg_default' AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
***************
*** 637,643 ****
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "(select usename from pg_shadow where usesysid=(select datdba from pg_database where
datname='template0')))," 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, '' as datacl, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "WHERE datallowconn ORDER BY 1");
--- 656,662 ----
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "(select usename from pg_shadow where usesysid=(select datdba from pg_database where
datname='template0')))," 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, '' as datacl, '0' as datmaxconn, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "WHERE datallowconn ORDER BY 1");
***************
*** 652,658 ****
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "pg_encoding_to_char(d.encoding), "
                             "'f' as datistemplate, "
!                            "'' as datacl, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "ORDER BY 1");
--- 671,677 ----
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "pg_encoding_to_char(d.encoding), "
                             "'f' as datistemplate, "
!                            "'' as datacl, '0' as datmaxconn, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "ORDER BY 1");
***************
*** 666,672 ****
          char       *dbencoding = PQgetvalue(res, i, 2);
          char       *dbistemplate = PQgetvalue(res, i, 3);
          char       *dbacl = PQgetvalue(res, i, 4);
!         char       *dbtablespace = PQgetvalue(res, i, 5);
          char       *fdbname;

          buf = createPQExpBuffer();
--- 685,692 ----
          char       *dbencoding = PQgetvalue(res, i, 2);
          char       *dbistemplate = PQgetvalue(res, i, 3);
          char       *dbacl = PQgetvalue(res, i, 4);
!         char       *dbmaxconn = PQgetvalue(res, i, 5);
!         char       *dbtablespace = PQgetvalue(res, i, 6);
          char       *fdbname;

          buf = createPQExpBuffer();
***************
*** 698,703 ****
--- 718,727 ----
                  appendPQExpBuffer(buf, " TABLESPACE = %s",
                                    fmtId(dbtablespace));

+             if (strcmp(dbmaxconn, "0") != 0)
+                 appendPQExpBuffer(buf, " MAX CONNECTIONS = %s",
+                                   fmtId(dbmaxconn));
+
              appendPQExpBuffer(buf, ";\n");

              if (strcmp(dbistemplate, "t") == 0)

Re: per user/database connections limit again

From
Bruce Momjian
Date:
The new syntax for this command is CREATE/ALTER DATABASE/USER:

                | MAX CONNECTIONS Iconst

This adds 'max' as a keyword, though at a fairly unreserved level, I
think.  Should we use the syntax LIMIT CONNECTIONS so we don't have to
add MAX as a keyword at all?

---------------------------------------------------------------------------

Petr Jelinek wrote:
> Stephen Frost wrote:
>
> >This should almost certainly be a pg_database_ownercheck() call instead.
> >
> >
> Right there wasn't pg_database_ownercheck at the time I was writing it,
> fixed
>
> >The rest needs to be updated for roles, but looks like it should be
> >pretty easy to do.  Much of it just needs to be repatched, the parts
> >that do need to be changed look to be pretty simple changes.
> >
> >
> Done.
>
> >I believe the use of SessionUserId is probably correct in this patch.
> >This does mean that this patch will only be for canlogin roles, but that
> >seems like it's probably correct.  Handling roles w/ members would
> >require much more thought.
> >
> >
> I don't think that having max connection for roles w/ members is doable
> because you can have 5 roles which has 1 user as member and each role
> has different number of max conections and there is no right way to
> decide what to do.
>
>
> New version which works with roles is attached (diffed against cvs),
> everything else is mostly same.
> I also had to readd roleid to flatfiles because I need it in
> InitProcess() function.
>
> --
> Regards
> Petr Jelinek (PJMODOS)
>
>

> Index: src/backend/commands/dbcommands.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/commands/dbcommands.c,v
> retrieving revision 1.164
> diff -c -r1.164 dbcommands.c
> *** src/backend/commands/dbcommands.c    30 Jun 2005 00:00:50 -0000    1.164
> --- src/backend/commands/dbcommands.c    3 Jul 2005 22:47:39 -0000
> ***************
> *** 53,60 ****
>
>   /* non-export function prototypes */
>   static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
> !             Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace);
>   static bool have_createdb_privilege(void);
> --- 53,60 ----
>
>   /* non-export function prototypes */
>   static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
> !             bool *dbAllowConnP,    Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace);
>   static bool have_createdb_privilege(void);
> ***************
> *** 74,79 ****
> --- 74,80 ----
>       int            src_encoding;
>       bool        src_istemplate;
>       bool        src_allowconn;
> +     int            src_maxconn;
>       Oid            src_lastsysoid;
>       TransactionId src_vacuumxid;
>       TransactionId src_frozenxid;
> ***************
> *** 91,100 ****
> --- 92,103 ----
>       DefElem    *downer = NULL;
>       DefElem    *dtemplate = NULL;
>       DefElem    *dencoding = NULL;
> +     DefElem    *dmaxconn = NULL;
>       char       *dbname = stmt->dbname;
>       char       *dbowner = NULL;
>       const char *dbtemplate = NULL;
>       int            encoding = -1;
> +     int            dbmaxconn = -1;
>
>   #ifndef WIN32
>       char        buf[2 * MAXPGPATH + 100];
> ***************
> *** 140,145 ****
> --- 143,156 ----
>                            errmsg("conflicting or redundant options")));
>               dencoding = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "location") == 0)
>           {
>               ereport(WARNING,
> ***************
> *** 185,190 ****
> --- 196,203 ----
>               elog(ERROR, "unrecognized node type: %d",
>                    nodeTag(dencoding->arg));
>       }
> +     if (dmaxconn && dmaxconn->arg)
> +         dbmaxconn = intVal(dmaxconn->arg);
>
>       /* obtain OID of proposed owner */
>       if (dbowner)
> ***************
> *** 218,224 ****
>        * idea, so accept possibility of race to create.  We will check again
>        * after we grab the exclusive lock.
>        */
> !     if (get_db_info(dbname, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_DUPLICATE_DATABASE),
> --- 231,237 ----
>        * idea, so accept possibility of race to create.  We will check again
>        * after we grab the exclusive lock.
>        */
> !     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_DUPLICATE_DATABASE),
> ***************
> *** 231,238 ****
>           dbtemplate = "template1";        /* Default template database name */
>
>       if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
> !                      &src_istemplate, &src_allowconn, &src_lastsysoid,
> !                      &src_vacuumxid, &src_frozenxid, &src_deftablespace))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
>            errmsg("template database \"%s\" does not exist", dbtemplate)));
> --- 244,252 ----
>           dbtemplate = "template1";        /* Default template database name */
>
>       if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
> !                      &src_maxconn, &src_istemplate, &src_allowconn,
> !                      &src_lastsysoid, &src_vacuumxid, &src_frozenxid,
> !                      &src_deftablespace))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
>            errmsg("template database \"%s\" does not exist", dbtemplate)));
> ***************
> *** 266,271 ****
> --- 280,289 ----
>       if (encoding < 0)
>           encoding = src_encoding;
>
> +     /* If dbmaxconn is defaulted, use source's dbmaxconn */
> +     if (dbmaxconn < 0)
> +         dbmaxconn = src_maxconn;
> +
>       /* Some encodings are client only */
>       if (!PG_VALID_BE_ENCODING(encoding))
>           ereport(ERROR,
> ***************
> *** 461,467 ****
>       pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
>
>       /* Check to see if someone else created same DB name meanwhile. */
> !     if (get_db_info(dbname, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>       {
>           /* Don't hold lock while doing recursive remove */
> --- 479,485 ----
>       pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);
>
>       /* Check to see if someone else created same DB name meanwhile. */
> !     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
>                       NULL, NULL, NULL, NULL, NULL, NULL))
>       {
>           /* Don't hold lock while doing recursive remove */
> ***************
> *** 487,492 ****
> --- 505,511 ----
>       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_datmaxconn - 1] = Int32GetDatum(dbmaxconn);
>       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);
> ***************
> *** 587,593 ****
>        */
>       pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
>
> !     if (!get_db_info(dbname, &db_id, NULL, NULL,
>                        &db_istemplate, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
> --- 606,612 ----
>        */
>       pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);
>
> !     if (!get_db_info(dbname, &db_id, NULL, NULL, NULL,
>                        &db_istemplate, NULL, NULL, NULL, NULL, NULL))
>           ereport(ERROR,
>                   (errcode(ERRCODE_UNDEFINED_DATABASE),
> ***************
> *** 783,788 ****
> --- 802,892 ----
>
>
>   /*
> +  * ALTER DATABASE name ...
> +  */
> + void
> + AlterDatabase(AlterDatabaseStmt *stmt)
> + {
> +     Datum        new_record[Natts_pg_database];
> +     char        new_record_nulls[Natts_pg_database];
> +     char        new_record_repl[Natts_pg_database];
> +     Relation    rel;
> +     HeapTuple    tuple,
> +                 newtuple;
> +     ScanKeyData scankey;
> +     SysScanDesc scan;
> +     ListCell   *option;
> +     int            maxconn = -1;    /* Maximum connections allowed */
> +
> +     DefElem    *dmaxconn = NULL;
> +
> +     /* Extract options from the statement node tree */
> +     foreach(option, stmt->options)
> +     {
> +         DefElem    *defel = (DefElem *) lfirst(option);
> +
> +         if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
> +     }
> +
> +     if (dmaxconn)
> +         maxconn = intVal(dmaxconn->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 (!have_createdb_privilege())
> +         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 (maxconn >= 0)
> +     {
> +         new_record[Anum_pg_database_datmaxconn - 1] = Int32GetDatum(maxconn);
> +         new_record_repl[Anum_pg_database_datmaxconn - 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);
> + }
> +
> +
> + /*
>    * ALTER DATABASE name SET ...
>    */
>   void
> ***************
> *** 971,978 ****
>
>   static bool
>   get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
> !             Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace)
>   {
> --- 1075,1082 ----
>
>   static bool
>   get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
> !             int *encodingP, int *dbMaxConnP, bool *dbIsTemplateP,
> !             bool *dbAllowConnP,    Oid *dbLastSysOidP,
>               TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
>               Oid *dbTablespace)
>   {
> ***************
> *** 1017,1022 ****
> --- 1121,1129 ----
>           /* allowing connections? */
>           if (dbAllowConnP)
>               *dbAllowConnP = dbform->datallowconn;
> +         /* maximum connections */
> +         if (dbMaxConnP)
> +             *dbMaxConnP = dbform->datmaxconn;
>           /* last system OID used in database */
>           if (dbLastSysOidP)
>               *dbLastSysOidP = dbform->datlastsysoid;
> Index: src/backend/commands/user.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/commands/user.c,v
> retrieving revision 1.155
> diff -c -r1.155 user.c
> *** src/backend/commands/user.c    29 Jun 2005 20:34:13 -0000    1.155
> --- src/backend/commands/user.c    3 Jul 2005 22:47:54 -0000
> ***************
> *** 85,90 ****
> --- 85,91 ----
>       bool        createrole = false;        /* Can this user create roles? */
>       bool        createdb = false;        /* Can the user create databases? */
>       bool        canlogin = false;        /* Can this user login? */
> +     int            maxconn = 0;            /* 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 */
> ***************
> *** 94,99 ****
> --- 95,101 ----
>       DefElem    *dcreaterole = NULL;
>       DefElem    *dcreatedb = NULL;
>       DefElem    *dcanlogin = NULL;
> +     DefElem    *dmaxconn = NULL;
>       DefElem    *daddroleto = NULL;
>       DefElem    *drolemembers = NULL;
>       DefElem    *dadminmembers = NULL;
> ***************
> *** 155,160 ****
> --- 157,170 ----
>                            errmsg("conflicting or redundant options")));
>               dcanlogin = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "addroleto") == 0)
>           {
>               if (daddroleto)
> ***************
> *** 202,207 ****
> --- 212,230 ----
>           createdb = intVal(dcreatedb->arg) != 0;
>       if (dcanlogin)
>           canlogin = intVal(dcanlogin->arg) != 0;
> +     if (dmaxconn)
> +     {
> +         maxconn = intVal(dmaxconn->arg);
> +         if (maxconn < 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS must not be negative")));
> +
> +         if (!canlogin && maxconn > 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS can be specified only for roles which can login")));
> +     }
>       if (daddroleto)
>           addroleto = (List *) daddroleto->arg;
>       if (drolemembers)
> ***************
> *** 265,270 ****
> --- 288,294 ----
>       /* 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_rolmaxconn - 1] = Int32GetDatum(maxconn);
>
>       if (password)
>       {
> ***************
> *** 369,374 ****
> --- 393,399 ----
>       int            createrole = -1;        /* Can this user create roles? */
>       int            createdb = -1;            /* Can the user create databases? */
>       int            canlogin = -1;            /* Can this user login? */
> +     int            maxconn = -1;            /* maximum connections allowed */
>       List       *rolemembers = NIL;        /* roles to be added/removed */
>       char       *validUntil = NULL;        /* time the login is valid until */
>       DefElem    *dpassword = NULL;
> ***************
> *** 376,381 ****
> --- 401,407 ----
>       DefElem    *dcreaterole = NULL;
>       DefElem    *dcreatedb = NULL;
>       DefElem    *dcanlogin = NULL;
> +     DefElem    *dmaxconn = NULL;
>       DefElem    *drolemembers = NULL;
>       DefElem    *dvalidUntil = NULL;
>       Oid            roleid;
> ***************
> *** 431,436 ****
> --- 457,470 ----
>                            errmsg("conflicting or redundant options")));
>               dcanlogin = defel;
>           }
> +         else if (strcmp(defel->defname, "maxconnections") == 0)
> +         {
> +             if (dmaxconn)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             dmaxconn = defel;
> +         }
>           else if (strcmp(defel->defname, "rolemembers") == 0 &&
>                    stmt->action != 0)
>           {
> ***************
> *** 463,468 ****
> --- 497,515 ----
>           createdb = intVal(dcreatedb->arg);
>       if (dcanlogin)
>           canlogin = intVal(dcanlogin->arg);
> +     if (dmaxconn)
> +     {
> +         maxconn = intVal(dmaxconn->arg);
> +         if (maxconn < 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS must not be negative")));
> +
> +         if (canlogin == 0 && maxconn > 0)
> +             ereport(ERROR,
> +                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> +                      errmsg("MAX CONNECTIONS can be specified only for roles which can login")));
> +     }
>       if (drolemembers)
>           rolemembers = (List *) drolemembers->arg;
>       if (dvalidUntil)
> ***************
> *** 502,507 ****
> --- 549,555 ----
>               !(createrole < 0 &&
>                 createdb < 0 &&
>                 canlogin < 0 &&
> +               maxconn < 0 &&
>                 !rolemembers &&
>                 !validUntil &&
>                 password &&
> ***************
> *** 553,558 ****
> --- 601,612 ----
>           new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r';
>       }
>
> +     if (maxconn >= 0)
> +     {
> +         new_record[Anum_pg_authid_rolmaxconn - 1] = Int32GetDatum(maxconn);
> +         new_record_repl[Anum_pg_authid_rolmaxconn - 1] = 'r';
> +     }
> +
>       /* password */
>       if (password)
>       {
> Index: src/backend/libpq/crypt.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/libpq/crypt.c,v
> retrieving revision 1.64
> diff -c -r1.64 crypt.c
> *** src/backend/libpq/crypt.c    29 Jun 2005 22:51:54 -0000    1.64
> --- src/backend/libpq/crypt.c    3 Jul 2005 22:47:57 -0000
> ***************
> *** 42,52 ****
>       if ((line = get_role_line(role)) == NULL)
>           return STATUS_ERROR;
>
> !     /* Skip over rolename */
>       token = list_head(*line);
>       if (token)
>           token = lnext(token);
>       if (token)
>       {
>           shadow_pass = (char *) lfirst(token);
>           token = lnext(token);
> --- 42,54 ----
>       if ((line = get_role_line(role)) == NULL)
>           return STATUS_ERROR;
>
> !     /* Skip over rolename and roleid */
>       token = list_head(*line);
>       if (token)
>           token = lnext(token);
>       if (token)
> +         token = lnext(token);
> +     if (token)
>       {
>           shadow_pass = (char *) lfirst(token);
>           token = lnext(token);
> Index: src/backend/libpq/hba.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/libpq/hba.c,v
> retrieving revision 1.144
> diff -c -r1.144 hba.c
> *** src/backend/libpq/hba.c    28 Jun 2005 22:16:45 -0000    1.144
> --- src/backend/libpq/hba.c    3 Jul 2005 22:48:12 -0000
> ***************
> *** 494,505 ****
>           return true;
>
>       /*
> !      * skip over the role name, password, valuntil, examine all the
>        * membership entries
>        */
> !     if (list_length(*line) < 4)
>           return false;
> !     for_each_cell(line_item, lnext(lnext(lnext(list_head(*line)))))
>       {
>           if (strcmp((char *) lfirst(line_item), role) == 0)
>               return true;
> --- 494,505 ----
>           return true;
>
>       /*
> !      * skip over the role name, id, password, valuntil, examine all the
>        * membership entries
>        */
> !     if (list_length(*line) < 5)
>           return false;
> !     for_each_cell(line_item, lnext(lnext(lnext(lnext(list_head(*line))))))
>       {
>           if (strcmp((char *) lfirst(line_item), role) == 0)
>               return true;
> Index: src/backend/nodes/copyfuncs.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
> retrieving revision 1.311
> diff -c -r1.311 copyfuncs.c
> *** src/backend/nodes/copyfuncs.c    2 Jul 2005 23:00:39 -0000    1.311
> --- src/backend/nodes/copyfuncs.c    3 Jul 2005 22:48:36 -0000
> ***************
> *** 2204,2209 ****
> --- 2204,2220 ----
>       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)
>   {
> ***************
> *** 3010,3015 ****
> --- 3021,3029 ----
>           case T_CreatedbStmt:
>               retval = _copyCreatedbStmt(from);
>               break;
> +         case T_AlterDatabaseStmt:
> +             retval = _copyAlterDatabaseStmt(from);
> +             break;
>           case T_AlterDatabaseSetStmt:
>               retval = _copyAlterDatabaseSetStmt(from);
>               break;
> Index: src/backend/nodes/equalfuncs.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
> retrieving revision 1.248
> diff -c -r1.248 equalfuncs.c
> *** src/backend/nodes/equalfuncs.c    2 Jul 2005 23:00:39 -0000    1.248
> --- src/backend/nodes/equalfuncs.c    3 Jul 2005 22:48:53 -0000
> ***************
> *** 1152,1157 ****
> --- 1152,1166 ----
>   }
>
>   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);
> ***************
> *** 2058,2063 ****
> --- 2067,2075 ----
>           case T_CreatedbStmt:
>               retval = _equalCreatedbStmt(a, b);
>               break;
> +         case T_AlterDatabaseStmt:
> +             retval = _equalAlterDatabaseStmt(a, b);
> +             break;
>           case T_AlterDatabaseSetStmt:
>               retval = _equalAlterDatabaseSetStmt(a, b);
>               break;
> Index: src/backend/parser/gram.y
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/parser/gram.y,v
> retrieving revision 2.501
> diff -c -r2.501 gram.y
> *** src/backend/parser/gram.y    29 Jun 2005 20:34:13 -0000    2.501
> --- src/backend/parser/gram.y    3 Jul 2005 22:50:20 -0000
> ***************
> *** 131,139 ****
>   }
>
>   %type <node>    stmt schema_stmt
> !         AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
> !         AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
> !         AlterRoleStmt AlterRoleSetStmt
>           AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
>           ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
>           CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
> --- 131,139 ----
>   }
>
>   %type <node>    stmt schema_stmt
> !         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
> !         AlterOwnerStmt AlterSeqStmt AlterTableStmt
> !         AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
>           AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
>           ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
>           CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
> ***************
> *** 165,172 ****
>
>   %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 <ival>    opt_lock lock_type cast_context
>   %type <boolean>    opt_force opt_or_replace
> --- 165,174 ----
>
>   %type <dbehavior>    opt_drop_behavior
>
> ! %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
> ***************
> *** 342,348 ****
>       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
>       CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
>       CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
>
> --- 344,350 ----
>       CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P
>       CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
>       CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
> !     COMMITTED CONNECTIONS CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
>       CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
>       CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
>
> ***************
> *** 373,379 ****
>       LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
>       LOCK_P LOGIN_P
>
> !     MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
>
>       NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
>       NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
> --- 375,381 ----
>       LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
>       LOCK_P LOGIN_P
>
> !     MATCH MAX MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
>
>       NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
>       NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
> ***************
> *** 486,492 ****
>           ;
>
>   stmt :
> !             AlterDatabaseSetStmt
>               | AlterDomainStmt
>               | AlterFunctionStmt
>               | AlterGroupStmt
> --- 488,495 ----
>           ;
>
>   stmt :
> !             AlterDatabaseStmt
> !             | AlterDatabaseSetStmt
>               | AlterDomainStmt
>               | AlterFunctionStmt
>               | AlterGroupStmt
> ***************
> *** 663,668 ****
> --- 666,675 ----
>                   {
>                       $$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
>                   }
> +             | MAX CONNECTIONS Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($3));
> +                 }
>               | IN_P ROLE name_list
>                   {
>                       $$ = makeDefElem("addroleto", (Node *)$3);
> ***************
> *** 4455,4460 ****
> --- 4462,4471 ----
>                   {
>                       $$ = makeDefElem("encoding", NULL);
>                   }
> +             | MAX CONNECTIONS opt_equal Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
> +                 }
>               | OWNER opt_equal name
>                   {
>                       $$ = makeDefElem("owner", (Node *)makeString($3));
> ***************
> *** 4481,4486 ****
> --- 4492,4507 ----
>    *
>    *****************************************************************************/
>
> + 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
>                   {
> ***************
> *** 4501,4506 ****
> --- 4522,4540 ----
>           ;
>
>
> + alterdb_opt_list:
> +             alterdb_opt_list alterdb_opt_item        { $$ = lappend($1, $2); }
> +             | /* EMPTY */                            { $$ = NIL; }
> +         ;
> +
> + alterdb_opt_item:
> +             MAX CONNECTIONS opt_equal Iconst
> +                 {
> +                     $$ = makeDefElem("maxconnections", (Node *)makeInteger($4));
> +                 }
> +         ;
> +
> +
>   /*****************************************************************************
>    *
>    *        DROP DATABASE
> ***************
> *** 7941,7946 ****
> --- 7975,7981 ----
>               | COMMENT
>               | COMMIT
>               | COMMITTED
> +             | CONNECTIONS
>               | CONSTRAINTS
>               | CONVERSION_P
>               | COPY
> ***************
> *** 8009,8014 ****
> --- 8044,8050 ----
>               | LOCK_P
>               | LOGIN_P
>               | MATCH
> +             | MAX
>               | MAXVALUE
>               | MINUTE_P
>               | MINVALUE
> Index: src/backend/parser/keywords.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/parser/keywords.c,v
> retrieving revision 1.162
> diff -c -r1.162 keywords.c
> *** src/backend/parser/keywords.c    29 Jun 2005 20:34:14 -0000    1.162
> --- src/backend/parser/keywords.c    3 Jul 2005 22:50:24 -0000
> ***************
> *** 83,88 ****
> --- 83,89 ----
>       {"comment", COMMENT},
>       {"commit", COMMIT},
>       {"committed", COMMITTED},
> +     {"connections", CONNECTIONS},
>       {"constraint", CONSTRAINT},
>       {"constraints", CONSTRAINTS},
>       {"conversion", CONVERSION_P},
> ***************
> *** 203,208 ****
> --- 204,210 ----
>       {"lock", LOCK_P},
>       {"login", LOGIN_P},
>       {"match", MATCH},
> +     {"max", MAX},
>       {"maxvalue", MAXVALUE},
>       {"minute", MINUTE_P},
>       {"minvalue", MINVALUE},
> Index: src/backend/storage/ipc/procarray.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/storage/ipc/procarray.c,v
> retrieving revision 1.3
> diff -c -r1.3 procarray.c
> *** src/backend/storage/ipc/procarray.c    17 Jun 2005 22:32:45 -0000    1.3
> --- src/backend/storage/ipc/procarray.c    3 Jul 2005 22:50:36 -0000
> ***************
> *** 734,739 ****
> --- 734,790 ----
>   }
>
>
> + /*
> +  * 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 && 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 && proc->roleId == roleid)
> +             count++;
> +     }
> +
> +     LWLockRelease(ProcArrayLock);
> +
> +     return count;
> + }
> +
> +
>   #define XidCacheRemove(i) \
>       do { \
>           MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \
> Index: src/backend/storage/lmgr/proc.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
> retrieving revision 1.160
> diff -c -r1.160 proc.c
> *** src/backend/storage/lmgr/proc.c    17 Jun 2005 22:32:45 -0000    1.160
> --- src/backend/storage/lmgr/proc.c    3 Jul 2005 22:50:51 -0000
> ***************
> *** 254,259 ****
> --- 254,260 ----
>       MyProc->xmin = InvalidTransactionId;
>       MyProc->pid = MyProcPid;
>       MyProc->databaseId = MyDatabaseId;
> +     MyProc->roleId = GetSessionUserId();
>       MyProc->lwWaiting = false;
>       MyProc->lwExclusive = false;
>       MyProc->lwWaitLink = NULL;
> Index: src/backend/tcop/utility.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/tcop/utility.c,v
> retrieving revision 1.240
> diff -c -r1.240 utility.c
> *** src/backend/tcop/utility.c    30 Jun 2005 00:00:51 -0000    1.240
> --- src/backend/tcop/utility.c    3 Jul 2005 22:51:06 -0000
> ***************
> *** 275,280 ****
> --- 275,281 ----
>
>       switch (nodeTag(parsetree))
>       {
> +         case T_AlterDatabaseStmt:
>           case T_AlterDatabaseSetStmt:
>           case T_AlterDomainStmt:
>           case T_AlterFunctionStmt:
> ***************
> *** 788,793 ****
> --- 789,798 ----
>               createdb((CreatedbStmt *) parsetree);
>               break;
>
> +         case T_AlterDatabaseStmt:
> +             AlterDatabase((AlterDatabaseStmt *) parsetree);
> +             break;
> +
>           case T_AlterDatabaseSetStmt:
>               AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
>               break;
> ***************
> *** 1504,1509 ****
> --- 1509,1518 ----
>               tag = "CREATE DATABASE";
>               break;
>
> +         case T_AlterDatabaseStmt:
> +             tag = "ALTER DATABASE";
> +             break;
> +
>           case T_AlterDatabaseSetStmt:
>               tag = "ALTER DATABASE";
>               break;
> Index: src/backend/utils/init/flatfiles.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/flatfiles.c,v
> retrieving revision 1.11
> diff -c -r1.11 flatfiles.c
> *** src/backend/utils/init/flatfiles.c    29 Jun 2005 20:34:15 -0000    1.11
> --- src/backend/utils/init/flatfiles.c    3 Jul 2005 22:51:18 -0000
> ***************
> *** 629,635 ****
>               ListCell *mem;
>
>               fputs_quote(arole->rolname, fp);
> !             fputs(" ", fp);
>               fputs_quote(arole->rolpassword, fp);
>               fputs(" ", fp);
>               fputs_quote(arole->rolvaliduntil, fp);
> --- 629,635 ----
>               ListCell *mem;
>
>               fputs_quote(arole->rolname, fp);
> !             fprintf(fp, " %u ", arole->roleid);
>               fputs_quote(arole->rolpassword, fp);
>               fputs(" ", fp);
>               fputs_quote(arole->rolvaliduntil, fp);
> Index: src/backend/utils/init/miscinit.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/miscinit.c,v
> retrieving revision 1.144
> diff -c -r1.144 miscinit.c
> *** src/backend/utils/init/miscinit.c    28 Jun 2005 22:16:45 -0000    1.144
> --- src/backend/utils/init/miscinit.c    3 Jul 2005 22:51:29 -0000
> ***************
> *** 39,44 ****
> --- 39,45 ----
>   #include "utils/guc.h"
>   #include "utils/lsyscache.h"
>   #include "utils/syscache.h"
> + #include "storage/procarray.h"
>
>
>   ProcessingMode Mode = InitProcessing;
> ***************
> *** 347,352 ****
> --- 348,365 ----
>
>       SetSessionUserId(roleid);    /* sets CurrentUserId too */
>
> +     /*
> +      * Check connection limit for user
> +      */
> +     if (rform->rolmaxconn > 0 && !AuthenticatedUserIsSuperuser &&
> +             CountUserBackends(AuthenticatedUserId) > rform->rolmaxconn)
> +     {
> +         ereport(FATAL,
> +             (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
> +          errmsg("sorry, too many clients already for role \"%s\"",
> +             rolename)));
> +     }
> +
>       /* Record username and superuser status as GUC settings too */
>       SetConfigOption("session_authorization", rolename,
>                       PGC_BACKEND, PGC_S_OVERRIDE);
> Index: src/backend/utils/init/postinit.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/postinit.c,v
> retrieving revision 1.151
> diff -c -r1.151 postinit.c
> *** src/backend/utils/init/postinit.c    28 Jun 2005 19:51:23 -0000    1.151
> --- src/backend/utils/init/postinit.c    3 Jul 2005 22:51:37 -0000
> ***************
> *** 47,52 ****
> --- 47,53 ----
>
>
>   static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace);
> + static bool FindMyRole(const char *name, Oid *role_id);
>   static void ReverifyMyDatabase(const char *name);
>   static void InitCommunication(void);
>   static void ShutdownPostgres(int code, Datum arg);
> ***************
> *** 101,106 ****
> --- 102,136 ----
>   }
>
>   /*
> +  * Get roleid from flatfiles
> +  *
> +  * We need this because we need to know userid before
> +  * InitProcess() is called
> +  */
> + static bool
> + FindMyRole(const char *name, Oid *role_id)
> + {
> +     List      **line;
> +     ListCell   *token;
> +
> +     if ((line = get_role_line(name)) == NULL)
> +         ereport(FATAL,
> +                 (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
> +                  errmsg("could not find role \"%s\"", name)));
> +
> +     token = list_head(*line);
> +     if (token)
> +         token = lnext(token);
> +     if (token)
> +     {
> +         *role_id = atoi((char*)lfirst(token));
> +         return true;
> +     }
> +
> +     return false;
> + }
> +
> + /*
>    * ReverifyMyDatabase -- recheck info obtained by FindMyDatabase
>    *
>    * Since FindMyDatabase cannot lock pg_database, the information it read
> ***************
> *** 166,182 ****
>                           name, MyDatabaseId)));
>       }
>
> -     /*
> -      * 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;")
> -      */
>       dbform = (Form_pg_database) GETSTRUCT(tup);
> !     if (IsUnderPostmaster && !dbform->datallowconn)
> !         ereport(FATAL,
> !                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> !          errmsg("database \"%s\" is not currently accepting connections",
>                   name)));
>
>       /*
>        * OK, we're golden.  Next to-do item is to save the encoding
> --- 196,230 ----
>                           name, MyDatabaseId)));
>       }
>
>       dbform = (Form_pg_database) GETSTRUCT(tup);
> !     if (IsUnderPostmaster)
> !     {
> !         /*
> !          * 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;")
> !          */
> !         if (!dbform->datallowconn)
> !         {
> !             ereport(FATAL,
> !                     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
> !              errmsg("database \"%s\" is not currently accepting connections",
> !                     name)));
> !         }
> !
> !         /*
> !          * Here we check cxonenction limit for this database
> !          */
> !         if (dbform->datmaxconn > 0 && !superuser() &&
> !                 CountDBBackends(MyDatabaseId) > dbform->datmaxconn)
> !         {
> !             ereport(FATAL,
> !                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
> !              errmsg("sorry, too many clients already for database \"%s\"",
>                   name)));
> +         }
> +     }
> +
>
>       /*
>        * OK, we're golden.  Next to-do item is to save the encoding
> ***************
> *** 352,357 ****
> --- 400,424 ----
>        */
>
>       /*
> +      * We need to know roleid in InitProcess() so we have read it from
> +      * flatfile, real user inicialization is done later
> +      */
> +     if (IsUnderPostmaster)
> +     {
> +         Oid roleid;
> +
> +         if (!FindMyRole(username, &roleid))
> +             ereport(FATAL,
> +                     (ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION,
> +                      errmsg("role \"%s\" does not exist",
> +                             username)));
> +
> +         SetSessionUserId(roleid);
> +     }
> +     else
> +         SetSessionUserId(BOOTSTRAP_SUPERUSERID);
> +
> +     /*
>        * Set up my per-backend PGPROC struct in shared memory.    (We need
>        * to know MyDatabaseId before we can do this, since it's entered into
>        * the PGPROC struct.)
> Index: src/include/catalog/pg_authid.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_authid.h,v
> retrieving revision 1.1
> diff -c -r1.1 pg_authid.h
> *** src/include/catalog/pg_authid.h    28 Jun 2005 05:09:05 -0000    1.1
> --- src/include/catalog/pg_authid.h    3 Jul 2005 22:51:50 -0000
> ***************
> *** 48,53 ****
> --- 48,54 ----
>       bool        rolcreatedb;    /* allowed to create databases? */
>       bool        rolcatupdate;    /* allowed to alter catalogs manually? */
>       bool        rolcanlogin;    /* allowed to log in as session user? */
> +     int4        rolmaxconn;        /* maximum connections allowed */
>
>       /* remaining fields may be null; use heap_getattr to read them! */
>       text        rolpassword;    /* password, if any */
> ***************
> *** 69,84 ****
>    *        compiler constants for pg_authid
>    * ----------------
>    */
> ! #define Natts_pg_authid                    9
>   #define Anum_pg_authid_rolname            1
>   #define Anum_pg_authid_rolsuper            2
>   #define Anum_pg_authid_rolcreaterole    3
>   #define Anum_pg_authid_rolcreatedb        4
>   #define Anum_pg_authid_rolcatupdate        5
>   #define Anum_pg_authid_rolcanlogin        6
> ! #define Anum_pg_authid_rolpassword        7
> ! #define Anum_pg_authid_rolvaliduntil    8
> ! #define Anum_pg_authid_rolconfig        9
>
>   /* ----------------
>    *        initial contents of pg_authid
> --- 70,86 ----
>    *        compiler constants for pg_authid
>    * ----------------
>    */
> ! #define Natts_pg_authid                    10
>   #define Anum_pg_authid_rolname            1
>   #define Anum_pg_authid_rolsuper            2
>   #define Anum_pg_authid_rolcreaterole    3
>   #define Anum_pg_authid_rolcreatedb        4
>   #define Anum_pg_authid_rolcatupdate        5
>   #define Anum_pg_authid_rolcanlogin        6
> ! #define Anum_pg_authid_rolmaxconn        7
> ! #define Anum_pg_authid_rolpassword        8
> ! #define Anum_pg_authid_rolvaliduntil    9
> ! #define Anum_pg_authid_rolconfig        10
>
>   /* ----------------
>    *        initial contents of pg_authid
> ***************
> *** 87,93 ****
>    * user choices.
>    * ----------------
>    */
> ! DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
>
>   #define BOOTSTRAP_SUPERUSERID 10
>
> --- 89,95 ----
>    * user choices.
>    * ----------------
>    */
> ! DATA(insert OID = 10 ( "POSTGRES" t t t t t 0 _null_ _null_ _null_ ));
>
>   #define BOOTSTRAP_SUPERUSERID 10
>
> Index: src/include/catalog/pg_database.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/catalog/pg_database.h,v
> retrieving revision 1.36
> diff -c -r1.36 pg_database.h
> *** src/include/catalog/pg_database.h    28 Jun 2005 05:09:06 -0000    1.36
> --- src/include/catalog/pg_database.h    3 Jul 2005 22:51:51 -0000
> ***************
> *** 40,45 ****
> --- 40,46 ----
>       int4        encoding;        /* character encoding */
>       bool        datistemplate;    /* allowed as CREATE DATABASE template? */
>       bool        datallowconn;    /* new connections allowed? */
> +     int4        datmaxconn;        /* maximum connections allowed */
>       Oid            datlastsysoid;    /* highest OID to consider a system OID */
>       TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
>       TransactionId datfrozenxid; /* all XIDs before this are frozen */
> ***************
> *** 59,78 ****
>    *        compiler constants for pg_database
>    * ----------------
>    */
> ! #define Natts_pg_database                11
>   #define Anum_pg_database_datname        1
>   #define Anum_pg_database_datdba            2
>   #define Anum_pg_database_encoding        3
>   #define Anum_pg_database_datistemplate    4
>   #define Anum_pg_database_datallowconn    5
> ! #define Anum_pg_database_datlastsysoid    6
> ! #define Anum_pg_database_datvacuumxid    7
> ! #define Anum_pg_database_datfrozenxid    8
> ! #define Anum_pg_database_dattablespace    9
> ! #define Anum_pg_database_datconfig        10
> ! #define Anum_pg_database_datacl            11
>
> ! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 1663 _null_ _null_ ));
>   DESCR("Default template database");
>   #define TemplateDbOid            1
>
> --- 60,80 ----
>    *        compiler constants for pg_database
>    * ----------------
>    */
> ! #define Natts_pg_database                12
>   #define Anum_pg_database_datname        1
>   #define Anum_pg_database_datdba            2
>   #define Anum_pg_database_encoding        3
>   #define Anum_pg_database_datistemplate    4
>   #define Anum_pg_database_datallowconn    5
> ! #define Anum_pg_database_datmaxconn        6
> ! #define Anum_pg_database_datlastsysoid    7
> ! #define Anum_pg_database_datvacuumxid    8
> ! #define Anum_pg_database_datfrozenxid    9
> ! #define Anum_pg_database_dattablespace    10
> ! #define Anum_pg_database_datconfig        11
> ! #define Anum_pg_database_datacl            12
>
> ! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 0 1663 _null_ _null_ ));
>   DESCR("Default template database");
>   #define TemplateDbOid            1
>
> Index: src/include/commands/dbcommands.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/commands/dbcommands.h,v
> retrieving revision 1.39
> diff -c -r1.39 dbcommands.h
> *** src/include/commands/dbcommands.h    28 Jun 2005 05:09:12 -0000    1.39
> --- src/include/commands/dbcommands.h    3 Jul 2005 22:51:53 -0000
> ***************
> *** 64,69 ****
> --- 64,70 ----
>   extern void createdb(const CreatedbStmt *stmt);
>   extern void dropdb(const char *dbname);
>   extern void RenameDatabase(const char *oldname, const char *newname);
> + extern void AlterDatabase(AlterDatabaseStmt *stmt);
>   extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
>   extern void AlterDatabaseOwner(const char *dbname, Oid newOwnerId);
>
> Index: src/include/nodes/nodes.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/nodes/nodes.h,v
> retrieving revision 1.172
> diff -c -r1.172 nodes.h
> *** src/include/nodes/nodes.h    28 Jun 2005 05:09:13 -0000    1.172
> --- src/include/nodes/nodes.h    3 Jul 2005 22:52:00 -0000
> ***************
> *** 270,275 ****
> --- 270,276 ----
>       T_ReindexStmt,
>       T_CheckPointStmt,
>       T_CreateSchemaStmt,
> +     T_AlterDatabaseStmt,
>       T_AlterDatabaseSetStmt,
>       T_AlterRoleSetStmt,
>       T_CreateConversionStmt,
> Index: src/include/nodes/parsenodes.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
> retrieving revision 1.285
> diff -c -r1.285 parsenodes.h
> *** src/include/nodes/parsenodes.h    28 Jun 2005 19:51:24 -0000    1.285
> --- src/include/nodes/parsenodes.h    3 Jul 2005 22:52:25 -0000
> ***************
> *** 1611,1616 ****
> --- 1611,1623 ----
>    *    Alter Database
>    * ----------------------
>    */
> + typedef struct AlterDatabaseStmt
> + {
> +     NodeTag        type;
> +     char       *dbname;            /* name of database to alter */
> +     List       *options;        /* List of DefElem nodes */
> + } AlterDatabaseStmt;
> +
>   typedef struct AlterDatabaseSetStmt
>   {
>       NodeTag        type;
> Index: src/include/storage/proc.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/storage/proc.h,v
> retrieving revision 1.79
> diff -c -r1.79 proc.h
> *** src/include/storage/proc.h    17 Jun 2005 22:32:50 -0000    1.79
> --- src/include/storage/proc.h    3 Jul 2005 22:52:29 -0000
> ***************
> *** 71,76 ****
> --- 71,77 ----
>
>       int            pid;            /* This backend's process id, or 0 */
>       Oid            databaseId;        /* OID of database this backend is using */
> +     Oid            roleId;            /* OID of role using conencted to backend */
>
>       /* Info about LWLock the process is currently waiting for, if any. */
>       bool        lwWaiting;        /* true if waiting for an LW lock */
> Index: src/include/storage/procarray.h
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/include/storage/procarray.h,v
> retrieving revision 1.2
> diff -c -r1.2 procarray.h
> *** src/include/storage/procarray.h    17 Jun 2005 22:32:50 -0000    1.2
> --- src/include/storage/procarray.h    3 Jul 2005 22:52:30 -0000
> ***************
> *** 31,36 ****
> --- 31,38 ----
>   extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);
>
>   extern int    CountActiveBackends(void);
> + extern int    CountDBBackends(Oid databaseid);
> + extern int    CountUserBackends(Oid roleid);
>
>   extern void XidCacheRemoveRunningXids(TransactionId xid,
>                             int nxids, TransactionId *xids);
> Index: src/tools/pgindent/pgindent
> ===================================================================
> RCS file: /projects/cvsroot/pgsql/src/tools/pgindent/pgindent,v
> retrieving revision 1.75
> diff -c -r1.75 pgindent
> *** src/tools/pgindent/pgindent    28 Jun 2005 23:55:30 -0000    1.75
> --- src/tools/pgindent/pgindent    3 Jul 2005 22:53:03 -0000
> ***************
> *** 177,182 ****
> --- 177,183 ----
>   -TAllocSetContext \
>   -TAllocateDesc \
>   -TAllocateDescKind \
> + -TAlterDatabaseStmt \
>   -TAlterDatabaseSetStmt \
>   -TAlterDomainStmt \
>   -TAlterFunctionStmt \

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> The new syntax for this command is CREATE/ALTER DATABASE/USER:
>                 | MAX CONNECTIONS Iconst
> This adds 'max' as a keyword, though at a fairly unreserved level, I
> think.  Should we use the syntax LIMIT CONNECTIONS so we don't have to
> add MAX as a keyword at all?

I didn't like that either.  I was thinking of just CONNECTIONS.
LIMIT CONNECTIONS sort of works grammatically, I guess.

            regards, tom lane

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Bruce Momjian wrote:

>The new syntax for this command is CREATE/ALTER DATABASE/USER:
>
>                | MAX CONNECTIONS Iconst
>
>This adds 'max' as a keyword, though at a fairly unreserved level, I
>think.  Should we use the syntax LIMIT CONNECTIONS so we don't have to
>add MAX as a keyword at all?
>
Yeah I have no problem with LIMIT CONNECTIONS, will you change it or
should I do it ?

btw where has new keyword to be added to not be added "at a fairly
unreserved level" ? (MAX is also added to keywords.c in that patch)

--
Regards
Petr Jelinek (PJMODOS)



Re: per user/database connections limit again

From
Bruce Momjian
Date:
Petr Jelinek wrote:
> Bruce Momjian wrote:
>
> >The new syntax for this command is CREATE/ALTER DATABASE/USER:
> >
> >                | MAX CONNECTIONS Iconst
> >
> >This adds 'max' as a keyword, though at a fairly unreserved level, I
> >think.  Should we use the syntax LIMIT CONNECTIONS so we don't have to
> >add MAX as a keyword at all?
> >
> Yeah I have no problem with LIMIT CONNECTIONS, will you change it or
> should I do it ?

I will do it.

> btw where has new keyword to be added to not be added "at a fairly
> unreserved level" ? (MAX is also added to keywords.c in that patch)

Right, I will remove the MAX addition.  parser/gram.y has this comment:

    /*
     * Keyword classification lists.  Generally, every keyword present in
     * the Postgres grammar should appear in exactly one of these lists.
     *
     * Put a new keyword into the first list that it can go into without causing
     * shift or reduce conflicts.  The earlier lists define "less reserved"
     * categories of keywords.
     */

I will check that your additions are in the right place.


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Bruce Momjian
Date:
I have "worked over" your patch and I think it is ready for application.

I changed the syntax to CONNECTION LIMIT, which seems most natural.  We
could skip CONNECTION and just use a LIMIT keyword, but that seems too
terse.

I removed your use of the pg_auth flat file.  By the time you have the
PROC entry to do your lookups, you might as well just use the system
cache.

There is a race condition in the code because we set our PROC entry
before we check for other entries.  If there is one connection left and
two backends do this at the same time, they would both fail, while one
should fail and the other succeed. Without a lock, I see no way to avoid
it so I just commented it in the code.

Also, I felt that zero should mean allow no/zero connections, rather
than representing unlimited connections.  I used -1 for unlimited.  We
can either document the use of -1, or add syntax to allow NO CONNECTION
LIMIT, or something like that.

The patch requires a catalog version update when applied.

---------------------------------------------------------------------------

Petr Jelinek wrote:
> Stephen Frost wrote:
>
> >This should almost certainly be a pg_database_ownercheck() call instead.
> >
> >
> Right there wasn't pg_database_ownercheck at the time I was writing it,
> fixed
>
> >The rest needs to be updated for roles, but looks like it should be
> >pretty easy to do.  Much of it just needs to be repatched, the parts
> >that do need to be changed look to be pretty simple changes.
> >
> >
> Done.
>
> >I believe the use of SessionUserId is probably correct in this patch.
> >This does mean that this patch will only be for canlogin roles, but that
> >seems like it's probably correct.  Handling roles w/ members would
> >require much more thought.
> >
> >
> I don't think that having max connection for roles w/ members is doable
> because you can have 5 roles which has 1 user as member and each role
> has different number of max conections and there is no right way to
> decide what to do.
>
>
> New version which works with roles is attached (diffed against cvs),
> everything else is mostly same.
> I also had to readd roleid to flatfiles because I need it in
> InitProcess() function.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
Index: src/backend/catalog/system_views.sql
===================================================================
RCS file: /cvsroot/pgsql/src/backend/catalog/system_views.sql,v
retrieving revision 1.17
diff -c -c -r1.17 system_views.sql
*** src/backend/catalog/system_views.sql    26 Jul 2005 16:38:26 -0000    1.17
--- src/backend/catalog/system_views.sql    28 Jul 2005 21:56:17 -0000
***************
*** 15,20 ****
--- 15,21 ----
          rolcreatedb,
          rolcatupdate,
          rolcanlogin,
+         rolconnlimit,
          '********'::text as rolpassword,
          rolvaliduntil,
          rolconfig,
***************
*** 28,33 ****
--- 29,35 ----
          rolcreatedb AS usecreatedb,
          rolsuper AS usesuper,
          rolcatupdate AS usecatupd,
+         rolconnlimit AS useconnlimit,
          rolpassword AS passwd,
          rolvaliduntil::abstime AS valuntil,
          rolconfig AS useconfig
Index: src/backend/commands/dbcommands.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v
retrieving revision 1.167
diff -c -c -r1.167 dbcommands.c
*** src/backend/commands/dbcommands.c    14 Jul 2005 21:46:29 -0000    1.167
--- src/backend/commands/dbcommands.c    28 Jul 2005 21:56:18 -0000
***************
*** 54,67 ****

  /* non-export function prototypes */
  static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
!             Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace);
  static bool have_createdb_privilege(void);
  static void remove_dbtablespaces(Oid db_id);

-
  /*
   * CREATE DATABASE
   */
--- 54,66 ----

  /* non-export function prototypes */
  static bool get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, int *dbConnLimitP, bool *dbIsTemplateP,
!             bool *dbAllowConnP,    Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace);
  static bool have_createdb_privilege(void);
  static void remove_dbtablespaces(Oid db_id);

  /*
   * CREATE DATABASE
   */
***************
*** 75,80 ****
--- 74,80 ----
      int            src_encoding;
      bool        src_istemplate;
      bool        src_allowconn;
+     int            src_connlimit;
      Oid            src_lastsysoid;
      TransactionId src_vacuumxid;
      TransactionId src_frozenxid;
***************
*** 92,101 ****
--- 92,103 ----
      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 = InvalidConnectionLimit;

  #ifndef WIN32
      char        buf[2 * MAXPGPATH + 100];
***************
*** 141,146 ****
--- 143,156 ----
                           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,191 ****
--- 196,203 ----
              elog(ERROR, "unrecognized node type: %d",
                   nodeTag(dencoding->arg));
      }
+     if (dconnlimit && dconnlimit->arg)
+         dbconnlimit = intVal(dconnlimit->arg);

      /* obtain OID of proposed owner */
      if (dbowner)
***************
*** 215,221 ****
       * idea, so accept possibility of race to create.  We will check again
       * after we grab the exclusive lock.
       */
!     if (get_db_info(dbname, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_DUPLICATE_DATABASE),
--- 227,233 ----
       * idea, so accept possibility of race to create.  We will check again
       * after we grab the exclusive lock.
       */
!     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_DUPLICATE_DATABASE),
***************
*** 228,235 ****
          dbtemplate = "template1";        /* Default template database name */

      if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
!                      &src_istemplate, &src_allowconn, &src_lastsysoid,
!                      &src_vacuumxid, &src_frozenxid, &src_deftablespace))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
           errmsg("template database \"%s\" does not exist", dbtemplate)));
--- 240,248 ----
          dbtemplate = "template1";        /* Default template database name */

      if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
!                      &src_connlimit, &src_istemplate, &src_allowconn,
!                      &src_lastsysoid, &src_vacuumxid, &src_frozenxid,
!                      &src_deftablespace))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
           errmsg("template database \"%s\" does not exist", dbtemplate)));
***************
*** 263,268 ****
--- 276,285 ----
      if (encoding < 0)
          encoding = src_encoding;

+     /* If dbconnlimit is defaulted, use source's dbconnlimit */
+     if (dbconnlimit == InvalidConnectionLimit)
+         dbconnlimit = src_connlimit;
+
      /* Some encodings are client only */
      if (!PG_VALID_BE_ENCODING(encoding))
          ereport(ERROR,
***************
*** 458,464 ****
      pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);

      /* Check to see if someone else created same DB name meanwhile. */
!     if (get_db_info(dbname, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
      {
          /* Don't hold lock while doing recursive remove */
--- 475,481 ----
      pg_database_rel = heap_open(DatabaseRelationId, ExclusiveLock);

      /* Check to see if someone else created same DB name meanwhile. */
!     if (get_db_info(dbname, NULL, NULL, NULL, NULL,
                      NULL, NULL, NULL, NULL, NULL, NULL))
      {
          /* Don't hold lock while doing recursive remove */
***************
*** 484,489 ****
--- 501,507 ----
      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);
***************
*** 590,596 ****
       */
      pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);

!     if (!get_db_info(dbname, &db_id, NULL, NULL,
                       &db_istemplate, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
--- 608,614 ----
       */
      pgdbrel = heap_open(DatabaseRelationId, ExclusiveLock);

!     if (!get_db_info(dbname, &db_id, NULL, NULL, NULL,
                       &db_istemplate, NULL, NULL, NULL, NULL, NULL))
          ereport(ERROR,
                  (errcode(ERRCODE_UNDEFINED_DATABASE),
***************
*** 791,796 ****
--- 809,899 ----


  /*
+  * ALTER DATABASE name ...
+  */
+ void
+ AlterDatabase(AlterDatabaseStmt *stmt)
+ {
+     Datum        new_record[Natts_pg_database];
+     char        new_record_nulls[Natts_pg_database];
+     char        new_record_repl[Natts_pg_database];
+     Relation    rel;
+     HeapTuple    tuple,
+                 newtuple;
+     ScanKeyData scankey;
+     SysScanDesc scan;
+     ListCell   *option;
+     int            connlimit = InvalidConnectionLimit;
+
+     DefElem    *dconnlimit = NULL;
+
+     /* 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;
+         }
+     }
+
+     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 (!have_createdb_privilege())
+         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 (connlimit != InvalidConnectionLimit)
+     {
+         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);
+ }
+
+
+ /*
   * ALTER DATABASE name SET ...
   */
  void
***************
*** 998,1005 ****

  static bool
  get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
!             Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace)
  {
--- 1101,1108 ----

  static bool
  get_db_info(const char *name, Oid *dbIdP, Oid *ownerIdP,
!             int *encodingP, int *dbConnLimitP, bool *dbIsTemplateP,
!             bool *dbAllowConnP,    Oid *dbLastSysOidP,
              TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
              Oid *dbTablespace)
  {
***************
*** 1044,1049 ****
--- 1147,1155 ----
          /* allowing connections? */
          if (dbAllowConnP)
              *dbAllowConnP = dbform->datallowconn;
+         /* connection limit */
+         if (dbConnLimitP)
+             *dbConnLimitP = dbform->datconnlimit;
          /* last system OID used in database */
          if (dbLastSysOidP)
              *dbLastSysOidP = dbform->datlastsysoid;
Index: src/backend/commands/user.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/user.c,v
retrieving revision 1.159
diff -c -c -r1.159 user.c
*** src/backend/commands/user.c    26 Jul 2005 22:37:49 -0000    1.159
--- src/backend/commands/user.c    28 Jul 2005 21:56:18 -0000
***************
*** 86,91 ****
--- 86,92 ----
      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,101 ****
--- 97,103 ----
      DefElem    *dcreaterole = NULL;
      DefElem    *dcreatedb = NULL;
      DefElem    *dcanlogin = NULL;
+     DefElem    *dconnlimit = NULL;
      DefElem    *daddroleto = NULL;
      DefElem    *drolemembers = NULL;
      DefElem    *dadminmembers = NULL;
***************
*** 178,183 ****
--- 180,193 ----
                           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,232 ****
--- 237,255 ----
          createdb = intVal(dcreatedb->arg) != 0;
      if (dcanlogin)
          canlogin = intVal(dcanlogin->arg) != 0;
+     if (dconnlimit)
+     {
+         connlimit = intVal(dconnlimit->arg);
+         if (connlimit < -1)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("CONNECTION LIMIT must greater or equal to -1")));
+
+         if (!canlogin && connlimit >= 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("CONNECTION LIMIT can be specified only for roles which can login")));
+     }
      if (daddroleto)
          addroleto = (List *) daddroleto->arg;
      if (drolemembers)
***************
*** 292,297 ****
--- 315,321 ----
      /* 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,406 ****
--- 425,431 ----
      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 = InvalidConnectionLimit;    /* Connection limit */
      List       *rolemembers = NIL;        /* roles to be added/removed */
      char       *validUntil = NULL;        /* time the login is valid until */
      DefElem    *dpassword = NULL;
***************
*** 409,414 ****
--- 434,440 ----
      DefElem    *dcreaterole = NULL;
      DefElem    *dcreatedb = NULL;
      DefElem    *dcanlogin = NULL;
+     DefElem    *dconnlimit = NULL;
      DefElem    *drolemembers = NULL;
      DefElem    *dvalidUntil = NULL;
      Oid            roleid;
***************
*** 472,477 ****
--- 498,511 ----
                           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,511 ****
--- 540,558 ----
          createdb = intVal(dcreatedb->arg);
      if (dcanlogin)
          canlogin = intVal(dcanlogin->arg);
+     if (dconnlimit)
+     {
+         connlimit = intVal(dconnlimit->arg);
+         if (connlimit < -1)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("CONNECTION LIMIT must greater or equal to -1")));
+
+         if (canlogin == 0 && connlimit >= 0)
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                      errmsg("CONNECTION LIMIT can be specified only for roles which can login")));
+     }
      if (drolemembers)
          rolemembers = (List *) drolemembers->arg;
      if (dvalidUntil)
***************
*** 545,550 ****
--- 592,598 ----
                createrole < 0 &&
                createdb < 0 &&
                canlogin < 0 &&
+               connlimit == InvalidConnectionLimit &&
                !rolemembers &&
                !validUntil &&
                password &&
***************
*** 602,607 ****
--- 650,661 ----
          new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r';
      }

+     if (connlimit != InvalidConnectionLimit)
+     {
+         new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
+         new_record_repl[Anum_pg_authid_rolconnlimit - 1] = 'r';
+     }
+
      /* password */
      if (password)
      {
Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.312
diff -c -c -r1.312 copyfuncs.c
*** src/backend/nodes/copyfuncs.c    26 Jul 2005 16:38:27 -0000    1.312
--- src/backend/nodes/copyfuncs.c    28 Jul 2005 21:56:19 -0000
***************
*** 2204,2209 ****
--- 2204,2220 ----
      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,3016 ****
--- 3022,3030 ----
          case T_CreatedbStmt:
              retval = _copyCreatedbStmt(from);
              break;
+         case T_AlterDatabaseStmt:
+             retval = _copyAlterDatabaseStmt(from);
+             break;
          case T_AlterDatabaseSetStmt:
              retval = _copyAlterDatabaseSetStmt(from);
              break;
Index: src/backend/nodes/equalfuncs.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
retrieving revision 1.249
diff -c -c -r1.249 equalfuncs.c
*** src/backend/nodes/equalfuncs.c    26 Jul 2005 16:38:27 -0000    1.249
--- src/backend/nodes/equalfuncs.c    28 Jul 2005 21:56:20 -0000
***************
*** 1152,1157 ****
--- 1152,1166 ----
  }

  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,2064 ****
--- 2068,2076 ----
          case T_CreatedbStmt:
              retval = _equalCreatedbStmt(a, b);
              break;
+         case T_AlterDatabaseStmt:
+             retval = _equalAlterDatabaseStmt(a, b);
+             break;
          case T_AlterDatabaseSetStmt:
              retval = _equalAlterDatabaseSetStmt(a, b);
              break;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.504
diff -c -c -r2.504 gram.y
*** src/backend/parser/gram.y    26 Jul 2005 22:37:50 -0000    2.504
--- src/backend/parser/gram.y    28 Jul 2005 21:56:24 -0000
***************
*** 131,139 ****
  }

  %type <node>    stmt schema_stmt
!         AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
!         AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
!         AlterRoleStmt AlterRoleSetStmt
          AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
          ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
          CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
--- 131,139 ----
  }

  %type <node>    stmt schema_stmt
!         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
!         AlterOwnerStmt AlterSeqStmt AlterTableStmt
!         AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
          AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
          ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
          CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreatePLangStmt
***************
*** 165,172 ****

  %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 <ival>    opt_lock lock_type cast_context
  %type <boolean>    opt_force opt_or_replace
--- 165,174 ----

  %type <dbehavior>    opt_drop_behavior

! %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,263 ****

  %type <boolean> copy_from opt_hold

! %type <ival>    fetch_count    opt_column event cursor_options
  %type <objtype>    reindex_type drop_type comment_type

  %type <node>    fetch_direction select_limit_value select_offset_value
--- 259,265 ----

  %type <boolean> copy_from opt_hold

! %type <ival>    iconst_opt_minus    opt_column event cursor_options
  %type <objtype>    reindex_type drop_type comment_type

  %type <node>    fetch_direction select_limit_value select_offset_value
***************
*** 342,348 ****
      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
      CREATEROLE CREATEUSER CROSS CSV CURRENT_DATE CURRENT_ROLE CURRENT_TIME
      CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE

--- 344,350 ----
      CACHE CALLED CASCADE CASE CAST CHAIN CHAR_P
      CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
      CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
!     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,492 ****
          ;

  stmt :
!             AlterDatabaseSetStmt
              | AlterDomainStmt
              | AlterFunctionStmt
              | AlterGroupStmt
--- 488,495 ----
          ;

  stmt :
!             AlterDatabaseStmt
!             | AlterDatabaseSetStmt
              | AlterDomainStmt
              | AlterFunctionStmt
              | AlterGroupStmt
***************
*** 672,677 ****
--- 675,684 ----
                  {
                      $$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
                  }
+             | CONNECTION LIMIT iconst_opt_minus
+                 {
+                     $$ = makeDefElem("connectionlimit", (Node *)makeInteger($3));
+                 }
              | IN_P ROLE name_list
                  {
                      $$ = makeDefElem("addroleto", (Node *)$3);
***************
*** 3044,3064 ****
                      n->howMany = -1;
                      $$ = (Node *)n;
                  }
!             | ABSOLUTE_P fetch_count
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_ABSOLUTE;
                      n->howMany = $2;
                      $$ = (Node *)n;
                  }
!             | RELATIVE_P fetch_count
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_RELATIVE;
                      n->howMany = $2;
                      $$ = (Node *)n;
                  }
!             | fetch_count
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_FORWARD;
--- 3051,3071 ----
                      n->howMany = -1;
                      $$ = (Node *)n;
                  }
!             | ABSOLUTE_P iconst_opt_minus
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_ABSOLUTE;
                      n->howMany = $2;
                      $$ = (Node *)n;
                  }
!             | RELATIVE_P iconst_opt_minus
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_RELATIVE;
                      n->howMany = $2;
                      $$ = (Node *)n;
                  }
!             | iconst_opt_minus
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_FORWARD;
***************
*** 3079,3085 ****
                      n->howMany = 1;
                      $$ = (Node *)n;
                  }
!             | FORWARD fetch_count
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_FORWARD;
--- 3086,3092 ----
                      n->howMany = 1;
                      $$ = (Node *)n;
                  }
!             | FORWARD iconst_opt_minus
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_FORWARD;
***************
*** 3100,3106 ****
                      n->howMany = 1;
                      $$ = (Node *)n;
                  }
!             | BACKWARD fetch_count
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_BACKWARD;
--- 3107,3113 ----
                      n->howMany = 1;
                      $$ = (Node *)n;
                  }
!             | BACKWARD iconst_opt_minus
                  {
                      FetchStmt *n = makeNode(FetchStmt);
                      n->direction = FETCH_BACKWARD;
***************
*** 3116,3126 ****
                  }
          ;

- fetch_count:
-             Iconst                                    { $$ = $1; }
-             | '-' Iconst                            { $$ = - $2; }
-         ;
-
  from_in:    FROM                                    {}
              | IN_P                                    {}
          ;
--- 3123,3128 ----
***************
*** 4473,4478 ****
--- 4475,4484 ----
                  {
                      $$ = makeDefElem("encoding", NULL);
                  }
+             | CONNECTION LIMIT opt_equal iconst_opt_minus
+                 {
+                     $$ = makeDefElem("connectionlimit", (Node *)makeInteger($4));
+                 }
              | OWNER opt_equal name
                  {
                      $$ = makeDefElem("owner", (Node *)makeString($3));
***************
*** 4499,4504 ****
--- 4505,4520 ----
   *
   *****************************************************************************/

+ 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,4524 ****
--- 4535,4553 ----
          ;


+ alterdb_opt_list:
+             alterdb_opt_list alterdb_opt_item        { $$ = lappend($1, $2); }
+             | /* EMPTY */                            { $$ = NIL; }
+         ;
+
+ alterdb_opt_item:
+             CONNECTION LIMIT opt_equal iconst_opt_minus
+                 {
+                     $$ = makeDefElem("connectionlimit", (Node *)makeInteger($4));
+                 }
+         ;
+
+
  /*****************************************************************************
   *
   *        DROP DATABASE
***************
*** 7875,7880 ****
--- 7904,7914 ----
  Sconst:        SCONST                                    { $$ = $1; };
  RoleId:        ColId                                    { $$ = $1; };

+ iconst_opt_minus:
+             Iconst                                    { $$ = $1; }
+             | '-' Iconst                            { $$ = - $2; }
+         ;
+
  /*
   * Name classification hierarchy.
   *
***************
*** 7959,7964 ****
--- 7993,7999 ----
              | COMMENT
              | COMMIT
              | COMMITTED
+             | CONNECTION
              | CONSTRAINTS
              | CONVERSION_P
              | COPY
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.163
diff -c -c -r1.163 keywords.c
*** src/backend/parser/keywords.c    26 Jul 2005 16:38:27 -0000    1.163
--- src/backend/parser/keywords.c    28 Jul 2005 21:56:24 -0000
***************
*** 83,88 ****
--- 83,89 ----
      {"comment", COMMENT},
      {"commit", COMMIT},
      {"committed", COMMITTED},
+     {"connection", CONNECTION},
      {"constraint", CONSTRAINT},
      {"constraints", CONSTRAINTS},
      {"conversion", CONVERSION_P},
Index: src/backend/storage/ipc/procarray.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/ipc/procarray.c,v
retrieving revision 1.3
diff -c -c -r1.3 procarray.c
*** src/backend/storage/ipc/procarray.c    17 Jun 2005 22:32:45 -0000    1.3
--- src/backend/storage/ipc/procarray.c    28 Jul 2005 21:56:27 -0000
***************
*** 734,739 ****
--- 734,790 ----
  }


+ /*
+  * 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 && 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 && proc->roleId == roleid)
+             count++;
+     }
+
+     LWLockRelease(ProcArrayLock);
+
+     return count;
+ }
+
+
  #define XidCacheRemove(i) \
      do { \
          MyProc->subxids.xids[i] = MyProc->subxids.xids[MyProc->subxids.nxids - 1]; \
Index: src/backend/storage/lmgr/proc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/storage/lmgr/proc.c,v
retrieving revision 1.160
diff -c -c -r1.160 proc.c
*** src/backend/storage/lmgr/proc.c    17 Jun 2005 22:32:45 -0000    1.160
--- src/backend/storage/lmgr/proc.c    28 Jul 2005 21:56:27 -0000
***************
*** 46,51 ****
--- 46,52 ----

  #include "miscadmin.h"
  #include "access/xact.h"
+ #include "catalog/pg_authid.h"
  #include "storage/bufmgr.h"
  #include "storage/ipc.h"
  #include "storage/proc.h"
***************
*** 254,259 ****
--- 255,262 ----
      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;
Index: src/backend/tcop/utility.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/tcop/utility.c,v
retrieving revision 1.241
diff -c -c -r1.241 utility.c
*** src/backend/tcop/utility.c    14 Jul 2005 05:13:41 -0000    1.241
--- src/backend/tcop/utility.c    28 Jul 2005 21:56:28 -0000
***************
*** 275,280 ****
--- 275,281 ----

      switch (nodeTag(parsetree))
      {
+         case T_AlterDatabaseStmt:
          case T_AlterDatabaseSetStmt:
          case T_AlterDomainStmt:
          case T_AlterFunctionStmt:
***************
*** 788,793 ****
--- 789,798 ----
              createdb((CreatedbStmt *) parsetree);
              break;

+         case T_AlterDatabaseStmt:
+             AlterDatabase((AlterDatabaseStmt *) parsetree);
+             break;
+
          case T_AlterDatabaseSetStmt:
              AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
              break;
***************
*** 1504,1509 ****
--- 1509,1518 ----
              tag = "CREATE DATABASE";
              break;

+         case T_AlterDatabaseStmt:
+             tag = "ALTER DATABASE";
+             break;
+
          case T_AlterDatabaseSetStmt:
              tag = "ALTER DATABASE";
              break;
Index: src/backend/utils/init/miscinit.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v
retrieving revision 1.147
diff -c -c -r1.147 miscinit.c
*** src/backend/utils/init/miscinit.c    25 Jul 2005 22:12:33 -0000    1.147
--- src/backend/utils/init/miscinit.c    28 Jul 2005 21:56:29 -0000
***************
*** 36,41 ****
--- 36,43 ----
  #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"
***************
*** 415,420 ****
--- 417,448 ----
      /* This sets OuterUserId/CurrentUserId too */
      SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);

+     /* Set it now that we have a user id */
+     MyProc->roleId = GetSessionUserId();
+
+     /*
+      * Check connection limit for user
+      * There is a race condition here --- we create our PROC before
+      * checking for other PROCs.  If two backends did this at the
+      * same time, they might think they were over the limit, while
+      * one should succeed and one fail.
+      */
+     datum = SysCacheGetAttr(AUTHNAME, roleTup,
+                             Anum_pg_authid_rolconnlimit, &isnull);
+     if (IsUnderPostmaster && !isnull)
+     {
+         int32 connlimit = DatumGetInt32(datum);
+
+         if (connlimit >= 0 && !AuthenticatedUserIsSuperuser &&
+             CountUserBackends(AuthenticatedUserId) > connlimit)
+         {
+             ereport(FATAL,
+                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
+              errmsg("sorry, too many clients already for role \"%s\"",
+                 rolename)));
+         }
+     }
+
      /* Record username and superuser status as GUC settings too */
      SetConfigOption("session_authorization", rolename,
                      PGC_BACKEND, PGC_S_OVERRIDE);
Index: src/backend/utils/init/postinit.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/init/postinit.c,v
retrieving revision 1.153
diff -c -c -r1.153 postinit.c
*** src/backend/utils/init/postinit.c    14 Jul 2005 05:13:41 -0000    1.153
--- src/backend/utils/init/postinit.c    28 Jul 2005 21:56:29 -0000
***************
*** 167,183 ****
                          name, MyDatabaseId)));
      }

-     /*
-      * 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;")
-      */
      dbform = (Form_pg_database) GETSTRUCT(tup);
!     if (IsUnderPostmaster && !dbform->datallowconn)
!         ereport(FATAL,
!                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!          errmsg("database \"%s\" is not currently accepting connections",
                  name)));

      /*
       * OK, we're golden.  Next to-do item is to save the encoding
--- 167,205 ----
                          name, MyDatabaseId)));
      }

      dbform = (Form_pg_database) GETSTRUCT(tup);
!     if (IsUnderPostmaster)
!     {
!         /*
!          * 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;")
!          */
!         if (!dbform->datallowconn)
!         {
!             ereport(FATAL,
!                     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!              errmsg("database \"%s\" is not currently accepting connections",
!                     name)));
!         }
!
!         /*
!          * Here we check connection limit for this database
!          * There is a race condition here --- we create our PROC before
!          * checking for other PROCs.  If two backends did this at the
!          * same time, they might think they were over the limit, while
!          * one should succeed and one fail.
!          */
!         if (dbform->datconnlimit >= 0 && !superuser() &&
!                 CountDBBackends(MyDatabaseId) > dbform->datconnlimit)
!         {
!             ereport(FATAL,
!                 (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
!              errmsg("sorry, too many clients already for database \"%s\"",
                  name)));
+         }
+     }
+

      /*
       * OK, we're golden.  Next to-do item is to save the encoding
Index: src/bin/pg_dump/pg_dumpall.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/pg_dump/pg_dumpall.c,v
retrieving revision 1.65
diff -c -c -r1.65 pg_dumpall.c
*** src/bin/pg_dump/pg_dumpall.c    25 Jul 2005 04:52:32 -0000    1.65
--- src/bin/pg_dump/pg_dumpall.c    28 Jul 2005 21:56:30 -0000
***************
*** 394,409 ****
      PGresult   *res;
      int            i;

!     if (server_version >= 70100)
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template0')) AS clusterowner
"
                             "FROM pg_shadow");
      else
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template1')) AS clusterowner
"
                             "FROM pg_shadow");

--- 394,415 ----
      PGresult   *res;
      int            i;

!     if (server_version >= 80100)
!         res = executeQuery(conn,
!                         "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, useconnlimit, "
!                            "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template0')) AS clusterowner
"
!                            "FROM pg_shadow");
!     else if (server_version >= 70100)
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, -1 AS useconnlimit, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template0')) AS clusterowner
"
                             "FROM pg_shadow");
      else
          res = executeQuery(conn,
                          "SELECT usename, usesysid, passwd, usecreatedb, "
!                            "usesuper, valuntil, -1 AS useconnlimit, "
                             "(usesysid = (SELECT datdba FROM pg_database WHERE datname = 'template1')) AS clusterowner
"
                             "FROM pg_shadow");

***************
*** 453,458 ****
--- 459,468 ----
              appendPQExpBuffer(buf, " VALID UNTIL '%s'",
                                PQgetvalue(res, i, 5));

+         if (strcmp(PQgetvalue(res, i, 6), "-1") != 0)
+             appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
+                               PQgetvalue(res, i, 6));
+
          appendPQExpBuffer(buf, ";\n");

          printf("%s", buf->data);
***************
*** 612,623 ****

      printf("--\n-- Database creation\n--\n\n");

!     if (server_version >= 80000)
          res = executeQuery(conn,
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, "
                             "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
--- 622,642 ----

      printf("--\n-- Database creation\n--\n\n");

!     if (server_version >= 80100)
          res = executeQuery(conn,
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, datconnlimit, "
!                            "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
!         "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
!                            "WHERE datallowconn ORDER BY 1");
!     else if (server_version >= 80000)
!         res = executeQuery(conn,
!                            "SELECT datname, "
!                            "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
!                            "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, -1 as datconnlimit, "
                             "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
***************
*** 626,632 ****
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, "
                             "'pg_default' AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
--- 645,651 ----
                             "SELECT datname, "
                             "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from
pg_databasewhere datname='template0'))), " 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, datacl, -1 as datconnlimit, "
                             "'pg_default' AS dattablespace "
          "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
                             "WHERE datallowconn ORDER BY 1");
***************
*** 637,643 ****
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "(select usename from pg_shadow where usesysid=(select datdba from pg_database where
datname='template0')))," 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, '' as datacl, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "WHERE datallowconn ORDER BY 1");
--- 656,662 ----
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "(select usename from pg_shadow where usesysid=(select datdba from pg_database where
datname='template0')))," 
                             "pg_encoding_to_char(d.encoding), "
!                            "datistemplate, '' as datacl, -1 as datconnlimit, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "WHERE datallowconn ORDER BY 1");
***************
*** 652,658 ****
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "pg_encoding_to_char(d.encoding), "
                             "'f' as datistemplate, "
!                            "'' as datacl, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "ORDER BY 1");
--- 671,677 ----
                  "(select usename from pg_shadow where usesysid=datdba), "
                             "pg_encoding_to_char(d.encoding), "
                             "'f' as datistemplate, "
!                            "'' as datacl, -1 as datconnlimit, "
                             "'pg_default' AS dattablespace "
                             "FROM pg_database d "
                             "ORDER BY 1");
***************
*** 666,672 ****
          char       *dbencoding = PQgetvalue(res, i, 2);
          char       *dbistemplate = PQgetvalue(res, i, 3);
          char       *dbacl = PQgetvalue(res, i, 4);
!         char       *dbtablespace = PQgetvalue(res, i, 5);
          char       *fdbname;

          buf = createPQExpBuffer();
--- 685,692 ----
          char       *dbencoding = PQgetvalue(res, i, 2);
          char       *dbistemplate = PQgetvalue(res, i, 3);
          char       *dbacl = PQgetvalue(res, i, 4);
!         char       *dbconnlimit = PQgetvalue(res, i, 5);
!         char       *dbtablespace = PQgetvalue(res, i, 6);
          char       *fdbname;

          buf = createPQExpBuffer();
***************
*** 698,703 ****
--- 718,727 ----
                  appendPQExpBuffer(buf, " TABLESPACE = %s",
                                    fmtId(dbtablespace));

+             if (strcmp(dbconnlimit, "-1") != 0)
+                 appendPQExpBuffer(buf, " CONNECTION LIMIT = %s",
+                                   dbconnlimit);
+
              appendPQExpBuffer(buf, ";\n");

              if (strcmp(dbistemplate, "t") == 0)
Index: src/include/miscadmin.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/miscadmin.h,v
retrieving revision 1.178
diff -c -c -r1.178 miscadmin.h
*** src/include/miscadmin.h    25 Jul 2005 22:12:34 -0000    1.178
--- src/include/miscadmin.h    28 Jul 2005 21:56:31 -0000
***************
*** 213,218 ****
--- 213,219 ----
  extern int    VacuumCostBalance;
  extern bool VacuumCostActive;

+ #define InvalidConnectionLimit    (-2)

  /* in tcop/postgres.c */
  extern void check_stack_depth(void);
Index: src/include/catalog/pg_authid.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/catalog/pg_authid.h,v
retrieving revision 1.2
diff -c -c -r1.2 pg_authid.h
*** src/include/catalog/pg_authid.h    26 Jul 2005 16:38:28 -0000    1.2
--- src/include/catalog/pg_authid.h    28 Jul 2005 21:56:31 -0000
***************
*** 49,54 ****
--- 49,55 ----
      bool        rolcreatedb;    /* allowed to create databases? */
      bool        rolcatupdate;    /* allowed to alter catalogs manually? */
      bool        rolcanlogin;    /* allowed to log in as session user? */
+     int4        rolconnlimit;        /* maximum connections allowed */

      /* remaining fields may be null; use heap_getattr to read them! */
      text        rolpassword;    /* password, if any */
***************
*** 70,76 ****
   *        compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid                    10
  #define Anum_pg_authid_rolname            1
  #define Anum_pg_authid_rolsuper            2
  #define Anum_pg_authid_rolinherit        3
--- 71,77 ----
   *        compiler constants for pg_authid
   * ----------------
   */
! #define Natts_pg_authid                    11
  #define Anum_pg_authid_rolname            1
  #define Anum_pg_authid_rolsuper            2
  #define Anum_pg_authid_rolinherit        3
***************
*** 78,86 ****
  #define Anum_pg_authid_rolcreatedb        5
  #define Anum_pg_authid_rolcatupdate        6
  #define Anum_pg_authid_rolcanlogin        7
! #define Anum_pg_authid_rolpassword        8
! #define Anum_pg_authid_rolvaliduntil    9
! #define Anum_pg_authid_rolconfig        10

  /* ----------------
   *        initial contents of pg_authid
--- 79,88 ----
  #define Anum_pg_authid_rolcreatedb        5
  #define Anum_pg_authid_rolcatupdate        6
  #define Anum_pg_authid_rolcanlogin        7
! #define Anum_pg_authid_rolconnlimit        8
! #define Anum_pg_authid_rolpassword        9
! #define Anum_pg_authid_rolvaliduntil    10
! #define Anum_pg_authid_rolconfig        11

  /* ----------------
   *        initial contents of pg_authid
***************
*** 89,95 ****
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t _null_ _null_ _null_ ));

  #define BOOTSTRAP_SUPERUSERID 10

--- 91,97 ----
   * user choices.
   * ----------------
   */
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t -1 _null_ _null_ _null_ ));

  #define BOOTSTRAP_SUPERUSERID 10

Index: src/include/catalog/pg_database.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/catalog/pg_database.h,v
retrieving revision 1.36
diff -c -c -r1.36 pg_database.h
*** src/include/catalog/pg_database.h    28 Jun 2005 05:09:06 -0000    1.36
--- src/include/catalog/pg_database.h    28 Jul 2005 21:56:31 -0000
***************
*** 40,45 ****
--- 40,46 ----
      int4        encoding;        /* character encoding */
      bool        datistemplate;    /* allowed as CREATE DATABASE template? */
      bool        datallowconn;    /* new connections allowed? */
+     int4        datconnlimit;        /* maximum connections allowed */
      Oid            datlastsysoid;    /* highest OID to consider a system OID */
      TransactionId datvacuumxid; /* all XIDs before this are vacuumed */
      TransactionId datfrozenxid; /* all XIDs before this are frozen */
***************
*** 59,78 ****
   *        compiler constants for pg_database
   * ----------------
   */
! #define Natts_pg_database                11
  #define Anum_pg_database_datname        1
  #define Anum_pg_database_datdba            2
  #define Anum_pg_database_encoding        3
  #define Anum_pg_database_datistemplate    4
  #define Anum_pg_database_datallowconn    5
! #define Anum_pg_database_datlastsysoid    6
! #define Anum_pg_database_datvacuumxid    7
! #define Anum_pg_database_datfrozenxid    8
! #define Anum_pg_database_dattablespace    9
! #define Anum_pg_database_datconfig        10
! #define Anum_pg_database_datacl            11

! DATA(insert OID = 1 (  template1 PGUID ENCODING t t 0 0 0 1663 _null_ _null_ ));
  DESCR("Default template database");
  #define TemplateDbOid            1

--- 60,80 ----
   *        compiler constants for pg_database
   * ----------------
   */
! #define Natts_pg_database                12
  #define Anum_pg_database_datname        1
  #define Anum_pg_database_datdba            2
  #define Anum_pg_database_encoding        3
  #define Anum_pg_database_datistemplate    4
  #define Anum_pg_database_datallowconn    5
! #define Anum_pg_database_datconnlimit        6
! #define Anum_pg_database_datlastsysoid    7
! #define Anum_pg_database_datvacuumxid    8
! #define Anum_pg_database_datfrozenxid    9
! #define Anum_pg_database_dattablespace    10
! #define Anum_pg_database_datconfig        11
! #define Anum_pg_database_datacl            12

! DATA(insert OID = 1 (  template1 PGUID ENCODING t t -1 0 0 0 1663 _null_ _null_ ));
  DESCR("Default template database");
  #define TemplateDbOid            1

Index: src/include/commands/dbcommands.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/commands/dbcommands.h,v
retrieving revision 1.40
diff -c -c -r1.40 dbcommands.h
*** src/include/commands/dbcommands.h    8 Jul 2005 04:12:27 -0000    1.40
--- src/include/commands/dbcommands.h    28 Jul 2005 21:56:31 -0000
***************
*** 55,60 ****
--- 55,61 ----
  extern void createdb(const CreatedbStmt *stmt);
  extern void dropdb(const char *dbname);
  extern void RenameDatabase(const char *oldname, const char *newname);
+ extern void AlterDatabase(AlterDatabaseStmt *stmt);
  extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt);
  extern void AlterDatabaseOwner(const char *dbname, Oid newOwnerId);

Index: src/include/nodes/nodes.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/nodes/nodes.h,v
retrieving revision 1.172
diff -c -c -r1.172 nodes.h
*** src/include/nodes/nodes.h    28 Jun 2005 05:09:13 -0000    1.172
--- src/include/nodes/nodes.h    28 Jul 2005 21:56:31 -0000
***************
*** 270,275 ****
--- 270,276 ----
      T_ReindexStmt,
      T_CheckPointStmt,
      T_CreateSchemaStmt,
+     T_AlterDatabaseStmt,
      T_AlterDatabaseSetStmt,
      T_AlterRoleSetStmt,
      T_CreateConversionStmt,
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.286
diff -c -c -r1.286 parsenodes.h
*** src/include/nodes/parsenodes.h    26 Jul 2005 16:38:28 -0000    1.286
--- src/include/nodes/parsenodes.h    28 Jul 2005 21:56:33 -0000
***************
*** 1624,1629 ****
--- 1624,1636 ----
   *    Alter Database
   * ----------------------
   */
+ typedef struct AlterDatabaseStmt
+ {
+     NodeTag        type;
+     char       *dbname;            /* name of database to alter */
+     List       *options;        /* List of DefElem nodes */
+ } AlterDatabaseStmt;
+
  typedef struct AlterDatabaseSetStmt
  {
      NodeTag        type;
Index: src/include/storage/proc.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/proc.h,v
retrieving revision 1.79
diff -c -c -r1.79 proc.h
*** src/include/storage/proc.h    17 Jun 2005 22:32:50 -0000    1.79
--- src/include/storage/proc.h    28 Jul 2005 21:56:33 -0000
***************
*** 71,76 ****
--- 71,77 ----

      int            pid;            /* This backend's process id, or 0 */
      Oid            databaseId;        /* OID of database this backend is using */
+     Oid            roleId;            /* OID of role using conencted to backend */

      /* Info about LWLock the process is currently waiting for, if any. */
      bool        lwWaiting;        /* true if waiting for an LW lock */
Index: src/include/storage/procarray.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/storage/procarray.h,v
retrieving revision 1.2
diff -c -c -r1.2 procarray.h
*** src/include/storage/procarray.h    17 Jun 2005 22:32:50 -0000    1.2
--- src/include/storage/procarray.h    28 Jul 2005 21:56:33 -0000
***************
*** 31,36 ****
--- 31,38 ----
  extern bool DatabaseHasActiveBackends(Oid databaseId, bool ignoreMyself);

  extern int    CountActiveBackends(void);
+ extern int    CountDBBackends(Oid databaseid);
+ extern int    CountUserBackends(Oid roleid);

  extern void XidCacheRemoveRunningXids(TransactionId xid,
                            int nxids, TransactionId *xids);
Index: src/test/regress/expected/rules.out
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/expected/rules.out,v
retrieving revision 1.105
diff -c -c -r1.105 rules.out
*** src/test/regress/expected/rules.out    26 Jul 2005 16:38:29 -0000    1.105
--- src/test/regress/expected/rules.out    28 Jul 2005 21:56:35 -0000
***************
*** 1281,1290 ****
   pg_indexes               | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname
AS"tablespace", pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN
pg_classi ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace
tON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")); 
   pg_locks                 | SELECT l.locktype, l."database", l.relation, l.page, l.tuple, l.transactionid, l.classid,
l.objid,l.objsubid, l."transaction", l.pid, l."mode", l."granted" FROM pg_lock_status() l(locktype text, "database"
oid,relation oid, page integer, tuple smallint, transactionid xid, classid oid, objid oid, objsubid smallint,
"transaction"xid, pid integer, "mode" text, "granted" boolean); 
   pg_prepared_xacts        | SELECT p."transaction", p.gid, p."prepared", u.rolname AS "owner", d.datname AS
"database"FROM ((pg_prepared_xact() p("transaction" xid, gid text, "prepared" timestamp with time zone, ownerid oid,
dbidoid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid))); 
!  pg_roles                 | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit,
pg_authid.rolcreaterole,pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS
rolpassword,pg_authid.rolvaliduntil, pg_authid.rolconfig, pg_authid.oid FROM pg_authid; 
   pg_rules                 | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid)
ASdefinition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid =
c.relnamespace)))WHERE (r.rulename <> '_RETURN'::name); 
   pg_settings              | SELECT a.name, a.setting, a.category, a.short_desc, a.extra_desc, a.context, a.vartype,
a.source,a.min_val, a.max_val FROM pg_show_all_settings() a(name text, setting text, category text, short_desc text,
extra_desctext, context text, vartype text, source text, min_val text, max_val text); 
!  pg_shadow                | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS
usecreatedb,pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolpassword AS passwd,
(pg_authid.rolvaliduntil)::abstimeAS valuntil, pg_authid.rolconfig AS useconfig FROM pg_authid WHERE
pg_authid.rolcanlogin;
   pg_stat_activity         | SELECT d.oid AS datid, d.datname, pg_stat_get_backend_pid(s.backendid) AS procpid,
pg_stat_get_backend_userid(s.backendid)AS usesysid, u.rolname AS usename, pg_stat_get_backend_activity(s.backendid) AS
current_query,pg_stat_get_backend_activity_start(s.backendid) AS query_start, pg_stat_get_backend_start(s.backendid) AS
backend_start,pg_stat_get_backend_client_addr(s.backendid) AS client_addr, pg_stat_get_backend_client_port(s.backendid)
ASclient_port FROM pg_database d, (SELECT pg_stat_get_backend_idset() AS backendid) s, pg_authid u WHERE
((pg_stat_get_backend_dbid(s.backendid)= d.oid) AND (pg_stat_get_backend_userid(s.backendid) = u.oid)); 
   pg_stat_all_indexes      | SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, c.relname, i.relname
ASindexrelname, pg_stat_get_numscans(i.oid) AS idx_scan, pg_stat_get_tuples_returned(i.oid) AS idx_tup_read,
pg_stat_get_tuples_fetched(i.oid)AS idx_tup_fetch FROM (((pg_class c JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN
pg_classi ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind =
'r'::"char");
   pg_stat_all_tables       | SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, pg_stat_get_numscans(c.oid) AS
seq_scan,pg_stat_get_tuples_returned(c.oid) AS seq_tup_read, sum(pg_stat_get_numscans(i.indexrelid)) AS idx_scan,
sum(pg_stat_get_tuples_fetched(i.indexrelid))AS idx_tup_fetch, pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins,
pg_stat_get_tuples_updated(c.oid)AS n_tup_upd, pg_stat_get_tuples_deleted(c.oid) AS n_tup_del FROM ((pg_class c LEFT
JOINpg_index i ON ((c.oid = i.indrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind =
'r'::"char")GROUP BY c.oid, n.nspname, c.relname; 
--- 1281,1290 ----
   pg_indexes               | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname
AS"tablespace", pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN
pg_classi ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace
tON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")); 
   pg_locks                 | SELECT l.locktype, l."database", l.relation, l.page, l.tuple, l.transactionid, l.classid,
l.objid,l.objsubid, l."transaction", l.pid, l."mode", l."granted" FROM pg_lock_status() l(locktype text, "database"
oid,relation oid, page integer, tuple smallint, transactionid xid, classid oid, objid oid, objsubid smallint,
"transaction"xid, pid integer, "mode" text, "granted" boolean); 
   pg_prepared_xacts        | SELECT p."transaction", p.gid, p."prepared", u.rolname AS "owner", d.datname AS
"database"FROM ((pg_prepared_xact() p("transaction" xid, gid text, "prepared" timestamp with time zone, ownerid oid,
dbidoid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid))); 
!  pg_roles                 | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit,
pg_authid.rolcreaterole,pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, pg_authid.rolconnlimit,
'********'::textAS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig, pg_authid.oid FROM pg_authid; 
   pg_rules                 | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid)
ASdefinition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid =
c.relnamespace)))WHERE (r.rulename <> '_RETURN'::name); 
   pg_settings              | SELECT a.name, a.setting, a.category, a.short_desc, a.extra_desc, a.context, a.vartype,
a.source,a.min_val, a.max_val FROM pg_show_all_settings() a(name text, setting text, category text, short_desc text,
extra_desctext, context text, vartype text, source text, min_val text, max_val text); 
!  pg_shadow                | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS
usecreatedb,pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolconnlimit AS
useconnlimit,pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, pg_authid.rolconfig AS
useconfigFROM pg_authid WHERE pg_authid.rolcanlogin; 
   pg_stat_activity         | SELECT d.oid AS datid, d.datname, pg_stat_get_backend_pid(s.backendid) AS procpid,
pg_stat_get_backend_userid(s.backendid)AS usesysid, u.rolname AS usename, pg_stat_get_backend_activity(s.backendid) AS
current_query,pg_stat_get_backend_activity_start(s.backendid) AS query_start, pg_stat_get_backend_start(s.backendid) AS
backend_start,pg_stat_get_backend_client_addr(s.backendid) AS client_addr, pg_stat_get_backend_client_port(s.backendid)
ASclient_port FROM pg_database d, (SELECT pg_stat_get_backend_idset() AS backendid) s, pg_authid u WHERE
((pg_stat_get_backend_dbid(s.backendid)= d.oid) AND (pg_stat_get_backend_userid(s.backendid) = u.oid)); 
   pg_stat_all_indexes      | SELECT c.oid AS relid, i.oid AS indexrelid, n.nspname AS schemaname, c.relname, i.relname
ASindexrelname, pg_stat_get_numscans(i.oid) AS idx_scan, pg_stat_get_tuples_returned(i.oid) AS idx_tup_read,
pg_stat_get_tuples_fetched(i.oid)AS idx_tup_fetch FROM (((pg_class c JOIN pg_index x ON ((c.oid = x.indrelid))) JOIN
pg_classi ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind =
'r'::"char");
   pg_stat_all_tables       | SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, pg_stat_get_numscans(c.oid) AS
seq_scan,pg_stat_get_tuples_returned(c.oid) AS seq_tup_read, sum(pg_stat_get_numscans(i.indexrelid)) AS idx_scan,
sum(pg_stat_get_tuples_fetched(i.indexrelid))AS idx_tup_fetch, pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins,
pg_stat_get_tuples_updated(c.oid)AS n_tup_upd, pg_stat_get_tuples_deleted(c.oid) AS n_tup_del FROM ((pg_class c LEFT
JOINpg_index i ON ((c.oid = i.indrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind =
'r'::"char")GROUP BY c.oid, n.nspname, c.relname; 

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Bruce Momjian wrote:

>I removed your use of the pg_auth flat file.  By the time you have the
>PROC entry to do your lookups, you might as well just use the system
>cache.
>
>There is a race condition in the code because we set our PROC entry
>before we check for other entries.  If there is one connection left and
>two backends do this at the same time, they would both fail, while one
>should fail and the other succeed. Without a lock, I see no way to avoid
>it so I just commented it in the code.
>
>
Yeah my working version was doing this too but I wanted to avoid lock on
PROC array and that race condition and because pg_auth is loaded anyway
I used it.

>Also, I felt that zero should mean allow no/zero connections, rather
>than representing unlimited connections.  I used -1 for unlimited.  We
>can either document the use of -1, or add syntax to allow NO CONNECTION
>LIMIT, or something like that.
>
>
Right, maybe we could remove datallowconn from pg_database (in future)
if we can achieve same thing using datconnlimit = 0 ?

>The patch requires a catalog version update when applied.
>
>
Yes, thanks for your work on this patch, I will write documentation for
it in next few days.

--
Regards
Petr Jelinek (PJMODOS)


Re: per user/database connections limit again

From
Bruce Momjian
Date:
Petr Jelinek wrote:
> Bruce Momjian wrote:
>
> >I removed your use of the pg_auth flat file.  By the time you have the
> >PROC entry to do your lookups, you might as well just use the system
> >cache.
> >
> >There is a race condition in the code because we set our PROC entry
> >before we check for other entries.  If there is one connection left and
> >two backends do this at the same time, they would both fail, while one
> >should fail and the other succeed. Without a lock, I see no way to avoid
> >it so I just commented it in the code.
> >
> >
> Yeah my working version was doing this too but I wanted to avoid lock on
> PROC array and that race condition and because pg_auth is loaded anyway
> I used it.

Well, we are locking the PROC array for the db scan as well, so I don't
see a difference for user.  Also, I don't see how it would avoid the
race condition.  We could scan PROC and then set our user value, but
that would allow possibly too many connections rather than too few.

> >Also, I felt that zero should mean allow no/zero connections, rather
> >than representing unlimited connections.  I used -1 for unlimited.  We
> >can either document the use of -1, or add syntax to allow NO CONNECTION
> >LIMIT, or something like that.
> >
> >
> Right, maybe we could remove datallowconn from pg_database (in future)
> if we can achieve same thing using datconnlimit = 0 ?

Yes, we certainly could, but I am betting we would need both because if
someone wanted to close down a database, _but_ keep the existing limit
so it could be resetored later, they would still use datallowconn.

> >The patch requires a catalog version update when applied.
> >
> >
> Yes, thanks for your work on this patch, I will write documentation for
> it in next few days.

Thanks.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Here is promised documentation.
Be warned that both my writing skills and my english are far from good :)

--
Regards
Petr Jelinek (PJMODOS)


Index: doc/src/sgml/catalogs.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/catalogs.sgml,v
retrieving revision 2.109
diff -c -r2.109 catalogs.sgml
*** doc/src/sgml/catalogs.sgml    26 Jul 2005 16:38:25 -0000    2.109
--- doc/src/sgml/catalogs.sgml    30 Jul 2005 18:48:10 -0000
***************
*** 1019,1024 ****
--- 1019,1035 ----
       </row>

       <row>
+       <entry><structfield>rolconnlimit</structfield></entry>
+       <entry><type>int4</type></entry>
+       <entry></entry>
+       <entry>
+        For roles that can login this sets maximum amount of concurrent
+        connections this role can make. Default value (-1) means
+        unlimited connections, zero (0) means role can't login.
+       </entry>
+      </row>
+
+      <row>
        <entry><structfield>rolpassword</structfield></entry>
        <entry><type>text</type></entry>
        <entry></entry>
***************
*** 1922,1927 ****
--- 1933,1949 ----
       </row>

       <row>
+       <entry><structfield>datconnlimit</structfield></entry>
+       <entry><type>int4</type></entry>
+       <entry></entry>
+       <entry>
+        Sets maximum amount of concurrent connections that can be made
+        to this database. Default value (-1) means unlimited connections,
+        zero (0) means that database isn't accepting connections.
+       </entry>
+      </row>
+
+      <row>
        <entry><structfield>datlastsysoid</structfield></entry>
        <entry><type>oid</type></entry>
        <entry></entry>
***************
*** 4812,4817 ****
--- 4834,4850 ----
       </row>

       <row>
+       <entry><structfield>rolconnlimit</structfield></entry>
+       <entry><type>int4</type></entry>
+       <entry></entry>
+       <entry>
+        For roles that can login this sets maximum amount of concurrent
+        connections this role can make. Default value (-1) means
+        unlimited connections, zero (0) means role can't login.
+       </entry>
+      </row>
+
+      <row>
        <entry><structfield>rolpassword</structfield></entry>
        <entry><type>text</type></entry>
        <entry></entry>
***************
*** 5094,5099 ****
--- 5127,5143 ----
       </row>

       <row>
+       <entry><structfield>useconnlimit</structfield></entry>
+       <entry><type>int4</type></entry>
+       <entry></entry>
+       <entry>
+        This sets maximum amount of concurrent connections this user can make.
+        Default value (-1) means unlimited connections,
+        zero (0) means user can't login.
+       </entry>
+      </row>
+
+      <row>
        <entry><structfield>passwd</structfield></entry>
        <entry><type>text</type></entry>
        <entry></entry>
Index: doc/src/sgml/ref/alter_database.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/alter_database.sgml,v
retrieving revision 1.15
diff -c -r1.15 alter_database.sgml
*** doc/src/sgml/ref/alter_database.sgml    5 Jan 2005 14:22:39 -0000    1.15
--- doc/src/sgml/ref/alter_database.sgml    30 Jul 2005 18:48:11 -0000
***************
*** 23,28 ****
--- 23,34 ----
  ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> SET <replaceable>parameter</replaceable> { TO | = }
{<replaceable>value</replaceable> | DEFAULT } 
  ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> RESET <replaceable>parameter</replaceable>

+ ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replaceable
class="PARAMETER">option</replaceable>[ ... ] ] 
+
+ where <replaceable class="PARAMETER">option</replaceable> can be:
+
+     CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
+
  ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>newname</replaceable>

  ALTER DATABASE <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceable>new_owner</replaceable>
***************
*** 51,57 ****
    </para>

    <para>
!    The third form changes the name of the database.  Only the database
     owner or a superuser can rename a database; non-superuser owners must
     also have the
     <literal>CREATEDB</literal> privilege.  The current database cannot
--- 57,68 ----
    </para>

    <para>
!    The third form changes certain per-database settings.  (See below for
!    details.)  Only database owner or superuser can change these settings.
!   </para>
!
!   <para>
!    The fourth form changes the name of the database.  Only the database
     owner or a superuser can rename a database; non-superuser owners must
     also have the
     <literal>CREATEDB</literal> privilege.  The current database cannot
***************
*** 60,66 ****
    </para>

    <para>
!    The fourth form changes the owner of the database.  Only a superuser
     can change the database's owner.
    </para>
   </refsect1>
--- 71,77 ----
    </para>

    <para>
!    The fifth form changes the owner of the database.  Only a superuser
     can change the database's owner.
    </para>
   </refsect1>
***************
*** 100,105 ****
--- 111,128 ----
        </listitem>
       </varlistentry>

+      <varlistentry>
+       <term><replaceable class="parameter">connlimit</replaceable></term>
+       <listitem>
+        <para>
+         Number specifying how many concurrent connections can be made
+         to this database. Default (-1) means unlimited
+         (limited by max_connections config variable only),
+         zero (0) means that no connections are allowed.
+        </para>
+       </listitem>
+      </varlistentry>
+
     <varlistentry>
      <term><replaceable>newname</replaceable></term>
      <listitem>
Index: doc/src/sgml/ref/alter_role.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/alter_role.sgml,v
retrieving revision 1.1
diff -c -r1.1 alter_role.sgml
*** doc/src/sgml/ref/alter_role.sgml    26 Jul 2005 23:24:02 -0000    1.1
--- doc/src/sgml/ref/alter_role.sgml    30 Jul 2005 18:48:12 -0000
***************
*** 30,35 ****
--- 30,36 ----
      | CREATEUSER | NOCREATEUSER
      | INHERIT | NOINHERIT
      | LOGIN | NOLOGIN
+     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
      | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
      | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'

***************
*** 118,123 ****
--- 119,135 ----
        <term><literal>NOINHERIT</literal></term>
        <term><literal>LOGIN</literal></term>
        <term><literal>NOLOGIN</literal></term>
+       <term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
+       <listitem>
+        <para>
+         If role can login, this specifies many concurrent connections
+         role can make. Default (-1) means unlimited
+         (limited by max_connections config variable only),
+         zero (0) means that role can't connect to server.
+        </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
        <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
        <term><literal>ENCRYPTED</></term>
        <term><literal>UNENCRYPTED</></term>
***************
*** 225,230 ****
--- 237,250 ----
    </para>

    <para>
+    Set role's maximum connections:
+
+ <programlisting>
+ ALTER ROLE peter WITH CONNECTION LIMIT 20;
+ </programlisting>
+   </para>
+
+   <para>
     Give a role a non-default setting of the
     <xref linkend="guc-maintenance-work-mem"> parameter:

Index: doc/src/sgml/ref/alter_user.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/alter_user.sgml,v
retrieving revision 1.38
diff -c -r1.38 alter_user.sgml
*** doc/src/sgml/ref/alter_user.sgml    26 Jul 2005 23:24:02 -0000    1.38
--- doc/src/sgml/ref/alter_user.sgml    30 Jul 2005 18:48:12 -0000
***************
*** 30,35 ****
--- 30,36 ----
      | CREATEUSER | NOCREATEUSER
      | INHERIT | NOINHERIT
      | LOGIN | NOLOGIN
+     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
      | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
      | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'

Index: doc/src/sgml/ref/create_database.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/create_database.sgml,v
retrieving revision 1.43
diff -c -r1.43 create_database.sgml
*** doc/src/sgml/ref/create_database.sgml    29 Oct 2004 03:17:22 -0000    1.43
--- doc/src/sgml/ref/create_database.sgml    30 Jul 2005 18:48:12 -0000
***************
*** 24,30 ****
      [ [ WITH ] [ OWNER [=] <replaceable class="parameter">dbowner</replaceable> ]
             [ TEMPLATE [=] <replaceable class="parameter">template</replaceable> ]
             [ ENCODING [=] <replaceable class="parameter">encoding</replaceable> ]
!            [ TABLESPACE [=] <replaceable class="parameter">tablespace</replaceable> ] ]
  </synopsis>
   </refsynopsisdiv>

--- 24,31 ----
      [ [ WITH ] [ OWNER [=] <replaceable class="parameter">dbowner</replaceable> ]
             [ TEMPLATE [=] <replaceable class="parameter">template</replaceable> ]
             [ ENCODING [=] <replaceable class="parameter">encoding</replaceable> ]
!            [ TABLESPACE [=] <replaceable class="parameter">tablespace</replaceable> ]
!            [ CONNECTION LIMIT [=] <replaceable class="parameter">connlimit</replaceable> ] ]
  </synopsis>
   </refsynopsisdiv>

***************
*** 123,128 ****
--- 124,140 ----
         </para>
        </listitem>
       </varlistentry>
+      <varlistentry>
+       <term><replaceable class="parameter">connlimit</replaceable></term>
+       <listitem>
+        <para>
+         Number specifying how many concurrent connections can be made
+         to this database. Default (-1) means unlimited
+         (limited by max_connections config variable only),
+         zero (0) means that no connections are allowed.
+        </para>
+       </listitem>
+      </varlistentry>
      </variablelist>

    <para>
Index: doc/src/sgml/ref/create_role.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/create_role.sgml,v
retrieving revision 1.1
diff -c -r1.1 create_role.sgml
*** doc/src/sgml/ref/create_role.sgml    26 Jul 2005 23:24:02 -0000    1.1
--- doc/src/sgml/ref/create_role.sgml    30 Jul 2005 18:48:13 -0000
***************
*** 30,35 ****
--- 30,36 ----
      | CREATEUSER | NOCREATEUSER
      | INHERIT | NOINHERIT
      | LOGIN | NOLOGIN
+     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
      | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
      | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
      | IN ROLE <replaceable class="PARAMETER">rolename</replaceable> [, ...]
***************
*** 173,178 ****
--- 174,191 ----
       </varlistentry>

       <varlistentry>
+       <term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
+       <listitem>
+        <para>
+         If role can login, this specifies many concurrent connections
+         role can make. Default (-1) means unlimited
+         (limited by max_connections config variable only),
+         zero (0) means that role can't connect to server.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
        <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
        <listitem>
         <para>
Index: doc/src/sgml/ref/create_user.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/ref/create_user.sgml,v
retrieving revision 1.37
diff -c -r1.37 create_user.sgml
*** doc/src/sgml/ref/create_user.sgml    26 Jul 2005 23:24:02 -0000    1.37
--- doc/src/sgml/ref/create_user.sgml    30 Jul 2005 18:48:13 -0000
***************
*** 30,35 ****
--- 30,36 ----
      | CREATEUSER | NOCREATEUSER
      | INHERIT | NOINHERIT
      | LOGIN | NOLOGIN
+     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
      | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
      | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
      | IN ROLE <replaceable class="PARAMETER">rolename</replaceable> [, ...]

Re: per user/database connections limit again

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> I have "worked over" your patch and I think it is ready for application.

I've made another pass over this and should be able to commit tomorrow
(I'm about to knock off for today, and ran out of time to test
pg_dumpall).  One thing I changed was that it didn't make sense to me
for CREATE DATABASE to copy the template database's datconnlimit.
We don't copy its datallowconn or datconfig, so why datconnlimit?

BTW I disagree with removing datallowconn; that is different from
datconnlimit = 0 because it is enforced even against superusers.
(Similar remarks apply for rolcanlogin vs rolconnlimit.)

            regards, tom lane


Attachment

Re: per user/database connections limit again

From
Peter Eisentraut
Date:
Am Montag, 25. Juli 2005 18:31 schrieb Tom Lane:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > The new syntax for this command is CREATE/ALTER DATABASE/USER:
> >                 | MAX CONNECTIONS Iconst
> >
> > This adds 'max' as a keyword, though at a fairly unreserved level, I
> > think.  Should we use the syntax LIMIT CONNECTIONS so we don't have to
> > add MAX as a keyword at all?
>
> I didn't like that either.  I was thinking of just CONNECTIONS.
> LIMIT CONNECTIONS sort of works grammatically, I guess.

Would this not work in the context of the general user-specific ALTER USER ...
SET something = something?

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

Re: per user/database connections limit again

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Am Montag, 25. Juli 2005 18:31 schrieb Tom Lane:
> > Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > > The new syntax for this command is CREATE/ALTER DATABASE/USER:
> > >                 | MAX CONNECTIONS Iconst
> > >
> > > This adds 'max' as a keyword, though at a fairly unreserved level, I
> > > think.  Should we use the syntax LIMIT CONNECTIONS so we don't have to
> > > add MAX as a keyword at all?
> >
> > I didn't like that either.  I was thinking of just CONNECTIONS.
> > LIMIT CONNECTIONS sort of works grammatically, I guess.
>
> Would this not work in the context of the general user-specific ALTER USER ...
> SET something = something?

No because it isn't a GUC variable, it is per-user/db value.  We could
have used that syntax, but it might confuse people.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Bruce Momjian
Date:
An updated version of your patch has been applied and will be in 8.1.  Thanks.

---------------------------------------------------------------------------

pgman wrote:
>
> I have "worked over" your patch and I think it is ready for application.
>
> I changed the syntax to CONNECTION LIMIT, which seems most natural.  We
> could skip CONNECTION and just use a LIMIT keyword, but that seems too
> terse.
>
> I removed your use of the pg_auth flat file.  By the time you have the
> PROC entry to do your lookups, you might as well just use the system
> cache.
>
> There is a race condition in the code because we set our PROC entry
> before we check for other entries.  If there is one connection left and
> two backends do this at the same time, they would both fail, while one
> should fail and the other succeed. Without a lock, I see no way to avoid
> it so I just commented it in the code.
>
> Also, I felt that zero should mean allow no/zero connections, rather
> than representing unlimited connections.  I used -1 for unlimited.  We
> can either document the use of -1, or add syntax to allow NO CONNECTION
> LIMIT, or something like that.
>
> The patch requires a catalog version update when applied.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: per user/database connections limit again

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Peter Eisentraut wrote:
>> Would this not work in the context of the general user-specific ALTER USER ...
>> SET something = something?

> No because it isn't a GUC variable, it is per-user/db value.  We could
> have used that syntax, but it might confuse people.

Yeah --- casting it as a GUC would create issues like "what is the
global default?".  I think treating it as a hard-wired feature is fine.

            regards, tom lane

Re: per user/database connections limit again

From
Peter Eisentraut
Date:
Am Montag, 1. August 2005 16:08 schrieb Bruce Momjian:
> > Would this not work in the context of the general user-specific ALTER
> > USER ... SET something = something?
>
> No because it isn't a GUC variable, it is per-user/db value.

GUC supports per-user/per-db values.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

Re: per user/database connections limit again

From
Tom Lane
Date:
Peter Eisentraut <peter_e@gmx.net> writes:
> Am Montag, 1. August 2005 16:08 schrieb Bruce Momjian:
>>> Would this not work in the context of the general user-specific ALTER
>>> USER ... SET something = something?
>>
>> No because it isn't a GUC variable, it is per-user/db value.

> GUC supports per-user/per-db values.

But not in the style that we'd want this to work.  You couldn't just
invent a single "connection_limit" variable, because a per-user setting
would override a per-database setting, which is not the desired
behavior.  You'd have to invent two separate GUC variables, and there
would be nothing except convention enforcing that they be set through
ALTER USER and ALTER DATABASE rather than at other random places.

We could do it that way, but it strikes me as messy and confusing,
and I don't see any actual benefit other than saving a few lines of
(already written) code.  In what way would a GUC-based implementation
be more useful than what's there?

            regards, tom lane

Re: per user/database connections limit again

From
Petr Jelinek
Date:
Peter Eisentraut wrote:

>GUC supports per-user/per-db values.
>
>
We already had discussion here about GUC for this and we agreed that
catalog change is better than new GUC variable in this case.

--
Regards
Petr Jelinek (PJMODOS)