CmdType     event;          /* type of rule being fired */
 } rewrite_event;
 
-static bool acquireLocksOnSubLinks(Node *node, void *context);
+typedef struct acquireLocksOnSubLinks_context
+{
+   bool        for_execute;    /* AcquireRewriteLocks' forExecute param */
+} acquireLocksOnSubLinks_context;
+
+static bool acquireLocksOnSubLinks(Node *node,
+                      acquireLocksOnSubLinks_context *context);
 static Query *rewriteRuleAction(Query *parsetree,
                  Query *rule_action,
                  Node *rule_qual,
  *   These locks will ensure that the relation schemas don't change under us
  *   while we are rewriting and planning the query.
  *
- * forUpdatePushedDown indicates that a pushed-down FOR [KEY] UPDATE/SHARE applies
- * to the current subquery, requiring all rels to be opened with RowShareLock.
- * This should always be false at the start of the recursion.
+ * forExecute indicates that the query is about to be executed.
+ * If so, we'll acquire RowExclusiveLock on the query's resultRelation,
+ * RowShareLock on any relation accessed FOR [KEY] UPDATE/SHARE, and
+ * AccessShareLock on all other relations mentioned.
+ *
+ * If forExecute is false, AccessShareLock is acquired on all relations.
+ * This case is suitable for ruleutils.c, for example, where we only need
+ * schema stability and we don't intend to actually modify any relations.
+ *
+ * forUpdatePushedDown indicates that a pushed-down FOR [KEY] UPDATE/SHARE
+ * applies to the current subquery, requiring all rels to be opened with at
+ * least RowShareLock.  This should always be false at the top of the
+ * recursion.  This flag is ignored if forExecute is false.
  *
  * 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
  * construction of a nested join was O(N^2) in the nesting depth.)
  */
 void
-AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
+AcquireRewriteLocks(Query *parsetree,
+                   bool forExecute,
+                   bool forUpdatePushedDown)
 {
    ListCell   *l;
    int         rt_index;
+   acquireLocksOnSubLinks_context context;
+
+   context.for_execute = forExecute;
 
    /*
     * First, process RTEs of the current query level.
                 * 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 [KEY] 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.
+                * Assuming forExecute is true, this logic must match what the
+                * executor will do, else we risk lock-upgrade deadlocks.
                 */
-               if (rt_index == parsetree->resultRelation)
+               if (!forExecute)
+                   lockmode = AccessShareLock;
+               else if (rt_index == parsetree->resultRelation)
                    lockmode = RowExclusiveLock;
                else if (forUpdatePushedDown ||
                         get_parse_rowmark(parsetree, rt_index) != NULL)
                 * recurse to process the represented subquery.
                 */
                AcquireRewriteLocks(rte->subquery,
+                                   forExecute,
                                    (forUpdatePushedDown ||
                            get_parse_rowmark(parsetree, rt_index) != NULL));
                break;
    {
        CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
 
-       AcquireRewriteLocks((Query *) cte->ctequery, false);
+       AcquireRewriteLocks((Query *) cte->ctequery, forExecute, false);
    }
 
    /*
     * the rtable and cteList.
     */
    if (parsetree->hasSubLinks)
-       query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
+       query_tree_walker(parsetree, acquireLocksOnSubLinks, &context,
                          QTW_IGNORE_RC_SUBQUERIES);
 }
 
  * Walker to find sublink subqueries for AcquireRewriteLocks
  */
 static bool
-acquireLocksOnSubLinks(Node *node, void *context)
+acquireLocksOnSubLinks(Node *node, acquireLocksOnSubLinks_context *context)
 {
    if (node == NULL)
        return false;
        SubLink    *sub = (SubLink *) node;
 
        /* Do what we came for */
-       AcquireRewriteLocks((Query *) sub->subselect, false);
+       AcquireRewriteLocks((Query *) sub->subselect,
+                           context->for_execute,
+                           false);
        /* Fall through to process lefthand args of SubLink */
    }
 
    int         rt_length;
    Query      *sub_action;
    Query     **sub_action_ptr;
+   acquireLocksOnSubLinks_context context;
+
+   context.for_execute = true;
 
    /*
     * Make modifiable copies of rule action and qual (what we're passed are
    /*
     * Acquire necessary locks and fix any deleted JOIN RTE entries.
     */
-   AcquireRewriteLocks(rule_action, false);
-   (void) acquireLocksOnSubLinks(rule_qual, NULL);
+   AcquireRewriteLocks(rule_action, true, false);
+   (void) acquireLocksOnSubLinks(rule_qual, &context);
 
    current_varno = rt_index;
    rt_length = list_length(parsetree->rtable);
     */
    rule_action = copyObject(linitial(rule->actions));
 
-   AcquireRewriteLocks(rule_action, forUpdatePushedDown);
+   AcquireRewriteLocks(rule_action, true, forUpdatePushedDown);
 
    /*
     * Recursively expand any view references inside the view.
 {
    /* Don't scribble on the passed qual (it's in the relcache!) */
    Node       *new_qual = (Node *) copyObject(rule_qual);
+   acquireLocksOnSubLinks_context context;
+
+   context.for_execute = true;
 
    /*
     * In case there are subqueries in the qual, acquire necessary locks and
     * rewriteRuleAction, but not entirely ... consider restructuring so that
     * we only need to process the qual this way once.)
     */
-   (void) acquireLocksOnSubLinks(new_qual, NULL);
+   (void) acquireLocksOnSubLinks(new_qual, &context);
 
    /* Fix references to OLD */
    ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);