diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index d3dfe9edf7e53510d90dc56041a46728510d3d43..af3b5fb0f5b995e7c16d9705e686f9a71d1fc5ae 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.135 2005/04/25 01:30:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.136 2005/06/03 23:05:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -100,6 +100,12 @@ ExplainQuery(ExplainStmt *stmt, DestReceiver *dest)
 	}
 	else
 	{
+		/*
+		 * Must acquire locks in case we didn't come fresh from the parser.
+		 * XXX this also scribbles on query, another reason for copyObject
+		 */
+		AcquireRewriteLocks(query);
+
 		/* Rewrite through rule system */
 		rewritten = QueryRewrite(query);
 
@@ -166,6 +172,8 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
 			cursorOptions = dcstmt->options;
 			/* Still need to rewrite cursor command */
 			Assert(query->commandType == CMD_SELECT);
+			/* get locks (we assume ExplainQuery already copied tree) */
+			AcquireRewriteLocks(query);
 			rewritten = QueryRewrite(query);
 			if (list_length(rewritten) != 1)
 				elog(ERROR, "unexpected rewrite result");
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index affbe2b3e4998b579fc48f46a9718876b7cc107c..0ff536661360462dcf43b8955bed2b60b92197db 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.41 2005/04/28 21:47:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.42 2005/06/03 23:05:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -76,6 +76,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
 	 * query, so we are not expecting rule rewriting to do anything
 	 * strange.
 	 */
+	AcquireRewriteLocks(query);
 	rewritten = QueryRewrite(query);
 	if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
 		elog(ERROR, "unexpected rewrite result");
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 16aae6869388b75caf6d1c272b5c789834e70ca2..be3416bd3e5bbdc32ba8569ba562a9f1869add19 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
  * Copyright (c) 2002-2005, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.38 2005/05/29 04:23:03 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.39 2005/06/03 23:05:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -99,6 +99,7 @@ PrepareQuery(PrepareStmt *stmt)
 	query = copyObject(stmt->query);
 
 	/* Rewrite the query. The result could be 0, 1, or many queries. */
+	AcquireRewriteLocks(query);
 	query_list = QueryRewrite(query);
 
 	/* Generate plans for queries.	Snapshot is already set. */
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 3a6e7b216a9ec17c09f026172241870500eecc31..a95f7dcd76325889613937d3fa26d30939f729c5 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.63 2004/12/31 22:00:23 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.64 2005/06/03 23:05:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -524,9 +524,7 @@ flatten_join_alias_vars_mutator(Node *node,
 				newvar = (Node *) lfirst(l);
 				attnum++;
 				/* Ignore dropped columns */
-				if (get_rte_attribute_is_dropped(context->root->rtable,
-												 var->varno,
-												 attnum))
+				if (IsA(newvar, Const))
 					continue;
 
 				/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index bb32ebdf1c1d20642194d93c13cd87ad9af0fc3e..25205d8894e9f9157d8b9400b2f218661c3988ae 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.108 2005/05/29 17:10:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.109 2005/06/03 23:05:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1202,8 +1202,7 @@ addImplicitRTE(ParseState *pstate, RangeVar *relation)
  * results.  If include_dropped is TRUE then empty strings and NULL constants
  * (not Vars!) are returned for dropped columns.
  *
- * The target RTE is the rtindex'th entry of rtable.  (The whole rangetable
- * must be passed since we need it to determine dropped-ness for JOIN columns.)
+ * The target RTE is the rtindex'th entry of rtable.
  * sublevels_up is the varlevelsup value to use in the created Vars.
  *
  * The output lists go into *colnames and *colvars.
@@ -1358,6 +1357,8 @@ expandRTE(List *rtable, int rtindex, int sublevels_up,
 				varattno = 0;
 				forboth(colname, rte->eref->colnames, aliasvar, rte->joinaliasvars)
 				{
+					Node	   *avar = (Node *) lfirst(aliasvar);
+
 					varattno++;
 
 					/*
@@ -1365,26 +1366,19 @@ expandRTE(List *rtable, int rtindex, int sublevels_up,
 					 * deleted columns in the join; but we have to check
 					 * since this routine is also used by the rewriter,
 					 * and joins found in stored rules might have join
-					 * columns for since-deleted columns.
+					 * columns for since-deleted columns.  This will be
+					 * signaled by a NULL Const in the alias-vars list.
 					 */
-					if (get_rte_attribute_is_dropped(rtable, rtindex,
-													 varattno))
+					if (IsA(avar, Const))
 					{
 						if (include_dropped)
 						{
 							if (colnames)
 								*colnames = lappend(*colnames,
-												makeString(pstrdup("")));
+													makeString(pstrdup("")));
 							if (colvars)
-							{
-								/*
-								 * can't use atttypid here, but it doesn't
-								 * really matter what type the Const
-								 * claims to be.
-								 */
 								*colvars = lappend(*colvars,
-												 makeNullConst(INT4OID));
-							}
+												   copyObject(avar));
 						}
 						continue;
 					}
@@ -1399,7 +1393,6 @@ expandRTE(List *rtable, int rtindex, int sublevels_up,
 
 					if (colvars)
 					{
-						Node	   *avar = (Node *) lfirst(aliasvar);
 						Var		   *varnode;
 
 						varnode = makeVar(rtindex, varattno,
@@ -1711,9 +1704,8 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
  *		Check whether attempted attribute ref is to a dropped column
  */
 bool
-get_rte_attribute_is_dropped(List *rtable, int rtindex, AttrNumber attnum)
+get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 {
-	RangeTblEntry *rte = rt_fetch(rtindex, rtable);
 	bool		result;
 
 	switch (rte->rtekind)
@@ -1750,8 +1742,8 @@ get_rte_attribute_is_dropped(List *rtable, int rtindex, AttrNumber attnum)
 				 * constructed, but one in a stored rule might contain
 				 * columns that were dropped from the underlying tables,
 				 * if said columns are nowhere explicitly referenced in
-				 * the rule.  So we have to recursively look at the
-				 * referenced column.
+				 * the rule.  This will be signaled to us by a NULL Const
+				 * in the joinaliasvars list.
 				 */
 				Var		   *aliasvar;
 
@@ -1760,18 +1752,7 @@ get_rte_attribute_is_dropped(List *rtable, int rtindex, AttrNumber attnum)
 					elog(ERROR, "invalid varattno %d", attnum);
 				aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
 
-				/*
-				 * If the list item isn't a simple Var, then it must
-				 * represent a merged column, ie a USING column, and so it
-				 * couldn't possibly be dropped (since it's referenced in
-				 * the join clause).
-				 */
-				if (!IsA(aliasvar, Var))
-					result = false;
-				else
-					result = get_rte_attribute_is_dropped(rtable,
-														  aliasvar->varno,
-													 aliasvar->varattno);
+				result = IsA(aliasvar, Const);
 			}
 			break;
 		case RTE_FUNCTION:
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 73c8dbab12740b7834141c48410ff3ab38a9296d..3c267ccdcd25b039b2b6242e892acecce0335d53 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.152 2005/05/29 18:34:57 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.153 2005/06/03 23:05:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,6 +40,7 @@ typedef struct rewrite_event
 	CmdType		event;			/* type of rule being fired */
 } rewrite_event;
 
+static bool acquireLocksOnSubLinks(Node *node, void *context);
 static Query *rewriteRuleAction(Query *parsetree,
 				  Query *rule_action,
 				  Node *rule_qual,
@@ -57,6 +58,181 @@ static List *matchLocks(CmdType event, RuleLock *rulelocks,
 static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
 
 
+/*
+ * AcquireRewriteLocks -
+ *	  Acquire suitable locks on all the relations mentioned in the Query.
+ *	  These locks will ensure that the relation schemas don't change under us
+ *	  while we are rewriting and planning the query.
+ *
+ * A secondary purpose of this routine is to fix up JOIN RTE references to
+ * dropped columns (see details below).  Because the RTEs are modified in
+ * place, it is generally appropriate for the caller of this routine to have
+ * first done a copyObject() to make a writable copy of the querytree in the
+ * current memory context.
+ *
+ * This processing can, and for efficiency's sake should, be skipped when the
+ * querytree has just been built by the parser: parse analysis already got
+ * all the same locks we'd get here, and the parser will have omitted dropped
+ * columns from JOINs to begin with.  But we must do this whenever we are
+ * dealing with a querytree produced earlier than the current command.
+ *
+ * About JOINs and dropped columns: although the parser never includes an
+ * already-dropped column in a JOIN RTE's alias var list, it is possible for
+ * such a list in a stored rule to include references to dropped columns.
+ * (If the column is not explicitly referenced anywhere else in the query,
+ * the dependency mechanism won't consider it used by the rule and so won't
+ * prevent the column drop.)  To support get_rte_attribute_is_dropped(),
+ * we replace join alias vars that reference dropped columns with NULL Const
+ * nodes.
+ *
+ * (In PostgreSQL 8.0, we did not do this processing but instead had
+ * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins.
+ * That approach had horrible performance unfortunately; in particular
+ * construction of a nested join was O(N^2) in the nesting depth.)
+ */
+void
+AcquireRewriteLocks(Query *parsetree)
+{
+	ListCell   *l;
+	int			rt_index;
+
+	/*
+	 * First, process RTEs of the current query level.
+	 */
+	rt_index = 0;
+	foreach(l, parsetree->rtable)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		Relation	rel;
+		LOCKMODE	lockmode;
+		List	   *newaliasvars;
+		ListCell   *ll;
+
+		++rt_index;
+		switch (rte->rtekind)
+		{
+			case RTE_RELATION:
+				/*
+				 * Grab the appropriate lock type for the relation, and
+				 * do not release it until end of transaction. This protects
+				 * the rewriter and planner against schema changes mid-query.
+				 *
+				 * If the relation is the query's result relation, then we
+				 * need RowExclusiveLock.  Otherwise, check to see if the
+				 * relation is accessed FOR UPDATE/SHARE or not.  We can't
+				 * just grab AccessShareLock because then the executor
+				 * would be trying to upgrade the lock, leading to possible
+				 * deadlocks.
+				 */
+				if (rt_index == parsetree->resultRelation)
+					lockmode = RowExclusiveLock;
+				else if (list_member_int(parsetree->rowMarks, rt_index))
+					lockmode = RowShareLock;
+				else
+					lockmode = AccessShareLock;
+
+				rel = heap_open(rte->relid, lockmode);
+				heap_close(rel, NoLock);
+				break;
+
+			case RTE_JOIN:
+				/*
+				 * Scan the join's alias var list to see if any columns
+				 * have been dropped, and if so replace those Vars with
+				 * NULL Consts.
+				 */
+				newaliasvars = NIL;
+				foreach(ll, rte->joinaliasvars)
+				{
+					Var		   *aliasvar = (Var *) lfirst(ll);
+
+					/*
+					 * If the list item isn't a simple Var, then it must
+					 * represent a merged column, ie a USING column, and so it
+					 * couldn't possibly be dropped, since it's referenced in
+					 * the join clause.  (Conceivably it could also be a
+					 * NULL constant already?  But that's OK too.)
+					 */
+					if (IsA(aliasvar, Var))
+					{
+						/*
+						 * The elements of an alias list have to refer to
+						 * earlier RTEs of the same rtable, because that's
+						 * the order the planner builds things in.  So we
+						 * already processed the referenced RTE, and so it's
+						 * safe to use get_rte_attribute_is_dropped on it.
+						 * (This might not hold after rewriting or planning,
+						 * but it's OK to assume here.)
+						 */
+						Assert(aliasvar->varlevelsup == 0);
+						if (aliasvar->varno >= rt_index)
+							elog(ERROR, "unexpected varno %d in JOIN RTE %d",
+								 aliasvar->varno, rt_index);
+						if (get_rte_attribute_is_dropped(
+							rt_fetch(aliasvar->varno, parsetree->rtable),
+							aliasvar->varattno))
+						{
+							/*
+							 * can't use vartype here, since that might be a
+							 * now-dropped type OID, but it doesn't really
+							 * matter what type the Const claims to be.
+							 */
+							aliasvar = (Var *) makeNullConst(INT4OID);
+						}
+					}
+					newaliasvars = lappend(newaliasvars, aliasvar);
+				}
+				rte->joinaliasvars = newaliasvars;
+				break;
+
+			case RTE_SUBQUERY:
+				/*
+				 * The subquery RTE itself is all right, but we have to
+				 * recurse to process the represented subquery.
+				 */
+				AcquireRewriteLocks(rte->subquery);
+				break;
+
+			default:
+				/* ignore other types of RTEs */
+				break;
+		}
+	}
+
+	/*
+	 * Recurse into sublink subqueries, too.  But we already did the ones
+	 * in the rtable.
+	 */
+	if (parsetree->hasSubLinks)
+		query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
+						  QTW_IGNORE_RT_SUBQUERIES);
+}
+
+/*
+ * Walker to find sublink subqueries for AcquireRewriteLocks
+ */
+static bool
+acquireLocksOnSubLinks(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, SubLink))
+	{
+		SubLink    *sub = (SubLink *) node;
+
+		/* Do what we came for */
+		AcquireRewriteLocks((Query *) sub->subselect);
+		/* Fall through to process lefthand args of SubLink */
+	}
+
+	/*
+	 * Do NOT recurse into Query nodes, because AcquireRewriteLocks already
+	 * processed subselects of subselects for us.
+	 */
+	return expression_tree_walker(node, acquireLocksOnSubLinks, context);
+}
+
+
 /*
  * rewriteRuleAction -
  *	  Rewrite the rule action with appropriate qualifiers (taken from
@@ -82,6 +258,12 @@ rewriteRuleAction(Query *parsetree,
 	rule_action = (Query *) copyObject(rule_action);
 	rule_qual = (Node *) copyObject(rule_qual);
 
+	/*
+	 * Acquire necessary locks and fix any deleted JOIN RTE entries.
+	 */
+	AcquireRewriteLocks(rule_action);
+	(void) acquireLocksOnSubLinks(rule_qual, NULL);
+
 	current_varno = rt_index;
 	rt_length = list_length(parsetree->rtable);
 	new_varno = PRS2_NEW_VARNO + rt_length;
@@ -693,6 +875,9 @@ matchLocks(CmdType event,
 }
 
 
+/*
+ * ApplyRetrieveRule - expand an ON SELECT rule
+ */
 static Query *
 ApplyRetrieveRule(Query *parsetree,
 				  RewriteRule *rule,
@@ -713,11 +898,16 @@ ApplyRetrieveRule(Query *parsetree,
 		elog(ERROR, "cannot handle per-attribute ON SELECT rule");
 
 	/*
-	 * Make a modifiable copy of the view query, and recursively expand
-	 * any view references inside it.
+	 * Make a modifiable copy of the view query, and acquire needed locks
+	 * on the relations it mentions.
 	 */
 	rule_action = copyObject(linitial(rule->actions));
 
+	AcquireRewriteLocks(rule_action);
+
+	/*
+	 * Recursively expand any view references inside the view.
+	 */
 	rule_action = fireRIRrules(rule_action, activeRIRs);
 
 	/*
@@ -868,7 +1058,6 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 		List	   *locks;
 		RuleLock   *rules;
 		RewriteRule *rule;
-		LOCKMODE	lockmode;
 		int			i;
 
 		++rt_index;
@@ -904,26 +1093,10 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 			continue;
 
 		/*
-		 * This may well be the first access to the relation during the
-		 * current statement (it will be, if this Query was extracted from
-		 * a rule or somehow got here other than via the parser).
-		 * Therefore, grab the appropriate lock type for the relation, and
-		 * do not release it until end of transaction.	This protects the
-		 * rewriter and planner against schema changes mid-query.
-		 *
-		 * If the relation is the query's result relation, then
-		 * RewriteQuery() already got the right lock on it, so we need no
-		 * additional lock. Otherwise, check to see if the relation is
-		 * accessed FOR UPDATE/SHARE or not.
+		 * We can use NoLock here since either the parser or
+		 * AcquireRewriteLocks should have locked the rel already.
 		 */
-		if (rt_index == parsetree->resultRelation)
-			lockmode = NoLock;
-		else if (list_member_int(parsetree->rowMarks, rt_index))
-			lockmode = RowShareLock;
-		else
-			lockmode = AccessShareLock;
-
-		rel = heap_open(rte->relid, lockmode);
+		rel = heap_open(rte->relid, NoLock);
 
 		/*
 		 * Collect the RIR rules that we must apply
@@ -1015,9 +1188,17 @@ CopyAndAddInvertedQual(Query *parsetree,
 					   int rt_index,
 					   CmdType event)
 {
-	Query	   *new_tree = (Query *) copyObject(parsetree);
+	/* Don't scribble on the passed qual (it's in the relcache!) */
 	Node	   *new_qual = (Node *) copyObject(rule_qual);
 
+	/*
+	 * In case there are subqueries in the qual, acquire necessary locks and
+	 * fix any deleted JOIN RTE entries.  (This is somewhat redundant with
+	 * rewriteRuleAction, but not entirely ... consider restructuring so
+	 * that we only need to process the qual this way once.)
+	 */
+	(void) acquireLocksOnSubLinks(new_qual, NULL);
+
 	/* Fix references to OLD */
 	ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
 	/* Fix references to NEW */
@@ -1030,9 +1211,9 @@ CopyAndAddInvertedQual(Query *parsetree,
 							  event,
 							  rt_index);
 	/* And attach the fixed qual */
-	AddInvertedQual(new_tree, new_qual);
+	AddInvertedQual(parsetree, new_qual);
 
-	return new_tree;
+	return parsetree;
 }
 
 
@@ -1112,7 +1293,7 @@ fireRules(Query *parsetree,
 			if (!*instead_flag)
 			{
 				if (*qual_product == NULL)
-					*qual_product = parsetree;
+					*qual_product = copyObject(parsetree);
 				*qual_product = CopyAndAddInvertedQual(*qual_product,
 													   event_qual,
 													   rt_index,
@@ -1177,15 +1358,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		Assert(rt_entry->rtekind == RTE_RELATION);
 
 		/*
-		 * This may well be the first access to the result relation during
-		 * the current statement (it will be, if this Query was extracted
-		 * from a rule or somehow got here other than via the parser).
-		 * Therefore, grab the appropriate lock type for a result
-		 * relation, and do not release it until end of transaction.  This
-		 * protects the rewriter and planner against schema changes
-		 * mid-query.
+		 * We can use NoLock here since either the parser or
+		 * AcquireRewriteLocks should have locked the rel already.
 		 */
-		rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
+		rt_entry_relation = heap_open(rt_entry->relid, NoLock);
 
 		/*
 		 * If it's an INSERT or UPDATE, rewrite the targetlist into
@@ -1251,7 +1427,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 			}
 		}
 
-		heap_close(rt_entry_relation, NoLock);	/* keep lock! */
+		heap_close(rt_entry_relation, NoLock);
 	}
 
 	/*
@@ -1295,8 +1471,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
  *	  Rewrite one query via query rewrite system, possibly returning 0
  *	  or many queries.
  *
- * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was
- * moved here so that it would be invoked during EXPLAIN.
+ * NOTE: the parsetree must either have come straight from the parser,
+ * or have been scanned by AcquireRewriteLocks to acquire suitable locks.
  */
 List *
 QueryRewrite(Query *parsetree)
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 297eb75d8909812306771919b39254ca0d0eecc8..58b1ffbce9aa4890777df5beb724b16a0f54f38d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.446 2005/06/02 21:03:24 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.447 2005/06/03 23:05:29 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -158,6 +158,7 @@ static int	SocketBackend(StringInfo inBuf);
 static int	ReadCommand(StringInfo inBuf);
 static bool log_after_parse(List *raw_parsetree_list,
 				const char *query_string, char **prepare_string);
+static List *pg_rewrite_queries(List *querytree_list);
 static void start_xact_command(void);
 static void finish_xact_command(void);
 static void SigHupHandler(SIGNAL_ARGS);
@@ -642,8 +643,11 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
 
 /*
  * Perform rewriting of a list of queries produced by parse analysis.
+ *
+ * Note: queries must just have come from the parser, because we do not do
+ * AcquireRewriteLocks() on them.
  */
-List *
+static List *
 pg_rewrite_queries(List *querytree_list)
 {
 	List	   *new_list = NIL;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index be53f7373d9ce5274941e9a3cd6c53fe4d52a592..c6de4df714a2600f0fcbc78f554230bb4e5bdc39 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.198 2005/05/31 03:03:59 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.199 2005/06/03 23:05:29 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -67,6 +67,7 @@
 #include "parser/parse_oper.h"
 #include "parser/parse_type.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
 #include "rewrite/rewriteSupport.h"
 #include "utils/array.h"
@@ -1661,6 +1662,9 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 		 */
 		query = getInsertSelectQuery(query, NULL);
 
+		/* Must acquire locks right away; see notes in get_query_def() */
+		AcquireRewriteLocks(query);
+
 		context.buf = buf;
 		context.namespaces = list_make1(&dpns);
 		context.varprefix = (list_length(query->rtable) != 1);
@@ -1795,6 +1799,14 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 	deparse_context context;
 	deparse_namespace dpns;
 
+	/*
+	 * Before we begin to examine the query, acquire locks on referenced
+	 * relations, and fix up deleted columns in JOIN RTEs.  This ensures
+	 * consistent results.  Note we assume it's OK to scribble on the
+	 * passed querytree!
+	 */
+	AcquireRewriteLocks(query);
+
 	context.buf = buf;
 	context.namespaces = lcons(&dpns, list_copy(parentnamespace));
 	context.varprefix = (parentnamespace != NIL ||
@@ -4245,6 +4257,7 @@ get_from_clause_alias(Alias *alias, int varno,
 					  Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
+	RangeTblEntry *rte = rt_fetch(varno, query->rtable);
 	ListCell   *col;
 	AttrNumber	attnum;
 	bool		first = true;
@@ -4256,7 +4269,7 @@ get_from_clause_alias(Alias *alias, int varno,
 	foreach(col, alias->colnames)
 	{
 		attnum++;
-		if (get_rte_attribute_is_dropped(query->rtable, varno, attnum))
+		if (get_rte_attribute_is_dropped(rte, attnum))
 			continue;
 		if (first)
 		{
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 97d5df935ac13cd4052a6d581e6faf870c63aadd..1208def12ce63674683d0bce0c19a62e92726e6b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.278 2005/04/28 21:47:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.279 2005/06/03 23:05:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -465,7 +465,10 @@ typedef struct DefElem
  *	  those columns are known to be dropped at parse time.	Again, however,
  *	  a stored rule might contain entries for columns dropped since the rule
  *	  was created.	(This is only possible for columns not actually referenced
- *	  in the rule.)
+ *	  in the rule.)  When loading a stored rule, we replace the joinaliasvars
+ *	  items for any such columns with NULL Consts.  (We can't simply delete
+ *	  them from the joinaliasvars list, because that would affect the attnums
+ *	  of Vars referencing the rest of the list.)
  *
  *	  inh is TRUE for relation references that should be expanded to include
  *	  inheritance children, if the rel has any.  This *must* be FALSE for
@@ -535,7 +538,10 @@ typedef struct RangeTblEntry
 	 * to the columns of the join result.  An alias Var referencing column
 	 * K of the join result can be replaced by the K'th element of
 	 * joinaliasvars --- but to simplify the task of reverse-listing
-	 * aliases correctly, we do not do that until planning time.
+	 * aliases correctly, we do not do that until planning time.  In a Query
+	 * loaded from a stored rule, it is also possible for joinaliasvars
+	 * items to be NULL Consts, denoting columns dropped since the rule was
+	 * made.
 	 */
 	JoinType	jointype;		/* type of join */
 	List	   *joinaliasvars;	/* list of alias-var expansions */
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index c6911e9a2f8a1d505c908a4d9d538e1c8ebf4e79..c1997bb787ca4c5b9891156c11f2ffbf9b974b40 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parsetree.h,v 1.29 2004/12/31 22:03:38 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parsetree.h,v 1.30 2005/06/03 23:05:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,8 +59,8 @@ extern void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
  * Check whether an attribute of an RTE has been dropped (note that
  * get_rte_attribute_type will fail on such an attr)
  */
-extern bool get_rte_attribute_is_dropped(List *rtable, int rtindex,
-							 AttrNumber attnum);
+extern bool get_rte_attribute_is_dropped(RangeTblEntry *rte,
+										 AttrNumber attnum);
 
 
 /* ----------------
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 566ab62c759525928fbb464bd5c911ed0aaef082..478dbdd0bab0366380659f7337a11fa6547073c3 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/rewrite/rewriteHandler.h,v 1.24 2004/12/31 22:03:41 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/rewriteHandler.h,v 1.25 2005/06/03 23:05:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,6 +17,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void AcquireRewriteLocks(Query *parsetree);
 extern Node *build_column_default(Relation rel, int attrno);
 
 #endif   /* REWRITEHANDLER_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 23b338e7ae1ba7469e904387b7752b87e14fbaf2..d694146d59ae1e69105df913d0dd602f31adc89a 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.74 2005/06/02 21:03:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.75 2005/06/03 23:05:30 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -48,7 +48,6 @@ extern List *pg_parse_and_rewrite(const char *query_string,
 extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree,
 					   Oid *paramTypes, int numParams);
-extern List *pg_rewrite_queries(List *querytree_list);
 extern Plan *pg_plan_query(Query *querytree, ParamListInfo boundParams);
 extern List *pg_plan_queries(List *querytrees, ParamListInfo boundParams,
 				bool needSnapshot);