diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 346867bbfc4b59d945674ad0d6ede4c9ea32f817..a8c3cb5ce07ca2d220c0c8c2f295928d4bb2381e 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.80 2004/01/10 23:28:44 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.81 2004/01/14 23:01:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -297,8 +297,8 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 											  makeAlias("*NEW*", NIL),
 											  false, false);
 	/* Must override addRangeTableEntry's default access-check flags */
-	rt_entry1->checkForRead = false;
-	rt_entry2->checkForRead = false;
+	rt_entry1->requiredPerms = 0;
+	rt_entry2->requiredPerms = 0;
 
 	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 9d64c979e08706279b0f48c1bebf6d80350ad2fc..a340477c47ab36121e3b854dd86667e4afb07547 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.226 2004/01/10 23:28:44 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.227 2004/01/14 23:01:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -86,8 +86,8 @@ static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid,
 		   EState *estate);
 static TupleTableSlot *EvalPlanQualNext(EState *estate);
 static void EndEvalPlanQual(EState *estate);
-static void ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation);
-static void ExecCheckXactReadOnly(Query *parsetree, CmdType operation);
+static void ExecCheckRTEPerms(RangeTblEntry *rte);
+static void ExecCheckXactReadOnly(Query *parsetree);
 static void EvalPlanQualStart(evalPlanQual *epq, EState *estate,
 				  evalPlanQual *priorepq);
 static void EvalPlanQualStop(evalPlanQual *epq);
@@ -136,8 +136,8 @@ ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
 	 * If the transaction is read-only, we need to check if any writes are
 	 * planned to non-temporary tables.
 	 */
-	if (!explainOnly)
-		ExecCheckXactReadOnly(queryDesc->parsetree, queryDesc->operation);
+	if (XactReadOnly && !explainOnly)
+		ExecCheckXactReadOnly(queryDesc->parsetree);
 
 	/*
 	 * Build EState, switch into per-query memory context for startup.
@@ -351,7 +351,7 @@ ExecutorRewind(QueryDesc *queryDesc)
  *		Check access permissions for all relations listed in a range table.
  */
 void
-ExecCheckRTPerms(List *rangeTable, CmdType operation)
+ExecCheckRTPerms(List *rangeTable)
 {
 	List	   *lp;
 
@@ -359,7 +359,7 @@ ExecCheckRTPerms(List *rangeTable, CmdType operation)
 	{
 		RangeTblEntry *rte = lfirst(lp);
 
-		ExecCheckRTEPerms(rte, operation);
+		ExecCheckRTEPerms(rte);
 	}
 }
 
@@ -368,18 +368,18 @@ ExecCheckRTPerms(List *rangeTable, CmdType operation)
  *		Check access permissions for a single RTE.
  */
 static void
-ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation)
+ExecCheckRTEPerms(RangeTblEntry *rte)
 {
+	AclMode		requiredPerms;
 	Oid			relOid;
 	AclId		userid;
-	AclResult	aclcheck_result;
 
 	/*
 	 * If it's a subquery, recursively examine its rangetable.
 	 */
 	if (rte->rtekind == RTE_SUBQUERY)
 	{
-		ExecCheckRTPerms(rte->subquery->rtable, operation);
+		ExecCheckRTPerms(rte->subquery->rtable);
 		return;
 	}
 
@@ -391,6 +391,13 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation)
 	if (rte->rtekind != RTE_RELATION)
 		return;
 
+	/*
+	 * No work if requiredPerms is empty.
+	 */
+	requiredPerms = rte->requiredPerms;
+	if (requiredPerms == 0)
+		return;
+
 	relOid = rte->relid;
 
 	/*
@@ -404,77 +411,68 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation)
 	 */
 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
-#define CHECK(MODE)		pg_class_aclcheck(relOid, userid, MODE)
+	/*
+	 * For each bit in requiredPerms, apply the required check.  (We can't
+	 * do this in one aclcheck call because aclcheck treats multiple bits
+	 * as OR semantics, when we want AND.)
+	 *
+	 * We use a well-known cute trick for isolating the rightmost one-bit
+	 * in a nonzero word.  See nodes/bitmapset.c for commentary.
+	 */
+#define RIGHTMOST_ONE(x) ((int32) (x) & -((int32) (x)))
 
-	if (rte->checkForRead)
+	while (requiredPerms != 0)
 	{
-		aclcheck_result = CHECK(ACL_SELECT);
-		if (aclcheck_result != ACLCHECK_OK)
-			aclcheck_error(aclcheck_result, ACL_KIND_CLASS,
-						   get_rel_name(relOid));
-	}
+		AclMode		thisPerm;
+		AclResult	aclcheck_result;
 
-	if (rte->checkForWrite)
-	{
-		/*
-		 * Note: write access in a SELECT context means SELECT FOR UPDATE.
-		 * Right now we don't distinguish that from true update as far as
-		 * permissions checks are concerned.
-		 */
-		switch (operation)
-		{
-			case CMD_INSERT:
-				aclcheck_result = CHECK(ACL_INSERT);
-				break;
-			case CMD_SELECT:
-			case CMD_UPDATE:
-				aclcheck_result = CHECK(ACL_UPDATE);
-				break;
-			case CMD_DELETE:
-				aclcheck_result = CHECK(ACL_DELETE);
-				break;
-			default:
-				elog(ERROR, "unrecognized operation code: %d",
-					 (int) operation);
-				aclcheck_result = ACLCHECK_OK;	/* keep compiler quiet */
-				break;
-		}
+		thisPerm = RIGHTMOST_ONE(requiredPerms);
+		requiredPerms &= ~thisPerm;
+
+		aclcheck_result = pg_class_aclcheck(relOid, userid, thisPerm);
 		if (aclcheck_result != ACLCHECK_OK)
 			aclcheck_error(aclcheck_result, ACL_KIND_CLASS,
 						   get_rel_name(relOid));
 	}
 }
 
+/*
+ * Check that the query does not imply any writes to non-temp tables.
+ */
 static void
-ExecCheckXactReadOnly(Query *parsetree, CmdType operation)
+ExecCheckXactReadOnly(Query *parsetree)
 {
-	if (!XactReadOnly)
-		return;
+	List	   *lp;
 
-	/* CREATE TABLE AS or SELECT INTO */
-	if (operation == CMD_SELECT && parsetree->into != NULL)
+	/*
+	 * CREATE TABLE AS or SELECT INTO?
+	 *
+	 * XXX should we allow this if the destination is temp?
+	 */
+	if (parsetree->into != NULL)
 		goto fail;
 
-	if (operation == CMD_DELETE || operation == CMD_INSERT
-		|| operation == CMD_UPDATE)
+	/* Fail if write permissions are requested on any non-temp table */
+	foreach(lp, parsetree->rtable)
 	{
-		List	   *lp;
+		RangeTblEntry *rte = lfirst(lp);
 
-		foreach(lp, parsetree->rtable)
+		if (rte->rtekind == RTE_SUBQUERY)
 		{
-			RangeTblEntry *rte = lfirst(lp);
+			ExecCheckXactReadOnly(rte->subquery);
+			continue;
+		}
 
-			if (rte->rtekind != RTE_RELATION)
-				continue;
+		if (rte->rtekind != RTE_RELATION)
+			continue;
 
-			if (!rte->checkForWrite)
-				continue;
+		if ((rte->requiredPerms & (~ACL_SELECT)) == 0)
+			continue;
 
-			if (isTempNamespace(get_rel_namespace(rte->relid)))
-				continue;
+		if (isTempNamespace(get_rel_namespace(rte->relid)))
+			continue;
 
-			goto fail;
-		}
+		goto fail;
 	}
 
 	return;
@@ -511,7 +509,7 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly)
 	 * rangetable here --- subplan RTEs will be checked during
 	 * ExecInitSubPlan().
 	 */
-	ExecCheckRTPerms(parseTree->rtable, operation);
+	ExecCheckRTPerms(parseTree->rtable);
 
 	/*
 	 * get information from query descriptor
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index da7d5915f20591b2976f908d2f7442cf0f22fdc1..1624f41fd540cf083cc241e467034f7f0262275a 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.59 2003/11/29 19:51:48 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.60 2004/01/14 23:01:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -670,10 +670,9 @@ ExecInitSubPlan(SubPlanState *node, EState *estate)
 	MemoryContext oldcontext;
 
 	/*
-	 * Do access checking on the rangetable entries in the subquery. Here,
-	 * we assume the subquery is a SELECT.
+	 * Do access checking on the rangetable entries in the subquery.
 	 */
-	ExecCheckRTPerms(subplan->rtable, CMD_SELECT);
+	ExecCheckRTPerms(subplan->rtable);
 
 	/*
 	 * initialize my state
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5a340ebaa9493b3f2d641b7dde4cb718ec5f7476..39f454fd3c9d38350ff8f0e63a6719e1173523c7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.276 2004/01/10 23:28:44 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.277 2004/01/14 23:01:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1258,8 +1258,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
 	COPY_NODE_FIELD(eref);
 	COPY_SCALAR_FIELD(inh);
 	COPY_SCALAR_FIELD(inFromCl);
-	COPY_SCALAR_FIELD(checkForRead);
-	COPY_SCALAR_FIELD(checkForWrite);
+	COPY_SCALAR_FIELD(requiredPerms);
 	COPY_SCALAR_FIELD(checkAsUser);
 
 	return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 932d79f31f48e1c1beaecf86175ee0295fac887b..7951fad039c629baf39ea0b7e18e5674d3cc5e38 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.214 2004/01/10 23:28:45 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.215 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1574,8 +1574,7 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 	COMPARE_NODE_FIELD(eref);
 	COMPARE_SCALAR_FIELD(inh);
 	COMPARE_SCALAR_FIELD(inFromCl);
-	COMPARE_SCALAR_FIELD(checkForRead);
-	COMPARE_SCALAR_FIELD(checkForWrite);
+	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
 
 	return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ce80cae4bdbf8e6478a8f4a4be97d53596a0a708..cd1fde7b5b7ffcc2f0d11c23dd9081017ab284ac 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.229 2004/01/06 04:31:01 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.230 2004/01/14 23:01:55 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -1358,9 +1358,8 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
-	WRITE_BOOL_FIELD(checkForRead);
-	WRITE_BOOL_FIELD(checkForWrite);
-	WRITE_OID_FIELD(checkAsUser);
+	WRITE_UINT_FIELD(requiredPerms);
+	WRITE_UINT_FIELD(checkAsUser);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index bbfcb1b454eca204394a56eb7717493e613d244b..93c71fd224775e28ee8b51616bfba178d65b410b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.164 2004/01/07 18:56:26 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.165 2004/01/14 23:01:55 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -939,9 +939,8 @@ _readRangeTblEntry(void)
 
 	READ_BOOL_FIELD(inh);
 	READ_BOOL_FIELD(inFromCl);
-	READ_BOOL_FIELD(checkForRead);
-	READ_BOOL_FIELD(checkForWrite);
-	READ_OID_FIELD(checkAsUser);
+	READ_UINT_FIELD(requiredPerms);
+	READ_UINT_FIELD(checkAsUser);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 2d724265f06dc698ec3a2fb0fbf9c89181aea881..50d5006a960d739cd2e4e1e0cbce6e0f292ec6c0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.111 2004/01/05 05:07:35 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.112 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -227,8 +227,7 @@ set_inherited_rel_pathlist(Query *root, RelOptInfo *rel,
 	 * it examines the parent's inheritlist entry.  There's no need to
 	 * check twice, so turn off access check bits in the original RTE.
 	 */
-	rte->checkForRead = false;
-	rte->checkForWrite = false;
+	rte->requiredPerms = 0;
 
 	/*
 	 * Initialize to compute size estimates for whole inheritance tree
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 33f32c1b377fb6e3321aff44f22d484147f5dbc1..8962082134726540583f5dd8b863df082b5b4d8f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.295 2004/01/11 04:58:17 neilc Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.296 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -472,7 +472,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	/* set up range table with just the result rel */
 	qry->resultRelation = setTargetTable(pstate, stmt->relation,
 							  interpretInhOption(stmt->relation->inhOpt),
-										 true);
+										 true,
+										 ACL_DELETE);
 
 	qry->distinctClause = NIL;
 
@@ -539,7 +540,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
 	 * table is not added to the joinlist or namespace.
 	 */
 	qry->resultRelation = setTargetTable(pstate, stmt->relation,
-										 false, false);
+										 false, false, ACL_INSERT);
 
 	/*
 	 * Is it INSERT ... SELECT or INSERT ... VALUES?
@@ -1721,8 +1722,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
 								makeAlias("*NEW*", NIL),
 								false, true);
 	/* Must override addRangeTableEntry's default access-check flags */
-	oldrte->checkForRead = false;
-	newrte->checkForRead = false;
+	oldrte->requiredPerms = 0;
+	newrte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add
@@ -1820,8 +1821,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
 			newrte = addRangeTableEntry(sub_pstate, stmt->relation,
 										makeAlias("*NEW*", NIL),
 										false, false);
-			oldrte->checkForRead = false;
-			newrte->checkForRead = false;
+			oldrte->requiredPerms = 0;
+			newrte->requiredPerms = 0;
 			addRTEtoQuery(sub_pstate, oldrte, false, true);
 			addRTEtoQuery(sub_pstate, newrte, false, true);
 
@@ -2493,7 +2494,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
 	qry->resultRelation = setTargetTable(pstate, stmt->relation,
 							  interpretInhOption(stmt->relation->inhOpt),
-										 true);
+										 true,
+										 ACL_UPDATE);
 
 	/*
 	 * the FROM clause is non-standard SQL syntax. We used to be able to
@@ -2880,7 +2882,7 @@ transformForUpdate(Query *qry, List *forUpdate)
 				case RTE_RELATION:
 					if (!intMember(i, rowMarks))	/* avoid duplicates */
 						rowMarks = lappendi(rowMarks, i);
-					rte->checkForWrite = true;
+					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 					break;
 				case RTE_SUBQUERY:
 					/*
@@ -2915,7 +2917,7 @@ transformForUpdate(Query *qry, List *forUpdate)
 						case RTE_RELATION:
 							if (!intMember(i, rowMarks)) /* avoid duplicates */
 								rowMarks = lappendi(rowMarks, i);
-							rte->checkForWrite = true;
+							rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 							break;
 						case RTE_SUBQUERY:
 							/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index d4e6747df6ff441fed81692bc03fb682da91191e..8b7be43af13414729dee092f60b738b733bb12bf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.125 2003/11/29 19:51:51 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.126 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -116,11 +116,14 @@ transformFromClause(ParseState *pstate, List *frmList)
  *	  to check for namespace conflict; we assume that the namespace was
  *	  initially empty in these cases.)
  *
+ *	  Finally, we mark the relation as requiring the permissions specified
+ *	  by requiredPerms.
+ *
  *	  Returns the rangetable index of the target relation.
  */
 int
 setTargetTable(ParseState *pstate, RangeVar *relation,
-			   bool inh, bool alsoSource)
+			   bool inh, bool alsoSource, AclMode requiredPerms)
 {
 	RangeTblEntry *rte;
 	int			rtindex;
@@ -149,16 +152,15 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
 
 	/*
-	 * Override addRangeTableEntry's default checkForRead, and instead
-	 * mark target table as requiring write access.
+	 * Override addRangeTableEntry's default ACL_SELECT permissions check,
+	 * and instead mark target table as requiring exactly the specified
+	 * permissions.
 	 *
 	 * If we find an explicit reference to the rel later during parse
-	 * analysis, scanRTEForColumn will change checkForRead to 'true'
-	 * again.  That can't happen for INSERT but it is possible for UPDATE
-	 * and DELETE.
+	 * analysis, scanRTEForColumn will add the ACL_SELECT bit back again.
+	 * That can't happen for INSERT but it is possible for UPDATE and DELETE.
 	 */
-	rte->checkForRead = false;
-	rte->checkForWrite = true;
+	rte->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 76caa60aeb24ab0f1045493d488be03ddc7dd05b..3e314bea963417675f3d3044d3c69aaac422c70d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.91 2003/11/29 19:51:52 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.92 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -437,7 +437,7 @@ RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
  * nothing.  It might seem that we need to propagate the mark to all the
  * contained RTEs, but that is not necessary.  This is so because a join
  * expression can only appear in a FROM clause, and any table named in
- * FROM will be marked checkForRead from the beginning.
+ * FROM will be marked as requiring read access from the beginning.
  */
 static Node *
 scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
@@ -477,7 +477,8 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
 						 errmsg("column reference \"%s\" is ambiguous",
 								colname)));
 			result = (Node *) make_var(pstate, rte, attnum);
-			rte->checkForRead = true;
+			/* Require read access */
+			rte->requiredPerms |= ACL_SELECT;
 		}
 	}
 
@@ -504,7 +505,8 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
 									 0, 0))
 			{
 				result = (Node *) make_var(pstate, rte, attnum);
-				rte->checkForRead = true;
+				/* Require read access */
+				rte->requiredPerms |= ACL_SELECT;
 			}
 		}
 	}
@@ -689,7 +691,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * Flags:
 	 * - this RTE should be expanded to include descendant tables,
 	 * - this RTE is in the FROM clause,
-	 * - this RTE should be checked for read/write access rights.
+	 * - this RTE should be checked for appropriate access rights.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -697,10 +699,9 @@ addRangeTableEntry(ParseState *pstate,
 	 */
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
-	rte->checkForRead = true;
-	rte->checkForWrite = false;
 
-	rte->checkAsUser = InvalidOid;		/* not set-uid by default, either */
+	rte->requiredPerms = ACL_SELECT;
+	rte->checkAsUser = 0;			/* not set-uid by default, either */
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join
@@ -784,7 +785,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * Flags:
 	 * - this RTE should be expanded to include descendant tables,
 	 * - this RTE is in the FROM clause,
-	 * - this RTE should be checked for read/write access rights.
+	 * - this RTE should be checked for appropriate access rights.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
 	 * which is the right thing for all except target tables.
@@ -792,10 +793,9 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 */
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
-	rte->checkForRead = true;
-	rte->checkForWrite = false;
 
-	rte->checkAsUser = InvalidOid;		/* not set-uid by default, either */
+	rte->requiredPerms = ACL_SELECT;
+	rte->checkAsUser = 0;			/* not set-uid by default, either */
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join
@@ -864,17 +864,16 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	 * Flags:
 	 * - this RTE should be expanded to include descendant tables,
 	 * - this RTE is in the FROM clause,
-	 * - this RTE should be checked for read/write access rights.
+	 * - this RTE should be checked for appropriate access rights.
 	 *
 	 * Subqueries are never checked for access rights.
 	 *----------
 	 */
 	rte->inh = false;			/* never true for subqueries */
 	rte->inFromCl = inFromCl;
-	rte->checkForRead = false;
-	rte->checkForWrite = false;
 
-	rte->checkAsUser = InvalidOid;
+	rte->requiredPerms = 0;
+	rte->checkAsUser = 0;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join
@@ -1034,15 +1033,17 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Flags:
 	 * - this RTE should be expanded to include descendant tables,
 	 * - this RTE is in the FROM clause,
-	 * - this RTE should be checked for read/write access rights.
+	 * - this RTE should be checked for appropriate access rights.
+	 *
+	 * Functions are never checked for access rights (at least, not by
+	 * the RTE permissions mechanism).
 	 *----------
 	 */
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
-	rte->checkForRead = true;
-	rte->checkForWrite = false;
 
-	rte->checkAsUser = InvalidOid;
+	rte->requiredPerms = 0;
+	rte->checkAsUser = 0;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join
@@ -1095,17 +1096,16 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	 * Flags:
 	 * - this RTE should be expanded to include descendant tables,
 	 * - this RTE is in the FROM clause,
-	 * - this RTE should be checked for read/write access rights.
+	 * - this RTE should be checked for appropriate access rights.
 	 *
 	 * Joins are never checked for access rights.
 	 *----------
 	 */
 	rte->inh = false;			/* never true for joins */
 	rte->inFromCl = inFromCl;
-	rte->checkForRead = false;
-	rte->checkForWrite = false;
 
-	rte->checkAsUser = InvalidOid;
+	rte->requiredPerms = 0;
+	rte->checkAsUser = 0;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 90497cf5b88f8450c2dbef15f30d99ed70a85023..f1cbe96fd2a2c206a3356f992f496b070e7f1f25 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.91 2003/11/29 19:51:55 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.92 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,7 +34,7 @@
 
 
 static void setRuleCheckAsUser(Query *qry, AclId userid);
-static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
+static bool setRuleCheckAsUser_walker(Node *node, AclId *context);
 
 
 /*
@@ -494,8 +494,8 @@ DefineQueryRewrite(RuleStmt *stmt)
  * Note: for a view (ON SELECT rule), the checkAsUser field of the *OLD*
  * RTE entry will be overridden when the view rule is expanded, and the
  * checkAsUser field of the *NEW* entry is irrelevant because that entry's
- * checkFor bits will never be set.  However, for other types of rules it's
- * important to set these fields to match the rule owner.  So we just set
+ * requiredPerms bits will always be zero.  However, for other types of rules
+ * it's important to set these fields to match the rule owner.  So we just set
  * them always.
  */
 static void
@@ -528,7 +528,7 @@ setRuleCheckAsUser(Query *qry, AclId userid)
  * Expression-tree walker to find sublink queries
  */
 static bool
-setRuleCheckAsUser_walker(Node *node, Oid *context)
+setRuleCheckAsUser_walker(Node *node, AclId *context)
 {
 	if (node == NULL)
 		return false;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 3f69110a36a8d8093c12938449769dced2461ce5..e66eb905f563a198f485b9872e80ffb95eaea900 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.132 2004/01/14 03:39:22 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.133 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -655,13 +655,11 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
 	Assert(subrte->relid == relation->rd_id);
-	subrte->checkForRead = rte->checkForRead;
-	subrte->checkForWrite = rte->checkForWrite;
+	subrte->requiredPerms = rte->requiredPerms;
 	subrte->checkAsUser = rte->checkAsUser;
 
-	rte->checkForRead = false;	/* no permission check on subquery itself */
-	rte->checkForWrite = false;
-	rte->checkAsUser = InvalidOid;
+	rte->requiredPerms = 0;		/* no permission check on subquery itself */
+	rte->checkAsUser = 0;
 
 	/*
 	 * FOR UPDATE of view?
@@ -713,7 +711,7 @@ markQueryForUpdate(Query *qry, bool skipOldNew)
 		{
 			if (!intMember(rti, qry->rowMarks))
 				qry->rowMarks = lappendi(qry->rowMarks, rti);
-			rte->checkForWrite = true;
+			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 1910e33e3b112cb316e5eaea7ff1061830e62cef..7dd262c1e464f424a41acbfb4faf420a7dc3fd2b 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.215 2004/01/06 23:55:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.216 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200401061
+#define CATALOG_VERSION_NO	200401141
 
 #endif
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8f1dc7fafaffb56001cb3ee98aa69eab1d23d61b..050894708c8bb57d104851eee87d4fa46d93beb3 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.104 2003/12/18 20:21:37 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.105 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,7 +91,7 @@ extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
 			ScanDirection direction, long count);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
-extern void ExecCheckRTPerms(List *rangeTable, CmdType operation);
+extern void ExecCheckRTPerms(List *rangeTable);
 extern void ExecEndPlan(PlanState *planstate, EState *estate);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8b6446d860518dd6ff2c004fbe5ac7c3d2e74fe5..01ff239a444548c9ffd915768f21c43d6cdb8dcd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.252 2004/01/10 23:28:45 neilc Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.253 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,6 +27,32 @@ typedef enum QuerySource
 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
 } QuerySource;
 
+/*
+ * Grantable rights are encoded so that we can OR them together in a bitmask.
+ * The present representation of AclItem limits us to 15 distinct rights,
+ * even though AclMode is defined as uint32.  See utils/acl.h.
+ *
+ * Caution: changing these codes breaks stored ACLs, hence forces initdb.
+ */
+typedef uint32 AclMode;			/* a bitmask of privilege bits */
+
+#define ACL_INSERT		(1<<0)	/* for relations */
+#define ACL_SELECT		(1<<1)
+#define ACL_UPDATE		(1<<2)
+#define ACL_DELETE		(1<<3)
+#define ACL_RULE		(1<<4)
+#define ACL_REFERENCES	(1<<5)
+#define ACL_TRIGGER		(1<<6)
+#define ACL_EXECUTE		(1<<7)	/* for functions */
+#define ACL_USAGE		(1<<8)	/* for languages and namespaces */
+#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
+#define ACL_CREATE_TEMP (1<<10) /* for databases */
+#define N_ACL_RIGHTS	11		/* 1 plus the last 1<<x */
+#define ACL_ALL_RIGHTS	(-1)	/* all-privileges marker in GRANT list */
+#define ACL_NO_RIGHTS	0
+/* Currently, SELECT ... FOR UPDATE requires UPDATE privileges */
+#define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
+
 
 /*****************************************************************************
  *	Query Tree
@@ -425,12 +451,13 @@ typedef struct DefElem
  *	  column names processed later, and it also shouldn't affect the
  *	  expansion of '*'.
  *
- *	  checkForRead, checkForWrite, and checkAsUser control run-time access
- *	  permissions checks.  A rel will be checked for read or write access
- *	  (or both, or neither) per checkForRead and checkForWrite.  If
- *	  checkAsUser is not InvalidOid, then do the permissions checks using
- *	  the access rights of that user, not the current effective user ID.
- *	  (This allows rules to act as setuid gateways.)
+ *	  requiredPerms and checkAsUser specify run-time access permissions
+ *	  checks to be performed at query startup.  The user must have *all*
+ *	  of the permissions that are OR'd together in requiredPerms (zero
+ *	  indicates no permissions checking).  If checkAsUser is not zero,
+ *	  then do the permissions checks using the access rights of that user,
+ *	  not the current effective user ID.  (This allows rules to act as
+ *	  setuid gateways.)
  *--------------------
  */
 typedef enum RTEKind
@@ -490,9 +517,8 @@ typedef struct RangeTblEntry
 	Alias	   *eref;			/* expanded reference names */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause */
-	bool		checkForRead;	/* check rel for read access */
-	bool		checkForWrite;	/* check rel for write access */
-	Oid			checkAsUser;	/* if not zero, check access as this user */
+	AclMode		requiredPerms;	/* bitmask of required access permissions */
+	AclId		checkAsUser;	/* if not zero, check access as this user */
 } RangeTblEntry;
 
 /*
@@ -809,26 +835,6 @@ typedef enum GrantObjectType
 	ACL_OBJECT_NAMESPACE		/* namespace */
 } GrantObjectType;
 
-/*
- * Grantable rights are encoded so that we can OR them together in a bitmask.
- * The present representation of AclItem limits us to 15 distinct rights.
- * Caution: changing these codes breaks stored ACLs, hence forces initdb.
- */
-#define ACL_INSERT		(1<<0)	/* for relations */
-#define ACL_SELECT		(1<<1)
-#define ACL_UPDATE		(1<<2)
-#define ACL_DELETE		(1<<3)
-#define ACL_RULE		(1<<4)
-#define ACL_REFERENCES	(1<<5)
-#define ACL_TRIGGER		(1<<6)
-#define ACL_EXECUTE		(1<<7)	/* for functions */
-#define ACL_USAGE		(1<<8)	/* for languages and namespaces */
-#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
-#define ACL_CREATE_TEMP (1<<10) /* for databases */
-#define N_ACL_RIGHTS	11		/* 1 plus the last 1<<x */
-#define ACL_ALL_RIGHTS	(-1)	/* all-privileges marker in GRANT list */
-#define ACL_NO_RIGHTS	0
-
 typedef struct GrantStmt
 {
 	NodeTag		type;
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index a57a80325b54826c4a8a3ecc2d56b9a25671888a..d91f5e80643ae5af28ec2dd433161f4c698c7c44 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.38 2003/11/29 22:41:09 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.39 2004/01/14 23:01:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,7 @@
 
 extern void transformFromClause(ParseState *pstate, List *frmList);
 extern int setTargetTable(ParseState *pstate, RangeVar *relation,
-			   bool inh, bool alsoSource);
+			   bool inh, bool alsoSource, AclMode requiredPerms);
 extern bool interpretInhOption(InhOption inhOpt);
 
 extern Node *transformWhereClause(ParseState *pstate, Node *clause,
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index be34fcce5ce12776a019964279ba4d42bc6d328e..efe7af30b202934de8078c68db648b91ff71ae90 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.65 2003/11/29 22:41:15 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.66 2004/01/14 23:01:55 tgl Exp $
  *
  * NOTES
  *	  An ACL array is simply an array of AclItems, representing the union
@@ -28,7 +28,12 @@
 #include "utils/array.h"
 
 
-/* typedef AclId is declared in c.h */
+/*
+ * typedef AclId is declared in c.h
+ *
+ * typedef AclMode is declared in parsenodes.h, also the individual privilege
+ * bit meanings are defined there
+ */
 
 #define ACL_ID_WORLD	0		/* placeholder for id in a WORLD acl item */
 
@@ -39,11 +44,6 @@
 #define ACL_IDTYPE_UID			0x01	/* user id - from pg_shadow */
 #define ACL_IDTYPE_GID			0x02	/* group id - from pg_group */
 
-/*
- * AclMode		a bitmask of privilege bits
- */
-typedef uint32 AclMode;
-
 /*
  * AclItem
  *