From 7ceed2a9b5f19c59a2797c5aa31d801c32cb0cc4 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 9 Jun 2001 23:21:55 +0000
Subject: [PATCH] Allow GRANT/REVOKE to/from more than one user per invocation.
  Command tag for GRANT/REVOKE is now just that, not "CHANGE".

On the way, migrate some of the aclitem internal representation away from
the parser and build a real parse tree instead.  Also add some 'const'
qualifiers.
---
 doc/src/sgml/ref/grant.sgml              |   4 +-
 doc/src/sgml/ref/revoke.sgml             |   4 +-
 src/backend/catalog/aclchk.c             | 197 ++++++++++++++---------
 src/backend/nodes/copyfuncs.c            |  37 +++--
 src/backend/nodes/equalfuncs.c           |  27 +++-
 src/backend/parser/gram.y                |  49 ++++--
 src/backend/tcop/utility.c               |  12 +-
 src/backend/utils/adt/acl.c              |  93 +++--------
 src/backend/utils/adt/varchar.c          |   4 +-
 src/include/nodes/nodes.h                |   5 +-
 src/include/nodes/parsenodes.h           |  24 ++-
 src/include/utils/acl.h                  |  18 +--
 src/test/regress/expected/privileges.out |  14 +-
 src/test/regress/sql/privileges.sql      |   6 +-
 14 files changed, 280 insertions(+), 214 deletions(-)

diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 1399d049d77..98f221cf192 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/grant.sgml,v 1.10 2001/05/27 09:59:28 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/grant.sgml,v 1.11 2001/06/09 23:21:54 petere Exp $
 Postgres documentation
 -->
 
@@ -18,7 +18,7 @@ Postgres documentation
 <synopsis>
 GRANT { { SELECT | INSERT | UPDATE | DELETE | RULE | REFERENCES | TRIGGER } [,...] | ALL [ PRIVILEGES ] }
     ON [ TABLE ] <replaceable class="PARAMETER">objectname</replaceable> [, ...]
-    TO { <replaceable class="PARAMETER">username</replaceable> | GROUP <replaceable class="PARAMETER">groupname</replaceable> | PUBLIC }
+    TO { <replaceable class="PARAMETER">username</replaceable> | GROUP <replaceable class="PARAMETER">groupname</replaceable> | PUBLIC } [, ...]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index a9988fbc1ea..f77ed62ea6b 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/revoke.sgml,v 1.14 2001/05/27 09:59:28 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/revoke.sgml,v 1.15 2001/06/09 23:21:54 petere Exp $
 Postgres documentation
 -->
 
@@ -18,7 +18,7 @@ Postgres documentation
 <synopsis>
 REVOKE { { SELECT | INSERT | UPDATE | DELETE | RULE | REFERENCES | TRIGGER } [,...] | ALL [ PRIVILEGES ] }
     ON [ TABLE ] <replaceable class="PARAMETER">object</replaceable> [, ...]
-    FROM { <replaceable class="PARAMETER">username</replaceable> | GROUP <replaceable class="PARAMETER">groupname</replaceable> | PUBLIC }
+    FROM { <replaceable class="PARAMETER">username</replaceable> | GROUP <replaceable class="PARAMETER">groupname</replaceable> | PUBLIC } [, ...]
 </synopsis>
  </refsynopsisdiv>
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index f772ea3a534..27479533844 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.49 2001/06/05 19:34:56 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/aclchk.c,v 1.50 2001/06/09 23:21:54 petere Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -63,95 +63,132 @@ dumpacl(Acl *acl)
 
 #endif /* ACLDEBUG */
 
+
 /*
- * ChangeAcl
+ * Called to execute the utility commands GRANT and REVOKE
  */
 void
-ChangeAcl(char *relname,
-		  AclItem *mod_aip,
-		  unsigned modechg)
+ExecuteGrantStmt(GrantStmt *stmt)
 {
-	unsigned	i;
-	Acl		   *old_acl,
-			   *new_acl;
-	Relation	relation;
-	HeapTuple	tuple;
-	HeapTuple	newtuple;
-	Datum		aclDatum;
-	Datum		values[Natts_pg_class];
-	char		nulls[Natts_pg_class];
-	char		replaces[Natts_pg_class];
-	Relation	idescs[Num_pg_class_indices];
-	bool		isNull;
+	List	   *i;
+	List	   *j;
 
-	/*
-	 * Find the pg_class tuple matching 'relname' and extract the ACL. If
-	 * there's no ACL, create a default using the pg_class.relowner field.
-	 */
-	relation = heap_openr(RelationRelationName, RowExclusiveLock);
-	tuple = SearchSysCache(RELNAME,
-						   PointerGetDatum(relname),
-						   0, 0, 0);
-	if (!HeapTupleIsValid(tuple))
+	/* see comment in pg_type.h */
+	Assert(ACLITEMSIZE == sizeof(AclItem));
+
+	foreach(i, stmt->relnames)
 	{
+		char	   *relname = strVal(lfirst(i));
+		Relation	relation;
+		HeapTuple	tuple;
+		Form_pg_class pg_class_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		unsigned	i;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_class];
+		char		nulls[Natts_pg_class];
+		char		replaces[Natts_pg_class];
+
+
+		if (!pg_ownercheck(GetUserId(), relname, RELNAME))
+			elog(ERROR, "permission denied");
+
+		/* open pg_class */
+		relation = heap_openr(RelationRelationName, RowExclusiveLock);
+		tuple = SearchSysCache(RELNAME,
+							   PointerGetDatum(relname),
+							   0, 0, 0);
+		if (!HeapTupleIsValid(tuple))
+		{
+			heap_close(relation, RowExclusiveLock);
+			elog(ERROR, "relation \"%s\" not found",
+				 relname);
+		}
+		pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
+
+		if (pg_class_tuple->relkind == RELKIND_INDEX)
+			elog(ERROR, "\"%s\" is an index",
+				 relname);
+
+		/*
+		 * If there's no ACL, create a default using the
+		 * pg_class.relowner field.
+		 */
+		aclDatum = SysCacheGetAttr(RELNAME, tuple, Anum_pg_class_relacl,
+								   &isNull);
+		if (isNull)
+			old_acl = acldefault(relname, pg_class_tuple->relowner);
+		else
+			/* get a detoasted copy of the rel's ACL */
+			old_acl = DatumGetAclPCopy(aclDatum);
+
+#ifdef ACLDEBUG
+		dumpacl(old_acl);
+#endif
+		new_acl = old_acl;
+
+		foreach(j, stmt->grantees)
+		{
+			PrivGrantee *grantee = (PrivGrantee *)lfirst(j);
+			char	   *granteeString;
+			char	   *aclString;
+			AclItem		aclitem;
+			unsigned	modechg;
+
+			if (grantee->username)
+				granteeString = aclmakeuser("U", grantee->username);
+			else if (grantee->groupname)
+				granteeString = aclmakeuser("G", grantee->groupname);
+			else
+				granteeString = aclmakeuser("A", "");
+
+			aclString = makeAclString(stmt->privileges, granteeString,
+									  stmt->is_grant ? '+' : '-');
+
+			/* Convert string ACL spec into internal form */
+			aclparse(aclString, &aclitem, &modechg);
+			new_acl = aclinsert3(new_acl, &aclitem, modechg);
+#ifdef ACLDEBUG
+			dumpacl(new_acl);
+#endif
+		}
+
+		/* finished building new ACL value, now insert it */
+		for (i = 0; i < Natts_pg_class; ++i)
+		{
+			replaces[i] = ' ';
+			nulls[i] = ' ';		/* ignored if replaces[i]==' ' anyway */
+			values[i] = (Datum) NULL; /* ignored if replaces[i]==' ' anyway */
+		}
+		replaces[Anum_pg_class_relacl - 1] = 'r';
+		values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
+		newtuple = heap_modifytuple(tuple, relation, values, nulls, replaces);
+
+		ReleaseSysCache(tuple);
+
+		simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+		{
+			/* keep the catalog indexes up to date */
+			Relation	idescs[Num_pg_class_indices];
+			CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices,
+							   idescs);
+			CatalogIndexInsert(idescs, Num_pg_class_indices, relation, newtuple);
+			CatalogCloseIndices(Num_pg_class_indices, idescs);
+		}
+
+		pfree(old_acl);
+		pfree(new_acl);
+
 		heap_close(relation, RowExclusiveLock);
-		elog(ERROR, "ChangeAcl: class \"%s\" not found",
-			 relname);
 	}
-
-	aclDatum = SysCacheGetAttr(RELNAME, tuple, Anum_pg_class_relacl,
-							   &isNull);
-	if (isNull)
-	{
-		/* No ACL, so build default ACL for rel */
-		AclId		ownerId;
-
-		ownerId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;
-		old_acl = acldefault(relname, ownerId);
-	}
-	else
-	{
-		/* get a detoasted copy of the rel's ACL */
-		old_acl = DatumGetAclPCopy(aclDatum);
-	}
-
-#ifdef ACLDEBUG
-	dumpacl(old_acl);
-#endif
-
-	new_acl = aclinsert3(old_acl, mod_aip, modechg);
-
-#ifdef ACLDEBUG
-	dumpacl(new_acl);
-#endif
-
-	for (i = 0; i < Natts_pg_class; ++i)
-	{
-		replaces[i] = ' ';
-		nulls[i] = ' ';			/* ignored if replaces[i] == ' ' anyway */
-		values[i] = (Datum) NULL;		/* ignored if replaces[i] == ' '
-										 * anyway */
-	}
-	replaces[Anum_pg_class_relacl - 1] = 'r';
-	values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
-	newtuple = heap_modifytuple(tuple, relation, values, nulls, replaces);
-
-	ReleaseSysCache(tuple);
-
-	simple_heap_update(relation, &newtuple->t_self, newtuple);
-
-	/* keep the catalog indices up to date */
-	CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices,
-					   idescs);
-	CatalogIndexInsert(idescs, Num_pg_class_indices, relation, newtuple);
-	CatalogCloseIndices(Num_pg_class_indices, idescs);
-
-	heap_close(relation, RowExclusiveLock);
-
-	pfree(old_acl);
-	pfree(new_acl);
 }
 
+
+
 AclId
 get_grosysid(char *groname)
 {
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 07907b63683..77ae4fb781a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.143 2001/06/05 05:26:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.144 2001/06/09 23:21:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,7 +24,6 @@
 
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
-#include "utils/acl.h"
 
 
 /*
@@ -1856,14 +1855,29 @@ _copyAlterTableStmt(AlterTableStmt *from)
 	return newnode;
 }
 
-static ChangeACLStmt *
-_copyChangeACLStmt(ChangeACLStmt *from)
+static GrantStmt *
+_copyGrantStmt(GrantStmt *from)
 {
-	ChangeACLStmt *newnode = makeNode(ChangeACLStmt);
+	GrantStmt *newnode = makeNode(GrantStmt);
 
-	Node_Copy(from, newnode, relNames);
-	if (from->aclString)
-		newnode->aclString = pstrdup(from->aclString);
+	newnode->is_grant = from->is_grant;
+	Node_Copy(from, newnode, relnames);
+	if (from->privileges)
+		newnode->privileges = pstrdup(from->privileges);
+	Node_Copy(from, newnode, grantees);
+
+	return newnode;
+}
+
+static PrivGrantee *
+_copyPrivGrantee(PrivGrantee *from)
+{
+	PrivGrantee *newnode = makeNode(PrivGrantee);
+
+	if (from->username)
+		newnode->username = pstrdup(from->username);
+	if (from->groupname)
+		newnode->groupname = pstrdup(from->groupname);
 
 	return newnode;
 }
@@ -2729,8 +2743,8 @@ copyObject(void *from)
 		case T_AlterTableStmt:
 			retval = _copyAlterTableStmt(from);
 			break;
-		case T_ChangeACLStmt:
-			retval = _copyChangeACLStmt(from);
+		case T_GrantStmt:
+			retval = _copyGrantStmt(from);
 			break;
 		case T_ClosePortalStmt:
 			retval = _copyClosePortalStmt(from);
@@ -2943,6 +2957,9 @@ copyObject(void *from)
 		case T_FkConstraint:
 			retval = _copyFkConstraint(from);
 			break;
+		case T_PrivGrantee:
+			retval = _copyPrivGrantee(from);
+			break;
 
 		default:
 			elog(ERROR, "copyObject: don't know how to copy node type %d",
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 656c1e9ea67..f7bfcc19776 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.91 2001/06/05 05:26:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.92 2001/06/09 23:21:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,7 +29,6 @@
 
 #include "nodes/plannodes.h"
 #include "nodes/relation.h"
-#include "utils/acl.h"
 #include "utils/datum.h"
 
 
@@ -755,16 +754,27 @@ _equalAlterTableStmt(AlterTableStmt *a, AlterTableStmt *b)
 }
 
 static bool
-_equalChangeACLStmt(ChangeACLStmt *a, ChangeACLStmt *b)
+_equalGrantStmt(GrantStmt *a, GrantStmt *b)
 {
-	if (!equal(a->relNames, b->relNames))
+	if (a->is_grant != b->is_grant)
 		return false;
-	if (!equalstr(a->aclString, b->aclString))
+	if (!equal(a->relnames, b->relnames))
+		return false;
+	if (!equalstr(a->privileges, b->privileges))
+		return false;
+	if (!equal(a->grantees, b->grantees))
 		return false;
 
 	return true;
 }
 
+static bool
+_equalPrivGrantee(PrivGrantee *a, PrivGrantee *b)
+{
+	return equalstr(a->username, b->username)
+		&& equalstr(a->groupname, b->groupname);
+}	
+
 static bool
 _equalClosePortalStmt(ClosePortalStmt *a, ClosePortalStmt *b)
 {
@@ -1898,8 +1908,8 @@ equal(void *a, void *b)
 		case T_AlterTableStmt:
 			retval = _equalAlterTableStmt(a, b);
 			break;
-		case T_ChangeACLStmt:
-			retval = _equalChangeACLStmt(a, b);
+		case T_GrantStmt:
+			retval = _equalGrantStmt(a, b);
 			break;
 		case T_ClosePortalStmt:
 			retval = _equalClosePortalStmt(a, b);
@@ -2113,6 +2123,9 @@ equal(void *a, void *b)
 		case T_FkConstraint:
 			retval = _equalFkConstraint(a, b);
 			break;
+		case T_PrivGrantee:
+			retval = _equalPrivGrantee(a, b);
+			break;
 
 		default:
 			elog(NOTICE, "equal: don't know whether nodes of type %d are equal",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 308b49fd727..263830244dc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.229 2001/06/07 04:50:56 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.230 2001/06/09 23:21:54 petere Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -177,7 +177,9 @@ static void doNegateFloat(Value *v);
 		OptUseOp, opt_class, SpecialRuleRelation
 
 %type <str>		opt_level, opt_encoding
-%type <str>		privileges, operation_commalist, grantee
+%type <str>		privileges, operation_commalist
+%type <node>	grantee
+%type <list>	grantee_list
 %type <chr>		operation, TriggerOneEvent
 
 %type <list>	stmtblock, stmtmulti,
@@ -2241,14 +2243,18 @@ from_in:  IN
 
 /*****************************************************************************
  *
- *		QUERY:
- *				GRANT [privileges] ON [relation_name_list] TO [GROUP] grantee
+ * GRANT privileges ON [TABLE] relation_name_list TO [GROUP] grantee, ...
  *
  *****************************************************************************/
 
-GrantStmt:  GRANT privileges ON opt_table relation_name_list TO grantee opt_with_grant
+GrantStmt:  GRANT privileges ON opt_table relation_name_list TO grantee_list opt_with_grant
 				{
-					$$ = (Node*)makeAclStmt($2,$5,$7,'+');
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = true;
+					n->relnames = $5;
+					n->privileges = $2;
+					n->grantees = $7;
+					$$ = (Node*)n;
 				}
 		;
 
@@ -2308,18 +2314,31 @@ operation:  SELECT
 
 grantee:  PUBLIC
 				{
-						$$ = aclmakeuser("A","");
+					PrivGrantee *n = makeNode(PrivGrantee);
+					n->username = NULL;
+					n->groupname = NULL;
+					$$ = (Node *)n;
 				}
 		| GROUP ColId
 				{
-						$$ = aclmakeuser("G",$2);
+					PrivGrantee *n = makeNode(PrivGrantee);
+					n->username = NULL;
+					n->groupname = $2;
+					$$ = (Node *)n;
 				}
 		| ColId
 				{
-						$$ = aclmakeuser("U",$1);
+					PrivGrantee *n = makeNode(PrivGrantee);
+					n->username = $1;
+					n->groupname = NULL;
+					$$ = (Node *)n;
 				}
 		;
 
+grantee_list: grantee					{ $$ = makeList1($1); }
+		| grantee_list ',' grantee		{ $$ = lappend($1, $3); }
+
+
 opt_with_grant:  WITH GRANT OPTION
 				{
 					elog(ERROR,"WITH GRANT OPTION is not supported.  Only relation owners can set privileges");
@@ -2330,14 +2349,18 @@ opt_with_grant:  WITH GRANT OPTION
 
 /*****************************************************************************
  *
- *		QUERY:
- *				REVOKE [privileges] ON [relation_name] FROM [user]
+ * REVOKE privileges ON [TABLE] relation_name_list FROM user, ...
  *
  *****************************************************************************/
 
-RevokeStmt:  REVOKE privileges ON opt_table relation_name_list FROM grantee
+RevokeStmt:  REVOKE privileges ON opt_table relation_name_list FROM grantee_list
 				{
-					$$ = (Node*)makeAclStmt($2,$5,$7,'-');
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = false;
+					n->relnames = $5;
+					n->privileges = $2;
+					n->grantees = $7;
+					$$ = (Node *)n;
 				}
 		;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5e6a044b921..bef1d6844a0 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.112 2001/05/30 20:52:32 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.113 2001/06/09 23:21:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -472,13 +472,13 @@ ProcessUtility(Node *parsetree,
 			break;
 
 
-		case T_ChangeACLStmt:
+		case T_GrantStmt:
 			{
-				ChangeACLStmt *stmt = (ChangeACLStmt *) parsetree;
+				GrantStmt *stmt = (GrantStmt *) parsetree;
+				commandTag = stmt->is_grant ? "GRANT" : "REVOKE";
+				set_ps_display(commandTag);
 
-				set_ps_display(commandTag = "CHANGE");
-
-				ExecuteChangeACLStmt(stmt);
+				ExecuteGrantStmt(stmt);
 			}
 			break;
 
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 1da525bd032..eeb95430271 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.60 2001/06/05 19:34:56 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/acl.c,v 1.61 2001/06/09 23:21:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,15 +27,13 @@
 #include "utils/memutils.h"
 #include "utils/syscache.h"
 
-static char *getid(char *s, char *n);
-static bool aclitemeq(AclItem *a1, AclItem *a2);
-static bool aclitemgt(AclItem *a1, AclItem *a2);
-static char *aclparse(char *s, AclItem *aip, unsigned *modechg);
+static const char *getid(const char *s, char *n);
+static bool aclitemeq(const AclItem *a1, const AclItem *a2);
+static bool aclitemgt(const AclItem *a1, const AclItem *a2);
 
 #define ACL_IDTYPE_GID_KEYWORD	"group"
 #define ACL_IDTYPE_UID_KEYWORD	"user"
 
-
 /*
  * getid
  *		Consumes the first alphanumeric string (identifier) found in string
@@ -48,11 +46,11 @@ static char *aclparse(char *s, AclItem *aip, unsigned *modechg);
  *		- loads the identifier into 'name'.  (If no identifier is found, 'name'
  *		  contains an empty string.)  name must be NAMEDATALEN bytes.
  */
-static char *
-getid(char *s, char *n)
+static const char *
+getid(const char *s, char *n)
 {
 	unsigned	len;
-	char	   *id;
+	const char *id;
 	int			in_quotes = 0;
 
 	Assert(s && n);
@@ -105,8 +103,8 @@ getid(char *s, char *n)
  *		  UID/GID, id type identifier and mode type values.
  *		- loads 'modechg' with the mode change flag.
  */
-static char *
-aclparse(char *s, AclItem *aip, unsigned *modechg)
+const char *
+aclparse(const char *s, AclItem *aip, unsigned *modechg)
 {
 	HeapTuple	htup;
 	char		name[NAMEDATALEN];
@@ -245,7 +243,7 @@ makeacl(int n)
 Datum
 aclitemin(PG_FUNCTION_ARGS)
 {
-	char	   *s = PG_GETARG_CSTRING(0);
+	const char *s = PG_GETARG_CSTRING(0);
 	AclItem    *aip;
 	unsigned	modechg;
 
@@ -351,13 +349,13 @@ aclitemout(PG_FUNCTION_ARGS)
  *		a boolean value indicating = or >
  */
 static bool
-aclitemeq(AclItem *a1, AclItem *a2)
+aclitemeq(const AclItem *a1, const AclItem *a2)
 {
 	return a1->ai_idtype == a2->ai_idtype && a1->ai_id == a2->ai_id;
 }
 
 static bool
-aclitemgt(AclItem *a1, AclItem *a2)
+aclitemgt(const AclItem *a1, const AclItem *a2)
 {
 	return ((a1->ai_idtype > a2->ai_idtype) ||
 			(a1->ai_idtype == a2->ai_idtype && a1->ai_id > a2->ai_id));
@@ -371,7 +369,7 @@ aclitemgt(AclItem *a1, AclItem *a2)
  * newly-created tables (or any table with a NULL acl entry in pg_class)
  */
 Acl *
-acldefault(char *relname, AclId ownerid)
+acldefault(const char *relname, AclId ownerid)
 {
 	Acl		   *acl;
 	AclItem    *aip;
@@ -398,7 +396,7 @@ acldefault(char *relname, AclId ownerid)
  * NB: caller is responsible for having detoasted the input ACL, if needed.
  */
 Acl *
-aclinsert3(Acl *old_acl, AclItem *mod_aip, unsigned modechg)
+aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg)
 {
 	Acl		   *new_acl;
 	AclItem    *old_aip,
@@ -595,41 +593,6 @@ aclcontains(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(false);
 }
 
-/*
- * ExecuteChangeACLStmt
- *		Called to execute the utility command type ChangeACLStmt
- */
-void
-ExecuteChangeACLStmt(ChangeACLStmt *stmt)
-{
-	AclItem aclitem;
-	unsigned	modechg;
-	List	   *i;
-
-	/* see comment in pg_type.h */
-	Assert(ACLITEMSIZE == sizeof(AclItem));
-
-	/* Convert string ACL spec into internal form */
-	aclparse(stmt->aclString, &aclitem, &modechg);
-
-	foreach(i, stmt->relNames)
-	{
-		char	   *relname = strVal(lfirst(i));
-		Relation	rel;
-
-		rel = heap_openr(relname, AccessExclusiveLock);
-		if (rel && rel->rd_rel->relkind == RELKIND_INDEX)
-			elog(ERROR, "\"%s\" is an index relation",
-				 relname);
-		if (!pg_ownercheck(GetUserId(), relname, RELNAME))
-			elog(ERROR, "you do not own class \"%s\"",
-				 relname);
-		ChangeAcl(relname, &aclitem, modechg);
-		/* close rel, but keep lock until end of xact */
-		heap_close(rel, NoLock);
-	}
-}
-
 
 /*
  * Parser support routines for ACL-related statements.
@@ -648,7 +611,7 @@ ExecuteChangeACLStmt(ChangeACLStmt *stmt)
  * does not add duplicate privileges
  */
 char *
-aclmakepriv(char *old_privlist, char new_priv)
+aclmakepriv(const char *old_privlist, char new_priv)
 {
 	char	   *priv;
 	int			i;
@@ -698,7 +661,7 @@ aclmakepriv(char *old_privlist, char new_priv)
  * Per above comments, we can't try to resolve a user or group name here.
  */
 char *
-aclmakeuser(char *user_type, char *user)
+aclmakeuser(const char *user_type, const char *user)
 {
 	char	   *user_list;
 
@@ -707,22 +670,20 @@ aclmakeuser(char *user_type, char *user)
 	return user_list;
 }
 
+
 /*
- * makeAclStmt:
- *	  create a ChangeACLStmt at parse time.
- *	  we take in the privileges, relation_name_list, and grantee
- *	  as well as a single character '+' or '-' to indicate grant or revoke
+ * makeAclString:  We take in the privileges and grantee as well as a
+ * single character '+' or '-' to indicate grant or revoke.
  *
  * We convert the information to the same external form recognized by
- * aclitemin (see aclparse), and save that string in the ChangeACLStmt.
- * Conversion to internal form happens when the statement is executed.
+ * aclitemin (see aclparse) and return that string.  Conversion to
+ * internal form happens when the statement is executed.
  */
-ChangeACLStmt *
-makeAclStmt(char *privileges, List *rel_list, char *grantee,
-			char grant_or_revoke)
+char *
+makeAclString(const char *privileges, const char *grantee, char grant_or_revoke)
 {
-	ChangeACLStmt *n = makeNode(ChangeACLStmt);
 	StringInfoData str;
+	char *ret;
 
 	initStringInfo(&str);
 
@@ -745,9 +706,7 @@ makeAclStmt(char *privileges, List *rel_list, char *grantee,
 		appendStringInfo(&str, "%c%s",
 						 grant_or_revoke, privileges);
 	}
-	n->relNames = rel_list;
-	n->aclString = pstrdup(str.data);
-
+	ret = pstrdup(str.data);
 	pfree(str.data);
-	return n;
+	return ret;
 }
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index cdd76afb55d..d70a256dfd1 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/varchar.c,v 1.79 2001/06/01 17:49:16 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/varchar.c,v 1.80 2001/06/09 23:21:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,7 @@
 #include "access/hash.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
-#include "utils/acl.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 25fe3955e1f..d62583c4d23 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.89 2001/04/24 00:08:38 tgl Exp $
+ * $Id: nodes.h,v 1.90 2001/06/09 23:21:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -148,7 +148,7 @@ typedef enum NodeTag
 	T_SelectStmt,
 	T_AlterTableStmt,
 	T_SetOperationStmt,
-	T_ChangeACLStmt,
+	T_GrantStmt,
 	T_ClosePortalStmt,
 	T_ClusterStmt,
 	T_CopyStmt,
@@ -224,6 +224,7 @@ typedef enum NodeTag
 	T_CaseWhen,
 	T_RowMarkXXX,				/* not used anymore; tag# available */
 	T_FkConstraint,
+	T_PrivGrantee,
 
 	/*
 	 * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (see fmgr.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 31aadb449d5..fe2d1bb7ffe 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.130 2001/06/04 23:27:23 momjian Exp $
+ * $Id: parsenodes.h,v 1.131 2001/06/09 23:21:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -137,15 +137,27 @@ typedef struct AlterTableStmt
 } AlterTableStmt;
 
 /* ----------------------
- *		Change ACL Statement
+ *		Grant Statement
  * ----------------------
  */
-typedef struct ChangeACLStmt
+
+typedef struct GrantStmt
 {
 	NodeTag		type;
-	List	   *relNames;
-	char	   *aclString;
-} ChangeACLStmt;
+	bool		is_grant;		/* not revoke */
+	List	   *relnames;
+	char	   *privileges;
+	List	   *grantees;
+} GrantStmt;
+
+
+typedef struct PrivGrantee
+{
+	NodeTag		type;
+	char	   *username;		/* if both are NULL then PUBLIC */
+	char	   *groupname;
+} PrivGrantee;
+
 
 /* ----------------------
  *		Close Portal Statement
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 2ea98d5cb6e..1cf751fad13 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: acl.h,v 1.33 2001/06/05 19:34:56 tgl Exp $
+ * $Id: acl.h,v 1.34 2001/06/09 23:21:55 petere Exp $
  *
  * NOTES
  *	  For backward-compatibility purposes we have to allow there
@@ -170,17 +170,14 @@ extern char *aclcheck_error_strings[];
 /*
  * routines used internally
  */
-extern Acl *acldefault(char *relname, AclId ownerid);
-
-extern Acl *aclinsert3(Acl *old_acl, AclItem *mod_aip, unsigned modechg);
+extern Acl *acldefault(const char *relname, AclId ownerid);
+extern Acl *aclinsert3(const Acl *old_acl, const AclItem *mod_aip, unsigned modechg);
 
 /*
  * routines used by the parser
  */
-extern char *aclmakepriv(char *old_privlist, char new_priv);
-extern char *aclmakeuser(char *user_type, char *user);
-extern ChangeACLStmt *makeAclStmt(char *privs, List *rel_list, char *grantee,
-			char grant_or_revoke);
+extern char *aclmakepriv(const char *old_privlist, char new_priv);
+extern char *aclmakeuser(const char *user_type, const char *user);
 
 /*
  * exported routines (from acl.c)
@@ -191,12 +188,13 @@ extern Datum aclitemout(PG_FUNCTION_ARGS);
 extern Datum aclinsert(PG_FUNCTION_ARGS);
 extern Datum aclremove(PG_FUNCTION_ARGS);
 extern Datum aclcontains(PG_FUNCTION_ARGS);
-extern void ExecuteChangeACLStmt(ChangeACLStmt *stmt);
+extern const char *aclparse(const char *s, AclItem *aip, unsigned *modechg);
+extern char *makeAclString(const char *privileges, const char *grantee, char grant_or_revoke);
 
 /*
  * prototypes for functions in aclchk.c
  */
-extern void ChangeAcl(char *relname, AclItem *mod_aip, unsigned modechg);
+extern void ExecuteGrantStmt(GrantStmt *stmt);
 extern AclId get_grosysid(char *groname);
 extern char *get_groname(AclId grosysid);
 
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index cb376e0bbc8..ea19667a169 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -39,7 +39,7 @@ SELECT * FROM atest1;
 (0 rows)
 
 GRANT ALL ON atest1 TO regressuser2;
-GRANT SELECT ON atest1 TO regressuser3;
+GRANT SELECT ON atest1 TO regressuser3, regressuser4;
 SELECT * FROM atest1;
  a | b 
 ---+---
@@ -90,7 +90,7 @@ ERROR:  LOCK TABLE: permission denied
 COPY atest2 FROM stdin; -- fail
 ERROR:  atest2: Permission denied.
 GRANT ALL ON atest1 TO PUBLIC; -- fail
-ERROR:  you do not own class "atest1"
+ERROR:  permission denied
 -- checks in subquery, both ok
 SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) );
  a | b 
@@ -146,6 +146,13 @@ SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) );
 ERROR:  atest2: Permission denied.
 SET SESSION AUTHORIZATION regressuser4;
 COPY atest2 FROM stdin; -- ok
+SELECT * FROM atest1; -- ok
+ a |  b  
+---+-----
+ 1 | two
+ 1 | two
+(2 rows)
+
 -- groups
 SET SESSION AUTHORIZATION regressuser3;
 CREATE TABLE atest3 (one int, two int, three int);
@@ -167,8 +174,7 @@ SELECT * FROM atestv1; -- ok
  1 | two
 (2 rows)
 
-GRANT SELECT ON atestv1 TO regressuser4;
-GRANT SELECT ON atestv3 TO regressuser4;
+GRANT SELECT ON atestv1, atestv3 TO regressuser4;
 SET SESSION AUTHORIZATION regressuser4;
 SELECT * FROM atestv1; -- ok
  a |  b  
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 1558273f7b2..2a096660834 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -34,7 +34,7 @@ REVOKE ALL ON atest1 FROM PUBLIC;
 SELECT * FROM atest1;
 
 GRANT ALL ON atest1 TO regressuser2;
-GRANT SELECT ON atest1 TO regressuser3;
+GRANT SELECT ON atest1 TO regressuser3, regressuser4;
 SELECT * FROM atest1;
 
 CREATE TABLE atest2 (col1 varchar(10), col2 boolean);
@@ -93,6 +93,7 @@ SET SESSION AUTHORIZATION regressuser4;
 COPY atest2 FROM stdin; -- ok
 bar	true
 \.
+SELECT * FROM atest1; -- ok
 
 
 -- groups
@@ -117,8 +118,7 @@ CREATE VIEW atestv2 AS SELECT * FROM atest2;
 CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok
 
 SELECT * FROM atestv1; -- ok
-GRANT SELECT ON atestv1 TO regressuser4;
-GRANT SELECT ON atestv3 TO regressuser4;
+GRANT SELECT ON atestv1, atestv3 TO regressuser4;
 
 SET SESSION AUTHORIZATION regressuser4;