]> granicus.if.org Git - postgresql/blobdiff - src/backend/rewrite/rewriteHandler.c
Implement an API to let foreign-data wrappers actually be functional.
[postgresql] / src / backend / rewrite / rewriteHandler.c
index cc1c454c96f5084dc7574774e02ca3e6d838df02..3a50642fce8008d6b99b816515022cd10773d232 100644 (file)
@@ -3,34 +3,30 @@
  * rewriteHandler.c
  *             Primary module of query rewriter.
  *
- * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.137 2004/05/29 05:55:13 tgl Exp $
+ *       src/backend/rewrite/rewriteHandler.c
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/heapam.h"
-#include "catalog/pg_operator.h"
+#include "access/sysattr.h"
 #include "catalog/pg_type.h"
-#include "miscadmin.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
-#include "optimizer/prep.h"
-#include "optimizer/var.h"
+#include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_coerce.h"
-#include "parser/parse_expr.h"
-#include "parser/parse_oper.h"
-#include "parser/parse_type.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "commands/trigger.h"
 
 
 /* We use a list of these to detect recursion in RewriteQuery */
@@ -40,33 +36,262 @@ 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,
                                  int rt_index,
-                                 CmdType event);
+                                 CmdType event,
+                                 bool *returning_flag);
 static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
-static void rewriteTargetList(Query *parsetree, Relation target_relation);
+static void rewriteTargetListIU(Query *parsetree, Relation target_relation,
+                                       List **attrno_list);
 static TargetEntry *process_matched_tle(TargetEntry *src_tle,
-                                                                               TargetEntry *prior_tle,
-                                                                               const char *attrName);
-static void markQueryForUpdate(Query *qry, bool skipOldNew);
+                                       TargetEntry *prior_tle,
+                                       const char *attrName);
+static Node *get_assignment_input(Node *node);
+static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
+                                List *attrnos);
+static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
+                                                               Relation target_relation);
+static void markQueryForLocking(Query *qry, Node *jtnode,
+                                       bool forUpdate, bool noWait, bool pushedDown);
 static List *matchLocks(CmdType event, RuleLock *rulelocks,
                   int varno, Query *parsetree);
-static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
+static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
+                        bool forUpdatePushedDown);
+
+
+/*
+ * 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.
+ *
+ * forUpdatePushedDown indicates that a pushed-down FOR 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.
+ *
+ * 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, bool forUpdatePushedDown)
+{
+       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;
+               Index           curinputvarno;
+               RangeTblEntry *curinputrte;
+               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 (forUpdatePushedDown ||
+                                                get_parse_rowmark(parsetree, rt_index) != NULL)
+                                       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.
+                                *
+                                * Since a join has only two inputs, we can expect to see
+                                * multiple references to the same input RTE; optimize away
+                                * multiple fetches.
+                                */
+                               newaliasvars = NIL;
+                               curinputvarno = 0;
+                               curinputrte = NULL;
+                               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 != curinputvarno)
+                                               {
+                                                       curinputvarno = aliasvar->varno;
+                                                       if (curinputvarno >= rt_index)
+                                                               elog(ERROR, "unexpected varno %d in JOIN RTE %d",
+                                                                        curinputvarno, rt_index);
+                                                       curinputrte = rt_fetch(curinputvarno,
+                                                                                                  parsetree->rtable);
+                                               }
+                                               if (get_rte_attribute_is_dropped(curinputrte,
+                                                                                                                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, -1);
+                                               }
+                                       }
+                                       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,
+                                                                       (forUpdatePushedDown ||
+                                                       get_parse_rowmark(parsetree, rt_index) != NULL));
+                               break;
+
+                       default:
+                               /* ignore other types of RTEs */
+                               break;
+               }
+       }
+
+       /* Recurse into subqueries in WITH */
+       foreach(l, parsetree->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+               AcquireRewriteLocks((Query *) cte->ctequery, false);
+       }
+
+       /*
+        * Recurse into sublink subqueries, too.  But we already did the ones in
+        * the rtable and cteList.
+        */
+       if (parsetree->hasSubLinks)
+               query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
+                                                 QTW_IGNORE_RC_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, false);
+               /* 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
  *       the triggering query).
+ *
+ * Input arguments:
+ *     parsetree - original query
+ *     rule_action - one action (query) of a rule
+ *     rule_qual - WHERE condition of rule, or NULL if unconditional
+ *     rt_index - RT index of result relation in original query
+ *     event - type of rule event
+ * Output arguments:
+ *     *returning_flag - set TRUE if we rewrite RETURNING clause in rule_action
+ *                                     (must be initialized to FALSE)
+ * Return value:
+ *     rewritten form of rule_action
  */
 static Query *
 rewriteRuleAction(Query *parsetree,
                                  Query *rule_action,
                                  Node *rule_qual,
                                  int rt_index,
-                                 CmdType event)
+                                 CmdType event,
+                                 bool *returning_flag)
 {
        int                     current_varno,
                                new_varno;
@@ -75,29 +300,35 @@ rewriteRuleAction(Query *parsetree,
        Query     **sub_action_ptr;
 
        /*
-        * Make modifiable copies of rule action and qual (what we're passed
-        * are the stored versions in the relcache; don't touch 'em!).
+        * Make modifiable copies of rule action and qual (what we're passed are
+        * the stored versions in the relcache; don't touch 'em!).
         */
        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, false);
+       (void) acquireLocksOnSubLinks(rule_qual, NULL);
+
        current_varno = rt_index;
-       rt_length = length(parsetree->rtable);
+       rt_length = list_length(parsetree->rtable);
        new_varno = PRS2_NEW_VARNO + rt_length;
 
        /*
-        * Adjust rule action and qual to offset its varnos, so that we can
-        * merge its rtable with the main parsetree's rtable.
+        * Adjust rule action and qual to offset its varnos, so that we can merge
+        * its rtable with the main parsetree's rtable.
         *
         * If the rule action is an INSERT...SELECT, the OLD/NEW rtable entries
-        * will be in the SELECT part, and we have to modify that rather than
-        * the top-level INSERT (kluge!).
+        * will be in the SELECT part, and we have to modify that rather than the
+        * top-level INSERT (kluge!).
         */
        sub_action = getInsertSelectQuery(rule_action, &sub_action_ptr);
 
        OffsetVarNodes((Node *) sub_action, rt_length, 0);
        OffsetVarNodes(rule_qual, rt_length, 0);
-       /* but references to *OLD* should point at original rt_index */
+       /* but references to OLD should point at original rt_index */
        ChangeVarNodes((Node *) sub_action,
                                   PRS2_OLD_VARNO + rt_length, rt_index, 0);
        ChangeVarNodes(rule_qual,
@@ -106,45 +337,75 @@ rewriteRuleAction(Query *parsetree,
        /*
         * Generate expanded rtable consisting of main parsetree's rtable plus
         * rule action's rtable; this becomes the complete rtable for the rule
-        * action.      Some of the entries may be unused after we finish
-        * rewriting, but we leave them all in place for two reasons:
+        * action.      Some of the entries may be unused after we finish rewriting,
+        * but we leave them all in place for two reasons:
         *
-        *              * We'd have a much harder job to adjust the query's varnos
-        *                if we selectively removed RT entries.
+        * We'd have a much harder job to adjust the query's varnos if we
+        * selectively removed RT entries.
         *
-        *              * If the rule is INSTEAD, then the original query won't be
-        *                executed at all, and so its rtable must be preserved so that
-        *                the executor will do the correct permissions checks on it.
+        * If the rule is INSTEAD, then the original query won't be executed at
+        * all, and so its rtable must be preserved so that the executor will do
+        * the correct permissions checks on it.
         *
         * RT entries that are not referenced in the completed jointree will be
-        * ignored by the planner, so they do not affect query semantics.  But
-        * any permissions checks specified in them will be applied during
-        * executor startup (see ExecCheckRTEPerms()).  This allows us to check
-        * that the caller has, say, insert-permission on a view, when the view
-        * is not semantically referenced at all in the resulting query.
+        * ignored by the planner, so they do not affect query semantics.  But any
+        * permissions checks specified in them will be applied during executor
+        * startup (see ExecCheckRTEPerms()).  This allows us to check that the
+        * caller has, say, insert-permission on a view, when the view is not
+        * semantically referenced at all in the resulting query.
         *
         * When a rule is not INSTEAD, the permissions checks done on its copied
         * RT entries will be redundant with those done during execution of the
         * original query, but we don't bother to treat that case differently.
         *
         * NOTE: because planner will destructively alter rtable, we must ensure
-        * that rule action's rtable is separate and shares no substructure
-        * with the main rtable.  Hence do a deep copy here.
+        * that rule action's rtable is separate and shares no substructure with
+        * the main rtable.  Hence do a deep copy here.
+        */
+       sub_action->rtable = list_concat((List *) copyObject(parsetree->rtable),
+                                                                        sub_action->rtable);
+
+       /*
+        * There could have been some SubLinks in parsetree's rtable, in which
+        * case we'd better mark the sub_action correctly.
         */
-       sub_action->rtable = nconc((List *) copyObject(parsetree->rtable),
-                                                          sub_action->rtable);
+       if (parsetree->hasSubLinks && !sub_action->hasSubLinks)
+       {
+               ListCell   *lc;
+
+               foreach(lc, parsetree->rtable)
+               {
+                       RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+                       switch (rte->rtekind)
+                       {
+                               case RTE_FUNCTION:
+                                       sub_action->hasSubLinks =
+                                               checkExprHasSubLink(rte->funcexpr);
+                                       break;
+                               case RTE_VALUES:
+                                       sub_action->hasSubLinks =
+                                               checkExprHasSubLink((Node *) rte->values_lists);
+                                       break;
+                               default:
+                                       /* other RTE types don't contain bare expressions */
+                                       break;
+                       }
+                       if (sub_action->hasSubLinks)
+                               break;                  /* no need to keep scanning rtable */
+               }
+       }
 
        /*
         * Each rule action's jointree should be the main parsetree's jointree
-        * plus that rule's jointree, but usually *without* the original
-        * rtindex that we're replacing (if present, which it won't be for
-        * INSERT). Note that if the rule action refers to OLD, its jointree
-        * will add a reference to rt_index.  If the rule action doesn't refer
-        * to OLD, but either the rule_qual or the user query quals do, then
-        * we need to keep the original rtindex in the jointree to provide
-        * data for the quals.  We don't want the original rtindex to be
-        * joined twice, however, so avoid keeping it if the rule action
-        * mentions it.
+        * plus that rule's jointree, but usually *without* the original rtindex
+        * that we're replacing (if present, which it won't be for INSERT). Note
+        * that if the rule action refers to OLD, its jointree will add a
+        * reference to rt_index.  If the rule action doesn't refer to OLD, but
+        * either the rule_qual or the user query quals do, then we need to keep
+        * the original rtindex in the jointree to provide data for the quals.  We
+        * don't want the original rtindex to be joined twice, however, so avoid
+        * keeping it if the rule action mentions it.
         *
         * As above, the action's jointree must not share substructure with the
         * main parsetree's.
@@ -158,15 +419,15 @@ rewriteRuleAction(Query *parsetree,
                keeporig = (!rangeTableEntry_used((Node *) sub_action->jointree,
                                                                                  rt_index, 0)) &&
                        (rangeTableEntry_used(rule_qual, rt_index, 0) ||
-                 rangeTableEntry_used(parsetree->jointree->quals, rt_index, 0));
+                        rangeTableEntry_used(parsetree->jointree->quals, rt_index, 0));
                newjointree = adjustJoinTreeList(parsetree, !keeporig, rt_index);
                if (newjointree != NIL)
                {
                        /*
-                        * If sub_action is a setop, manipulating its jointree will do
-                        * no good at all, because the jointree is dummy.  (Perhaps
-                        * someday we could push the joining and quals down to the
-                        * member statements of the setop?)
+                        * If sub_action is a setop, manipulating its jointree will do no
+                        * good at all, because the jointree is dummy.  (Perhaps someday
+                        * we could push the joining and quals down to the member
+                        * statements of the setop?)
                         */
                        if (sub_action->setOperations != NULL)
                                ereport(ERROR,
@@ -174,26 +435,22 @@ rewriteRuleAction(Query *parsetree,
                                                 errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
 
                        sub_action->jointree->fromlist =
-                               nconc(newjointree, sub_action->jointree->fromlist);
+                               list_concat(newjointree, sub_action->jointree->fromlist);
+
+                       /*
+                        * There could have been some SubLinks in newjointree, in which
+                        * case we'd better mark the sub_action correctly.
+                        */
+                       if (parsetree->hasSubLinks && !sub_action->hasSubLinks)
+                               sub_action->hasSubLinks =
+                                       checkExprHasSubLink((Node *) newjointree);
                }
        }
 
        /*
-        * We copy the qualifications of the parsetree to the action and vice
-        * versa. So force hasSubLinks if one of them has it. If this is not
-        * right, the flag will get cleared later, but we mustn't risk having
-        * it not set when it needs to be.      (XXX this should probably be
-        * handled by AddQual and friends, not here...)
-        */
-       if (parsetree->hasSubLinks)
-               sub_action->hasSubLinks = TRUE;
-       else if (sub_action->hasSubLinks)
-               parsetree->hasSubLinks = TRUE;
-
-       /*
-        * Event Qualification forces copying of parsetree and splitting into
-        * two queries one w/rule_qual, one w/NOT rule_qual. Also add user
-        * query qual onto rule action
+        * Event Qualification forces copying of parsetree and splitting into two
+        * queries one w/rule_qual, one w/NOT rule_qual. Also add user query qual
+        * onto rule action
         */
        AddQual(sub_action, rule_qual);
 
@@ -207,7 +464,8 @@ rewriteRuleAction(Query *parsetree,
         * apply it to sub_action; we have to remember to update the sublink
         * inside rule_action, too.
         */
-       if (event == CMD_INSERT || event == CMD_UPDATE)
+       if ((event == CMD_INSERT || event == CMD_UPDATE) &&
+               sub_action->commandType != CMD_UTILITY)
        {
                sub_action = (Query *) ResolveNew((Node *) sub_action,
                                                                                  new_varno,
@@ -216,13 +474,49 @@ rewriteRuleAction(Query *parsetree,
                                                                                                   sub_action->rtable),
                                                                                  parsetree->targetList,
                                                                                  event,
-                                                                                 current_varno);
+                                                                                 current_varno,
+                                                                                 NULL);
                if (sub_action_ptr)
                        *sub_action_ptr = sub_action;
                else
                        rule_action = sub_action;
        }
 
+       /*
+        * If rule_action has a RETURNING clause, then either throw it away if the
+        * triggering query has no RETURNING clause, or rewrite it to emit what
+        * the triggering query's RETURNING clause asks for.  Throw an error if
+        * more than one rule has a RETURNING clause.
+        */
+       if (!parsetree->returningList)
+               rule_action->returningList = NIL;
+       else if (rule_action->returningList)
+       {
+               if (*returning_flag)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                  errmsg("cannot have RETURNING lists in multiple rules")));
+               *returning_flag = true;
+               rule_action->returningList = (List *)
+                       ResolveNew((Node *) parsetree->returningList,
+                                          parsetree->resultRelation,
+                                          0,
+                                          rt_fetch(parsetree->resultRelation,
+                                                               parsetree->rtable),
+                                          rule_action->returningList,
+                                          CMD_SELECT,
+                                          0,
+                                          &rule_action->hasSubLinks);
+
+               /*
+                * There could have been some SubLinks in parsetree's returningList,
+                * in which case we'd better mark the rule_action correctly.
+                */
+               if (parsetree->hasSubLinks && !rule_action->hasSubLinks)
+                       rule_action->hasSubLinks =
+                               checkExprHasSubLink((Node *) rule_action->returningList);
+       }
+
        return rule_action;
 }
 
@@ -249,8 +543,11 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
                        if (IsA(rtr, RangeTblRef) &&
                                rtr->rtindex == rt_index)
                        {
-                               newjointree = lremove(rtr, newjointree);
-                               /* foreach is safe because we exit loop after lremove... */
+                               newjointree = list_delete_ptr(newjointree, rtr);
+
+                               /*
+                                * foreach is safe because we exit loop after list_delete...
+                                */
                                break;
                        }
                }
@@ -260,7 +557,7 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
 
 
 /*
- * rewriteTargetList - rewrite INSERT/UPDATE targetlist into standard form
+ * rewriteTargetListIU - rewrite INSERT/UPDATE targetlist into standard form
  *
  * This has the following responsibilities:
  *
@@ -272,83 +569,140 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
  * and UPDATE, replace explicit DEFAULT specifications with column default
  * expressions.
  *
- * 2. Merge multiple entries for the same target attribute, or declare error
- * if we can't.  Presently, multiple entries are only allowed for UPDATE of
- * an array field, for example "UPDATE table SET foo[2] = 42, foo[4] = 43".
+ * 2. For an UPDATE on a view, add tlist entries for any unassigned-to
+ * attributes, assigning them their old values.  These will later get
+ * expanded to the output values of the view.  (This is equivalent to what
+ * the planner's expand_targetlist() will do for UPDATE on a regular table,
+ * but it's more convenient to do it here while we still have easy access
+ * to the view's original RT index.)
+ *
+ * 3. Merge multiple entries for the same target attribute, or declare error
+ * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
+ * portions of an array or record field, for example
+ *                     UPDATE table SET foo[2] = 42, foo[4] = 43;
  * We can merge such operations into a single assignment op.  Essentially,
  * the expression we want to produce in this case is like
  *             foo = array_set(array_set(foo, 2, 42), 4, 43)
  *
- * 3. Sort the tlist into standard order: non-junk fields in order by resno,
+ * 4. Sort the tlist into standard order: non-junk fields in order by resno,
  * then junk fields (these in no particular order).
  *
- * We must do items 1 and 2 before firing rewrite rules, else rewritten
- * references to NEW.foo will produce wrong or incomplete results.     Item 3
+ * We must do items 1,2,3 before firing rewrite rules, else rewritten
+ * references to NEW.foo will produce wrong or incomplete results.     Item 4
  * is not needed for rewriting, but will be needed by the planner, and we
- * can do it essentially for free while handling items 1 and 2.
+ * can do it essentially for free while handling the other items.
+ *
+ * If attrno_list isn't NULL, we return an additional output besides the
+ * rewritten targetlist: an integer list of the assigned-to attnums, in
+ * order of the original tlist's non-junk entries.  This is needed for
+ * processing VALUES RTEs.
  */
 static void
-rewriteTargetList(Query *parsetree, Relation target_relation)
+rewriteTargetListIU(Query *parsetree, Relation target_relation,
+                                       List **attrno_list)
 {
        CmdType         commandType = parsetree->commandType;
-       List       *tlist = parsetree->targetList;
+       TargetEntry **new_tles;
        List       *new_tlist = NIL;
+       List       *junk_tlist = NIL;
+       Form_pg_attribute att_tup;
        int                     attrno,
+                               next_junk_attrno,
                                numattrs;
        ListCell   *temp;
 
+       if (attrno_list)                        /* initialize optional result list */
+               *attrno_list = NIL;
+
        /*
-        * Scan the tuple description in the relation's relcache entry to make
-        * sure we have all the user attributes in the right order.
+        * We process the normal (non-junk) attributes by scanning the input tlist
+        * once and transferring TLEs into an array, then scanning the array to
+        * build an output tlist.  This avoids O(N^2) behavior for large numbers
+        * of attributes.
+        *
+        * Junk attributes are tossed into a separate list during the same tlist
+        * scan, then appended to the reconstructed tlist.
         */
        numattrs = RelationGetNumberOfAttributes(target_relation);
+       new_tles = (TargetEntry **) palloc0(numattrs * sizeof(TargetEntry *));
+       next_junk_attrno = numattrs + 1;
 
-       for (attrno = 1; attrno <= numattrs; attrno++)
+       foreach(temp, parsetree->targetList)
        {
-               Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1];
-               TargetEntry *new_tle = NULL;
+               TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
 
-               /* We can ignore deleted attributes */
-               if (att_tup->attisdropped)
-                       continue;
+               if (!old_tle->resjunk)
+               {
+                       /* Normal attr: stash it into new_tles[] */
+                       attrno = old_tle->resno;
+                       if (attrno < 1 || attrno > numattrs)
+                               elog(ERROR, "bogus resno %d in targetlist", attrno);
+                       att_tup = target_relation->rd_att->attrs[attrno - 1];
+
+                       /* put attrno into attrno_list even if it's dropped */
+                       if (attrno_list)
+                               *attrno_list = lappend_int(*attrno_list, attrno);
+
+                       /* We can (and must) ignore deleted attributes */
+                       if (att_tup->attisdropped)
+                               continue;
 
-               /*
-                * Look for targetlist entries matching this attr.
-                *
-                * Junk attributes are not candidates to be matched.
-                */
-               foreach(temp, tlist)
+                       /* Merge with any prior assignment to same attribute */
+                       new_tles[attrno - 1] =
+                               process_matched_tle(old_tle,
+                                                                       new_tles[attrno - 1],
+                                                                       NameStr(att_tup->attname));
+               }
+               else
                {
-                       TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
-                       Resdom     *resdom = old_tle->resdom;
+                       /*
+                        * Copy all resjunk tlist entries to junk_tlist, and assign them
+                        * resnos above the last real resno.
+                        *
+                        * Typical junk entries include ORDER BY or GROUP BY expressions
+                        * (are these actually possible in an INSERT or UPDATE?), system
+                        * attribute references, etc.
+                        */
 
-                       if (!resdom->resjunk && resdom->resno == attrno)
+                       /* Get the resno right, but don't copy unnecessarily */
+                       if (old_tle->resno != next_junk_attrno)
                        {
-                               new_tle = process_matched_tle(old_tle, new_tle,
-                                                                                         NameStr(att_tup->attname));
-                               /* keep scanning to detect multiple assignments to attr */
+                               old_tle = flatCopyTargetEntry(old_tle);
+                               old_tle->resno = next_junk_attrno;
                        }
+                       junk_tlist = lappend(junk_tlist, old_tle);
+                       next_junk_attrno++;
                }
+       }
+
+       for (attrno = 1; attrno <= numattrs; attrno++)
+       {
+               TargetEntry *new_tle = new_tles[attrno - 1];
+
+               att_tup = target_relation->rd_att->attrs[attrno - 1];
+
+               /* We can (and must) ignore deleted attributes */
+               if (att_tup->attisdropped)
+                       continue;
 
                /*
-                * Handle the two cases where we need to insert a default
-                * expression: it's an INSERT and there's no tlist entry for the
-                * column, or the tlist entry is a DEFAULT placeholder node.
+                * Handle the two cases where we need to insert a default expression:
+                * it's an INSERT and there's no tlist entry for the column, or the
+                * tlist entry is a DEFAULT placeholder node.
                 */
                if ((new_tle == NULL && commandType == CMD_INSERT) ||
-                 (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault)))
+                       (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault)))
                {
                        Node       *new_expr;
 
                        new_expr = build_column_default(target_relation, attrno);
 
                        /*
-                        * If there is no default (ie, default is effectively NULL),
-                        * we can omit the tlist entry in the INSERT case, since the
-                        * planner can insert a NULL for itself, and there's no point
-                        * in spending any more rewriter cycles on the entry.  But in
-                        * the UPDATE case we've got to explicitly set the column to
-                        * NULL.
+                        * If there is no default (ie, default is effectively NULL), we
+                        * can omit the tlist entry in the INSERT case, since the planner
+                        * can insert a NULL for itself, and there's no point in spending
+                        * any more rewriter cycles on the entry.  But in the UPDATE case
+                        * we've got to explicitly set the column to NULL.
                         */
                        if (!new_expr)
                        {
@@ -357,65 +711,58 @@ rewriteTargetList(Query *parsetree, Relation target_relation)
                                else
                                {
                                        new_expr = (Node *) makeConst(att_tup->atttypid,
+                                                                                                 -1,
                                                                                                  att_tup->attlen,
                                                                                                  (Datum) 0,
                                                                                                  true, /* isnull */
                                                                                                  att_tup->attbyval);
                                        /* this is to catch a NOT NULL domain constraint */
                                        new_expr = coerce_to_domain(new_expr,
-                                                                                               InvalidOid,
+                                                                                               InvalidOid, -1,
                                                                                                att_tup->atttypid,
-                                                                                               COERCE_IMPLICIT_CAST);
+                                                                                               COERCE_IMPLICIT_CAST,
+                                                                                               -1,
+                                                                                               false,
+                                                                                               false);
                                }
                        }
 
                        if (new_expr)
-                               new_tle = makeTargetEntry(makeResdom(attrno,
-                                                                                                        att_tup->atttypid,
-                                                                                                        att_tup->atttypmod,
+                               new_tle = makeTargetEntry((Expr *) new_expr,
+                                                                                 attrno,
+                                                                                 pstrdup(NameStr(att_tup->attname)),
+                                                                                 false);
+               }
+
+               /*
+                * For an UPDATE on a view, provide a dummy entry whenever there is
+                * no explicit assignment.
+                */
+               if (new_tle == NULL && commandType == CMD_UPDATE &&
+                       target_relation->rd_rel->relkind == RELKIND_VIEW)
+               {
+                       Node       *new_expr;
+
+                       new_expr = (Node *) makeVar(parsetree->resultRelation,
+                                                                               attrno,
+                                                                               att_tup->atttypid,
+                                                                               att_tup->atttypmod,
+                                                                               att_tup->attcollation,
+                                                                               0);
+
+                       new_tle = makeTargetEntry((Expr *) new_expr,
+                                                                         attrno,
                                                                          pstrdup(NameStr(att_tup->attname)),
-                                                                                                        false),
-                                                                                 (Expr *) new_expr);
+                                                                         false);
                }
 
                if (new_tle)
                        new_tlist = lappend(new_tlist, new_tle);
        }
 
-       /*
-        * Copy all resjunk tlist entries to the end of the new tlist, and
-        * assign them resnos above the last real resno.
-        *
-        * Typical junk entries include ORDER BY or GROUP BY expressions (are
-        * these actually possible in an INSERT or UPDATE?), system attribute
-        * references, etc.
-        */
-       foreach(temp, tlist)
-       {
-               TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
-               Resdom     *resdom = old_tle->resdom;
+       pfree(new_tles);
 
-               if (resdom->resjunk)
-               {
-                       /* Get the resno right, but don't copy unnecessarily */
-                       if (resdom->resno != attrno)
-                       {
-                               resdom = (Resdom *) copyObject((Node *) resdom);
-                               resdom->resno = attrno;
-                               old_tle = makeTargetEntry(resdom, old_tle->expr);
-                       }
-                       new_tlist = lappend(new_tlist, old_tle);
-                       attrno++;
-               }
-               else
-               {
-                       /* Let's just make sure we processed all the non-junk items */
-                       if (resdom->resno < 1 || resdom->resno > numattrs)
-                               elog(ERROR, "bogus resno %d in targetlist", resdom->resno);
-               }
-       }
-
-       parsetree->targetList = new_tlist;
+       parsetree->targetList = list_concat(new_tlist, junk_tlist);
 }
 
 
@@ -430,43 +777,70 @@ process_matched_tle(TargetEntry *src_tle,
                                        TargetEntry *prior_tle,
                                        const char *attrName)
 {
-       Resdom     *resdom = src_tle->resdom;
+       TargetEntry *result;
+       Node       *src_expr;
+       Node       *prior_expr;
+       Node       *src_input;
+       Node       *prior_input;
        Node       *priorbottom;
-       ArrayRef   *newexpr;
+       Node       *newexpr;
 
        if (prior_tle == NULL)
        {
                /*
-                * Normal case where this is the first assignment to the
-                * attribute.
+                * Normal case where this is the first assignment to the attribute.
                 */
                return src_tle;
        }
 
-       /*
+       /*----------
         * Multiple assignments to same attribute.      Allow only if all are
-        * array-assign operators with same bottom array object.
+        * FieldStore or ArrayRef assignment operations.  This is a bit
+        * tricky because what we may actually be looking at is a nest of
+        * such nodes; consider
+        *              UPDATE tab SET col.fld1.subfld1 = x, col.fld2.subfld2 = y
+        * The two expressions produced by the parser will look like
+        *              FieldStore(col, fld1, FieldStore(placeholder, subfld1, x))
+        *              FieldStore(col, fld2, FieldStore(placeholder, subfld2, x))
+        * However, we can ignore the substructure and just consider the top
+        * FieldStore or ArrayRef from each assignment, because it works to
+        * combine these as
+        *              FieldStore(FieldStore(col, fld1,
+        *                                                        FieldStore(placeholder, subfld1, x)),
+        *                                 fld2, FieldStore(placeholder, subfld2, x))
+        * Note the leftmost expression goes on the inside so that the
+        * assignments appear to occur left-to-right.
+        *
+        * For FieldStore, instead of nesting we can generate a single
+        * FieldStore with multiple target fields.      We must nest when
+        * ArrayRefs are involved though.
+        *----------
         */
-       if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) ||
-               ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
-               prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
-               ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
-               ((ArrayRef *) src_tle->expr)->refrestype !=
-               ((ArrayRef *) prior_tle->expr)->refrestype)
+       src_expr = (Node *) src_tle->expr;
+       prior_expr = (Node *) prior_tle->expr;
+       src_input = get_assignment_input(src_expr);
+       prior_input = get_assignment_input(prior_expr);
+       if (src_input == NULL ||
+               prior_input == NULL ||
+               exprType(src_expr) != exprType(prior_expr))
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("multiple assignments to same column \"%s\"",
                                                attrName)));
 
        /*
-        * Prior TLE could be a nest of ArrayRefs if we do this more than
-        * once.
+        * Prior TLE could be a nest of assignments if we do this more than once.
         */
-       priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr;
-       while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
-                  ((ArrayRef *) priorbottom)->refassgnexpr != NULL)
-               priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr;
-       if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
+       priorbottom = prior_input;
+       for (;;)
+       {
+               Node       *newbottom = get_assignment_input(priorbottom);
+
+               if (newbottom == NULL)
+                       break;                          /* found the original Var reference */
+               priorbottom = newbottom;
+       }
+       if (!equal(priorbottom, src_input))
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("multiple assignments to same column \"%s\"",
@@ -475,13 +849,72 @@ process_matched_tle(TargetEntry *src_tle,
        /*
         * Looks OK to nest 'em.
         */
-       newexpr = makeNode(ArrayRef);
-       memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
-       newexpr->refexpr = prior_tle->expr;
+       if (IsA(src_expr, FieldStore))
+       {
+               FieldStore *fstore = makeNode(FieldStore);
 
-       return makeTargetEntry(resdom, (Expr *) newexpr);
+               if (IsA(prior_expr, FieldStore))
+               {
+                       /* combine the two */
+                       memcpy(fstore, prior_expr, sizeof(FieldStore));
+                       fstore->newvals =
+                               list_concat(list_copy(((FieldStore *) prior_expr)->newvals),
+                                                       list_copy(((FieldStore *) src_expr)->newvals));
+                       fstore->fieldnums =
+                               list_concat(list_copy(((FieldStore *) prior_expr)->fieldnums),
+                                                       list_copy(((FieldStore *) src_expr)->fieldnums));
+               }
+               else
+               {
+                       /* general case, just nest 'em */
+                       memcpy(fstore, src_expr, sizeof(FieldStore));
+                       fstore->arg = (Expr *) prior_expr;
+               }
+               newexpr = (Node *) fstore;
+       }
+       else if (IsA(src_expr, ArrayRef))
+       {
+               ArrayRef   *aref = makeNode(ArrayRef);
+
+               memcpy(aref, src_expr, sizeof(ArrayRef));
+               aref->refexpr = (Expr *) prior_expr;
+               newexpr = (Node *) aref;
+       }
+       else
+       {
+               elog(ERROR, "cannot happen");
+               newexpr = NULL;
+       }
+
+       result = flatCopyTargetEntry(src_tle);
+       result->expr = (Expr *) newexpr;
+       return result;
 }
 
+/*
+ * If node is an assignment node, return its input; else return NULL
+ */
+static Node *
+get_assignment_input(Node *node)
+{
+       if (node == NULL)
+               return NULL;
+       if (IsA(node, FieldStore))
+       {
+               FieldStore *fstore = (FieldStore *) node;
+
+               return (Node *) fstore->arg;
+       }
+       else if (IsA(node, ArrayRef))
+       {
+               ArrayRef   *aref = (ArrayRef *) node;
+
+               if (aref->refassgnexpr == NULL)
+                       return NULL;
+               return (Node *) aref->refexpr;
+       }
+       return NULL;
+}
 
 /*
  * Make an expression tree for the default value for a column.
@@ -522,8 +955,7 @@ build_column_default(Relation rel, int attrno)
        if (expr == NULL)
        {
                /*
-                * No per-column default, so look for a default for the type
-                * itself.
+                * No per-column default, so look for a default for the type itself.
                 */
                expr = get_typdefault(atttype);
        }
@@ -534,9 +966,9 @@ build_column_default(Relation rel, int attrno)
        /*
         * Make sure the value is coerced to the target column type; this will
         * generally be true already, but there seem to be some corner cases
-        * involving domain defaults where it might not be true. This should
-        * match the parser's processing of non-defaulted expressions --- see
-        * updateTargetListEntry().
+        * involving domain defaults where it might not be true. This should match
+        * the parser's processing of non-defaulted expressions --- see
+        * transformAssignedExpr().
         */
        exprtype = exprType(expr);
 
@@ -544,7 +976,8 @@ build_column_default(Relation rel, int attrno)
                                                                 expr, exprtype,
                                                                 atttype, atttypmod,
                                                                 COERCION_ASSIGNMENT,
-                                                                COERCE_IMPLICIT_CAST);
+                                                                COERCE_IMPLICIT_CAST,
+                                                                -1);
        if (expr == NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -553,12 +986,175 @@ build_column_default(Relation rel, int attrno)
                                                NameStr(att_tup->attname),
                                                format_type_be(atttype),
                                                format_type_be(exprtype)),
-                  errhint("You will need to rewrite or cast the expression.")));
+                          errhint("You will need to rewrite or cast the expression.")));
 
        return expr;
 }
 
 
+/* Does VALUES RTE contain any SetToDefault items? */
+static bool
+searchForDefault(RangeTblEntry *rte)
+{
+       ListCell   *lc;
+
+       foreach(lc, rte->values_lists)
+       {
+               List       *sublist = (List *) lfirst(lc);
+               ListCell   *lc2;
+
+               foreach(lc2, sublist)
+               {
+                       Node       *col = (Node *) lfirst(lc2);
+
+                       if (IsA(col, SetToDefault))
+                               return true;
+               }
+       }
+       return false;
+}
+
+/*
+ * When processing INSERT ... VALUES with a VALUES RTE (ie, multiple VALUES
+ * lists), we have to replace any DEFAULT items in the VALUES lists with
+ * the appropriate default expressions.  The other aspects of targetlist
+ * rewriting need be applied only to the query's targetlist proper.
+ *
+ * Note that we currently can't support subscripted or field assignment
+ * in the multi-VALUES case.  The targetlist will contain simple Vars
+ * referencing the VALUES RTE, and therefore process_matched_tle() will
+ * reject any such attempt with "multiple assignments to same column".
+ */
+static void
+rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
+{
+       List       *newValues;
+       ListCell   *lc;
+
+       /*
+        * Rebuilding all the lists is a pretty expensive proposition in a big
+        * VALUES list, and it's a waste of time if there aren't any DEFAULT
+        * placeholders.  So first scan to see if there are any.
+        */
+       if (!searchForDefault(rte))
+               return;                                 /* nothing to do */
+
+       /* Check list lengths (we can assume all the VALUES sublists are alike) */
+       Assert(list_length(attrnos) == list_length(linitial(rte->values_lists)));
+
+       newValues = NIL;
+       foreach(lc, rte->values_lists)
+       {
+               List       *sublist = (List *) lfirst(lc);
+               List       *newList = NIL;
+               ListCell   *lc2;
+               ListCell   *lc3;
+
+               forboth(lc2, sublist, lc3, attrnos)
+               {
+                       Node       *col = (Node *) lfirst(lc2);
+                       int                     attrno = lfirst_int(lc3);
+
+                       if (IsA(col, SetToDefault))
+                       {
+                               Form_pg_attribute att_tup;
+                               Node       *new_expr;
+
+                               att_tup = target_relation->rd_att->attrs[attrno - 1];
+
+                               if (!att_tup->attisdropped)
+                                       new_expr = build_column_default(target_relation, attrno);
+                               else
+                                       new_expr = NULL;        /* force a NULL if dropped */
+
+                               /*
+                                * If there is no default (ie, default is effectively NULL),
+                                * we've got to explicitly set the column to NULL.
+                                */
+                               if (!new_expr)
+                               {
+                                       new_expr = (Node *) makeConst(att_tup->atttypid,
+                                                                                                 -1,
+                                                                                                 att_tup->attlen,
+                                                                                                 (Datum) 0,
+                                                                                                 true, /* isnull */
+                                                                                                 att_tup->attbyval);
+                                       /* this is to catch a NOT NULL domain constraint */
+                                       new_expr = coerce_to_domain(new_expr,
+                                                                                               InvalidOid, -1,
+                                                                                               att_tup->atttypid,
+                                                                                               COERCE_IMPLICIT_CAST,
+                                                                                               -1,
+                                                                                               false,
+                                                                                               false);
+                               }
+                               newList = lappend(newList, new_expr);
+                       }
+                       else
+                               newList = lappend(newList, col);
+               }
+               newValues = lappend(newValues, newList);
+       }
+       rte->values_lists = newValues;
+}
+
+
+/*
+ * rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed
+ *
+ * This function adds a "junk" TLE that is needed to allow the executor to
+ * find the original row for the update or delete.  When the target relation
+ * is a regular table, the junk TLE emits the ctid attribute of the original
+ * row.  When the target relation is a view, there is no ctid, so we instead
+ * emit a whole-row Var that will contain the "old" values of the view row.
+ *
+ * For UPDATE queries, this is applied after rewriteTargetListIU.  The
+ * ordering isn't actually critical at the moment.
+ */
+static void
+rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
+                                       Relation target_relation)
+{
+       Var                *var;
+       const char *attrname;
+       TargetEntry *tle;
+
+       if (target_relation->rd_rel->relkind == RELKIND_RELATION)
+       {
+               /*
+                * Emit CTID so that executor can find the row to update or delete.
+                */
+               var = makeVar(parsetree->resultRelation,
+                                         SelfItemPointerAttributeNumber,
+                                         TIDOID,
+                                         -1,
+                                         InvalidOid,
+                                         0);
+
+               attrname = "ctid";
+       }
+       else
+       {
+               /*
+                * Emit whole-row Var so that executor will have the "old" view row
+                * to pass to the INSTEAD OF trigger.
+                */
+               var = makeWholeRowVar(target_rte,
+                                                         parsetree->resultRelation,
+                                                         0);
+
+               attrname = "wholerow";
+       }
+
+       tle = makeTargetEntry((Expr *) var,
+                                                 list_length(parsetree->targetList) + 1,
+                                                 pstrdup(attrname),
+                                                 true);
+
+       parsetree->targetList = lappend(parsetree->targetList, tle);
+}
+
+
 /*
  * matchLocks -
  *       match the list of locks and returns the matching rules
@@ -588,6 +1184,28 @@ matchLocks(CmdType event,
        {
                RewriteRule *oneLock = rulelocks->rules[i];
 
+               /*
+                * Suppress ON INSERT/UPDATE/DELETE rules that are disabled or
+                * configured to not fire during the current sessions replication
+                * role. ON SELECT rules will always be applied in order to keep views
+                * working even in LOCAL or REPLICA role.
+                */
+               if (oneLock->event != CMD_SELECT)
+               {
+                       if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
+                       {
+                               if (oneLock->enabled == RULE_FIRES_ON_ORIGIN ||
+                                       oneLock->enabled == RULE_DISABLED)
+                                       continue;
+                       }
+                       else    /* ORIGIN or LOCAL ROLE */
+                       {
+                               if (oneLock->enabled == RULE_FIRES_ON_REPLICA ||
+                                       oneLock->enabled == RULE_DISABLED)
+                                       continue;
+                       }
+               }
+
                if (oneLock->event == event)
                {
                        if (parsetree->commandType != CMD_SELECT ||
@@ -603,37 +1221,114 @@ matchLocks(CmdType event,
 }
 
 
+/*
+ * ApplyRetrieveRule - expand an ON SELECT rule
+ */
 static Query *
 ApplyRetrieveRule(Query *parsetree,
                                  RewriteRule *rule,
                                  int rt_index,
                                  bool relation_level,
                                  Relation relation,
-                                 bool relIsUsed,
-                                 List *activeRIRs)
+                                 List *activeRIRs,
+                                 bool forUpdatePushedDown)
 {
        Query      *rule_action;
        RangeTblEntry *rte,
                           *subrte;
+       RowMarkClause *rc;
 
-       if (length(rule->actions) != 1)
+       if (list_length(rule->actions) != 1)
                elog(ERROR, "expected just one rule action");
        if (rule->qual != NULL)
                elog(ERROR, "cannot handle qualified ON SELECT rule");
        if (!relation_level)
                elog(ERROR, "cannot handle per-attribute ON SELECT rule");
 
+       if (rt_index == parsetree->resultRelation)
+       {
+               /*
+                * We have a view as the result relation of the query, and it wasn't
+                * rewritten by any rule.  This case is supported if there is an
+                * INSTEAD OF trigger that will trap attempts to insert/update/delete
+                * view rows.  The executor will check that; for the moment just plow
+                * ahead.  We have two cases:
+                *
+                * For INSERT, we needn't do anything.  The unmodified RTE will serve
+                * fine as the result relation.
+                *
+                * For UPDATE/DELETE, we need to expand the view so as to have source
+                * data for the operation.  But we also need an unmodified RTE to
+                * serve as the target.  So, copy the RTE and add the copy to the
+                * rangetable.  Note that the copy does not get added to the jointree.
+                * Also note that there's a hack in fireRIRrules to avoid calling
+                * this function again when it arrives at the copied RTE.
+                */
+               if (parsetree->commandType == CMD_INSERT)
+                       return parsetree;
+               else if (parsetree->commandType == CMD_UPDATE ||
+                                parsetree->commandType == CMD_DELETE)
+               {
+                       RangeTblEntry *newrte;
+
+                       rte = rt_fetch(rt_index, parsetree->rtable);
+                       newrte = copyObject(rte);
+                       parsetree->rtable = lappend(parsetree->rtable, newrte);
+                       parsetree->resultRelation = list_length(parsetree->rtable);
+
+                       /*
+                        * There's no need to do permissions checks twice, so wipe out
+                        * the permissions info for the original RTE (we prefer to keep
+                        * the bits set on the result RTE).
+                        */
+                       rte->requiredPerms = 0;
+                       rte->checkAsUser = InvalidOid;
+                       rte->selectedCols = NULL;
+                       rte->modifiedCols = NULL;
+
+                       /*
+                        * For the most part, Vars referencing the view should remain as
+                        * they are, meaning that they implicitly represent OLD values.
+                        * But in the RETURNING list if any, we want such Vars to
+                        * represent NEW values, so change them to reference the new RTE.
+                        *
+                        * Since ChangeVarNodes scribbles on the tree in-place, copy the
+                        * RETURNING list first for safety.
+                        */
+                       parsetree->returningList = copyObject(parsetree->returningList);
+                       ChangeVarNodes((Node *) parsetree->returningList, rt_index,
+                                                  parsetree->resultRelation, 0);
+
+                       /* Now, continue with expanding the original view RTE */
+               }
+               else
+                       elog(ERROR, "unrecognized commandType: %d",
+                                (int) parsetree->commandType);
+       }
+
+       /*
+        * If FOR UPDATE/SHARE of view, be sure we get right initial lock on the
+        * relations it references.
+        */
+       rc = get_parse_rowmark(parsetree, rt_index);
+       forUpdatePushedDown |= (rc != NULL);
+
        /*
-        * 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));
 
-       rule_action = fireRIRrules(rule_action, activeRIRs);
+       AcquireRewriteLocks(rule_action, forUpdatePushedDown);
+
+       /*
+        * Recursively expand any view references inside the view.
+        */
+       rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
 
        /*
-        * VIEWs are really easy --- just plug the view query in as a
-        * subselect, replacing the relation's original RTE.
+        * Now, plug the view query in as a subselect, replacing the relation's
+        * original RTE.
         */
        rte = rt_fetch(rt_index, parsetree->rtable);
 
@@ -643,75 +1338,94 @@ ApplyRetrieveRule(Query *parsetree,
        rte->inh = false;                       /* must not be set for a subquery */
 
        /*
-        * We move the view's permission check data down to its rangetable.
-        * The checks will actually be done against the *OLD* entry therein.
+        * We move the view's permission check data down to its rangetable. The
+        * checks will actually be done against the OLD entry therein.
         */
        subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
        Assert(subrte->relid == relation->rd_id);
        subrte->requiredPerms = rte->requiredPerms;
        subrte->checkAsUser = rte->checkAsUser;
+       subrte->selectedCols = rte->selectedCols;
+       subrte->modifiedCols = rte->modifiedCols;
 
        rte->requiredPerms = 0;         /* no permission check on subquery itself */
-       rte->checkAsUser = 0;
+       rte->checkAsUser = InvalidOid;
+       rte->selectedCols = NULL;
+       rte->modifiedCols = NULL;
 
        /*
-        * FOR UPDATE of view?
+        * If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
+        * FOR UPDATE/SHARE, the same as the parser would have done if the view's
+        * subquery had been written out explicitly.
+        *
+        * Note: we don't consider forUpdatePushedDown here; such marks will be
+        * made by recursing from the upper level in markQueryForLocking.
         */
-       if (intMember(rt_index, parsetree->rowMarks))
-       {
-               /*
-                * Remove the view from the list of rels that will actually be
-                * marked FOR UPDATE by the executor.  It will still be access-
-                * checked for write access, though.
-                */
-               parsetree->rowMarks = lremovei(rt_index, parsetree->rowMarks);
-
-               /*
-                * Set up the view's referenced tables as if FOR UPDATE.
-                */
-               markQueryForUpdate(rule_action, true);
-       }
+       if (rc != NULL)
+               markQueryForLocking(rule_action, (Node *) rule_action->jointree,
+                                                       rc->forUpdate, rc->noWait, true);
 
        return parsetree;
 }
 
 /*
- * Recursively mark all relations used by a view as FOR UPDATE.
+ * Recursively mark all relations used by a view as FOR UPDATE/SHARE.
  *
  * This may generate an invalid query, eg if some sub-query uses an
  * aggregate.  We leave it to the planner to detect that.
  *
- * NB: this must agree with the parser's transformForUpdate() routine.
+ * NB: this must agree with the parser's transformLockingClause() routine.
+ * However, unlike the parser we have to be careful not to mark a view's
+ * OLD and NEW rels for updating.  The best way to handle that seems to be
+ * to scan the jointree to determine which rels are used.
  */
 static void
-markQueryForUpdate(Query *qry, bool skipOldNew)
+markQueryForLocking(Query *qry, Node *jtnode,
+                                       bool forUpdate, bool noWait, bool pushedDown)
 {
-       Index           rti = 0;
-       ListCell   *l;
-
-       foreach(l, qry->rtable)
+       if (jtnode == NULL)
+               return;
+       if (IsA(jtnode, RangeTblRef))
        {
-               RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
-
-               rti++;
-
-               /* Ignore OLD and NEW entries if we are at top level of view */
-               if (skipOldNew &&
-                       (rti == PRS2_OLD_VARNO || rti == PRS2_NEW_VARNO))
-                       continue;
+               int                     rti = ((RangeTblRef *) jtnode)->rtindex;
+               RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
 
                if (rte->rtekind == RTE_RELATION)
                {
-                       if (!intMember(rti, qry->rowMarks))
-                               qry->rowMarks = lappendi(qry->rowMarks, rti);
-                       rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+                       /* ignore foreign tables */
+                       if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
+                       {
+                               applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
+                               rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+                       }
                }
                else if (rte->rtekind == RTE_SUBQUERY)
                {
-                       /* FOR UPDATE of subquery is propagated to subquery's rels */
-                       markQueryForUpdate(rte->subquery, false);
+                       applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
+                       /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
+                       markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
+                                                               forUpdate, noWait, true);
                }
+               /* other RTE types are unaffected by FOR UPDATE */
        }
+       else if (IsA(jtnode, FromExpr))
+       {
+               FromExpr   *f = (FromExpr *) jtnode;
+               ListCell   *l;
+
+               foreach(l, f->fromlist)
+                       markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown);
+       }
+       else if (IsA(jtnode, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) jtnode;
+
+               markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown);
+               markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown);
+       }
+       else
+               elog(ERROR, "unrecognized node type: %d",
+                        (int) nodeTag(jtnode));
 }
 
 
@@ -739,13 +1453,13 @@ fireRIRonSubLink(Node *node, List *activeRIRs)
 
                /* Do what we came for */
                sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect,
-                                                                                          activeRIRs);
+                                                                                          activeRIRs, false);
                /* Fall through to process lefthand args of SubLink */
        }
 
        /*
-        * Do NOT recurse into Query nodes, because fireRIRrules already
-        * processed subselects of subselects for us.
+        * Do NOT recurse into Query nodes, because fireRIRrules already processed
+        * subselects of subselects for us.
         */
        return expression_tree_walker(node, fireRIRonSubLink,
                                                                  (void *) activeRIRs);
@@ -757,24 +1471,24 @@ fireRIRonSubLink(Node *node, List *activeRIRs)
  *     Apply all RIR rules on each rangetable entry in a query
  */
 static Query *
-fireRIRrules(Query *parsetree, List *activeRIRs)
+fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
 {
+       int                     origResultRelation = parsetree->resultRelation;
        int                     rt_index;
+       ListCell   *lc;
 
        /*
-        * don't try to convert this into a foreach loop, because rtable list
-        * can get changed each time through...
+        * don't try to convert this into a foreach loop, because rtable list can
+        * get changed each time through...
         */
        rt_index = 0;
-       while (rt_index < length(parsetree->rtable))
+       while (rt_index < list_length(parsetree->rtable))
        {
                RangeTblEntry *rte;
                Relation        rel;
                List       *locks;
                RuleLock   *rules;
                RewriteRule *rule;
-               LOCKMODE        lockmode;
-               bool            relIsUsed;
                int                     i;
 
                ++rt_index;
@@ -782,13 +1496,15 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                rte = rt_fetch(rt_index, parsetree->rtable);
 
                /*
-                * A subquery RTE can't have associated rules, so there's nothing
-                * to do to this level of the query, but we must recurse into the
+                * A subquery RTE can't have associated rules, so there's nothing to
+                * do to this level of the query, but we must recurse into the
                 * subquery to expand any rule references in it.
                 */
                if (rte->rtekind == RTE_SUBQUERY)
                {
-                       rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
+                       rte->subquery = fireRIRrules(rte->subquery, activeRIRs,
+                                                                                (forUpdatePushedDown ||
+                                                       get_parse_rowmark(parsetree, rt_index) != NULL));
                        continue;
                }
 
@@ -802,35 +1518,26 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                 * If the table is not referenced in the query, then we ignore it.
                 * This prevents infinite expansion loop due to new rtable entries
                 * inserted by expansion of a rule. A table is referenced if it is
-                * part of the join set (a source table), or is referenced by any
-                * Var nodes, or is the result table.
+                * part of the join set (a source table), or is referenced by any Var
+                * nodes, or is the result table.
                 */
-               relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0);
-
-               if (!relIsUsed && rt_index != parsetree->resultRelation)
+               if (rt_index != parsetree->resultRelation &&
+                       !rangeTableEntry_used((Node *) parsetree, rt_index, 0))
                        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 or not.
+                * Also, if this is a new result relation introduced by
+                * ApplyRetrieveRule, we don't want to do anything more with it.
                 */
-               if (rt_index == parsetree->resultRelation)
-                       lockmode = NoLock;
-               else if (intMember(rt_index, parsetree->rowMarks))
-                       lockmode = RowShareLock;
-               else
-                       lockmode = AccessShareLock;
+               if (rt_index == parsetree->resultRelation &&
+                       rt_index != origResultRelation)
+                       continue;
 
-               rel = heap_open(rte->relid, lockmode);
+               /*
+                * We can use NoLock here since either the parser or
+                * AcquireRewriteLocks should have locked the rel already.
+                */
+               rel = heap_open(rte->relid, NoLock);
 
                /*
                 * Collect the RIR rules that we must apply
@@ -866,12 +1573,12 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                {
                        ListCell   *l;
 
-                       if (oidMember(RelationGetRelid(rel), activeRIRs))
+                       if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
                                ereport(ERROR,
                                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                                 errmsg("infinite recursion detected in rules for relation \"%s\"",
                                                                RelationGetRelationName(rel))));
-                       activeRIRs = lconso(RelationGetRelid(rel), activeRIRs);
+                       activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
 
                        foreach(l, locks)
                        {
@@ -882,8 +1589,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                                                                                          rt_index,
                                                                                          rule->attrno == -1,
                                                                                          rel,
-                                                                                         relIsUsed,
-                                                                                         activeRIRs);
+                                                                                         activeRIRs,
+                                                                                         forUpdatePushedDown);
                        }
 
                        activeRIRs = list_delete_first(activeRIRs);
@@ -892,30 +1599,22 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
                heap_close(rel, NoLock);
        }
 
-       /*
-        * Recurse into sublink subqueries, too.  But we already did the ones
-        * in the rtable.
-        */
-       if (parsetree->hasSubLinks)
-               query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
-                                                 QTW_IGNORE_RT_SUBQUERIES);
+       /* Recurse into subqueries in WITH */
+       foreach(lc, parsetree->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
+
+               cte->ctequery = (Node *)
+                       fireRIRrules((Query *) cte->ctequery, activeRIRs, false);
+       }
 
        /*
-        * If the query was marked having aggregates, check if this is still
-        * true after rewriting.  Ditto for sublinks.  Note there should be no
-        * aggs in the qual at this point.      (Does this code still do anything
-        * useful?      The view-becomes-subselect-in-FROM approach doesn't look
-        * like it could remove aggs or sublinks...)
+        * Recurse into sublink subqueries, too.  But we already did the ones in
+        * the rtable and cteList.
         */
-       if (parsetree->hasAggs)
-       {
-               parsetree->hasAggs = checkExprHasAggs((Node *) parsetree);
-               if (parsetree->hasAggs)
-                       if (checkExprHasAggs((Node *) parsetree->jointree))
-                               elog(ERROR, "failed to remove aggregates from qual");
-       }
        if (parsetree->hasSubLinks)
-               parsetree->hasSubLinks = checkExprHasSubLink((Node *) parsetree);
+               query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
+                                                 QTW_IGNORE_RC_SUBQUERIES);
 
        return parsetree;
 }
@@ -940,9 +1639,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 */
@@ -953,11 +1660,12 @@ CopyAndAddInvertedQual(Query *parsetree,
                                                          rt_fetch(rt_index, parsetree->rtable),
                                                          parsetree->targetList,
                                                          event,
-                                                         rt_index);
+                                                         rt_index,
+                                                         &parsetree->hasSubLinks);
        /* And attach the fixed qual */
-       AddInvertedQual(new_tree, new_qual);
+       AddInvertedQual(parsetree, new_qual);
 
-       return new_tree;
+       return parsetree;
 }
 
 
@@ -973,6 +1681,8 @@ CopyAndAddInvertedQual(Query *parsetree,
  * Output arguments:
  *     *instead_flag - set TRUE if any unqualified INSTEAD rule is found
  *                                     (must be initialized to FALSE)
+ *     *returning_flag - set TRUE if we rewrite RETURNING clause in any rule
+ *                                     (must be initialized to FALSE)
  *     *qual_product - filled with modified original query if any qualified
  *                                     INSTEAD rule is found (must be initialized to NULL)
  * Return value:
@@ -993,6 +1703,7 @@ fireRules(Query *parsetree,
                  CmdType event,
                  List *locks,
                  bool *instead_flag,
+                 bool *returning_flag,
                  Query **qual_product)
 {
        List       *results = NIL;
@@ -1023,13 +1734,13 @@ fireRules(Query *parsetree,
                if (qsrc == QSRC_QUAL_INSTEAD_RULE)
                {
                        /*
-                        * If there are INSTEAD rules with qualifications, the
-                        * original query is still performed. But all the negated rule
-                        * qualifications of the INSTEAD rules are added so it does
-                        * its actions only in cases where the rule quals of all
-                        * INSTEAD rules are false. Think of it as the default action
-                        * in a case. We save this in *qual_product so RewriteQuery()
-                        * can add it to the query list after we mangled it up enough.
+                        * If there are INSTEAD rules with qualifications, the original
+                        * query is still performed. But all the negated rule
+                        * qualifications of the INSTEAD rules are added so it does its
+                        * actions only in cases where the rule quals of all INSTEAD rules
+                        * are false. Think of it as the default action in a case. We save
+                        * this in *qual_product so RewriteQuery() can add it to the query
+                        * list after we mangled it up enough.
                         *
                         * If we have already found an unqualified INSTEAD rule, then
                         * *qual_product won't be used, so don't bother building it.
@@ -1037,7 +1748,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,
@@ -1054,7 +1765,8 @@ fireRules(Query *parsetree,
                                continue;
 
                        rule_action = rewriteRuleAction(parsetree, rule_action,
-                                                                                       event_qual, rt_index, event);
+                                                                                       event_qual, rt_index, event,
+                                                                                       returning_flag);
 
                        rule_action->querySource = qsrc;
                        rule_action->canSetTag = false;         /* might change later */
@@ -1079,15 +1791,17 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 {
        CmdType         event = parsetree->commandType;
        bool            instead = false;
+       bool            returning = false;
        Query      *qual_product = NULL;
        List       *rewritten = NIL;
 
        /*
-        * If the statement is an update, insert or delete - fire rules on it.
+        * If the statement is an insert, update, or delete, adjust its targetlist
+        * as needed, and then fire INSERT/UPDATE/DELETE rules on it.
         *
-        * SELECT rules are handled later when we have all the queries that
-        * should get executed.  Also, utilities aren't rewritten at all (do
-        * we still need that check?)
+        * SELECT rules are handled later when we have all the queries that should
+        * get executed.  Also, utilities aren't rewritten at all (do we still
+        * need that check?)
         */
        if (event != CMD_SELECT && event != CMD_UTILITY)
        {
@@ -1102,24 +1816,62 @@ 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
-                * standard form.  This will be needed by the planner anyway, and
-                * doing it now ensures that any references to NEW.field will
-                * behave sanely.
+                * Rewrite the targetlist as needed for the command type.
                 */
-               if (event == CMD_INSERT || event == CMD_UPDATE)
-                       rewriteTargetList(parsetree, rt_entry_relation);
+               if (event == CMD_INSERT)
+               {
+                       RangeTblEntry *values_rte = NULL;
+
+                       /*
+                        * If it's an INSERT ... VALUES (...), (...), ... there will be a
+                        * single RTE for the VALUES targetlists.
+                        */
+                       if (list_length(parsetree->jointree->fromlist) == 1)
+                       {
+                               RangeTblRef *rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist);
+
+                               if (IsA(rtr, RangeTblRef))
+                               {
+                                       RangeTblEntry *rte = rt_fetch(rtr->rtindex,
+                                                                                                 parsetree->rtable);
+
+                                       if (rte->rtekind == RTE_VALUES)
+                                               values_rte = rte;
+                               }
+                       }
+
+                       if (values_rte)
+                       {
+                               List       *attrnos;
+
+                               /* Process the main targetlist ... */
+                               rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos);
+                               /* ... and the VALUES expression lists */
+                               rewriteValuesRTE(values_rte, rt_entry_relation, attrnos);
+                       }
+                       else
+                       {
+                               /* Process just the main targetlist */
+                               rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
+                       }
+               }
+               else if (event == CMD_UPDATE)
+               {
+                       rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
+                       rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
+               }
+               else if (event == CMD_DELETE)
+               {
+                       rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
+               }
+               else
+                       elog(ERROR, "unrecognized commandType: %d", (int) event);
 
                /*
                 * Collect and apply the appropriate rules.
@@ -1136,16 +1888,17 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
                                                                                event,
                                                                                locks,
                                                                                &instead,
+                                                                               &returning,
                                                                                &qual_product);
 
                        /*
-                        * If we got any product queries, recursively rewrite them ---
-                        * but first check for recursion!
+                        * If we got any product queries, recursively rewrite them --- but
+                        * first check for recursion!
                         */
                        if (product_queries != NIL)
                        {
-                               ListCell           *n;
-                               rewrite_event  *rev;
+                               ListCell   *n;
+                               rewrite_event *rev;
 
                                foreach(n, rewrite_events)
                                {
@@ -1153,9 +1906,9 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
                                        if (rev->relation == RelationGetRelid(rt_entry_relation) &&
                                                rev->event == event)
                                                ereport(ERROR,
-                                                        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                                         errmsg("infinite recursion detected in rules for relation \"%s\"",
-                                                  RelationGetRelationName(rt_entry_relation))));
+                                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                                errmsg("infinite recursion detected in rules for relation \"%s\"",
+                                                          RelationGetRelationName(rt_entry_relation))));
                                }
 
                                rev = (rewrite_event *) palloc(sizeof(rewrite_event));
@@ -1169,22 +1922,64 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
                                        List       *newstuff;
 
                                        newstuff = RewriteQuery(pt, rewrite_events);
-                                       rewritten = nconc(rewritten, newstuff);
+                                       rewritten = list_concat(rewritten, newstuff);
                                }
+
+                               rewrite_events = list_delete_first(rewrite_events);
+                       }
+               }
+
+               /*
+                * If there is an INSTEAD, and the original query has a RETURNING, we
+                * have to have found a RETURNING in the rule(s), else fail. (Because
+                * DefineQueryRewrite only allows RETURNING in unconditional INSTEAD
+                * rules, there's no need to worry whether the substituted RETURNING
+                * will actually be executed --- it must be.)
+                */
+               if ((instead || qual_product != NULL) &&
+                       parsetree->returningList &&
+                       !returning)
+               {
+                       switch (event)
+                       {
+                               case CMD_INSERT:
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("cannot perform INSERT RETURNING on relation \"%s\"",
+                                                                RelationGetRelationName(rt_entry_relation)),
+                                                        errhint("You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.")));
+                                       break;
+                               case CMD_UPDATE:
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("cannot perform UPDATE RETURNING on relation \"%s\"",
+                                                                RelationGetRelationName(rt_entry_relation)),
+                                                        errhint("You need an unconditional ON UPDATE DO INSTEAD rule with a RETURNING clause.")));
+                                       break;
+                               case CMD_DELETE:
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("cannot perform DELETE RETURNING on relation \"%s\"",
+                                                                RelationGetRelationName(rt_entry_relation)),
+                                                        errhint("You need an unconditional ON DELETE DO INSTEAD rule with a RETURNING clause.")));
+                                       break;
+                               default:
+                                       elog(ERROR, "unrecognized commandType: %d",
+                                                (int) event);
+                                       break;
                        }
                }
 
-               heap_close(rt_entry_relation, NoLock);  /* keep lock! */
+               heap_close(rt_entry_relation, NoLock);
        }
 
        /*
-        * For INSERTs, the original query is done first; for UPDATE/DELETE,
-        * it is done last.  This is needed because update and delete rule
-        * actions might not do anything if they are invoked after the update
-        * or delete is performed. The command counter increment between the
-        * query executions makes the deleted (and maybe the updated) tuples
-        * disappear so the scans for them in the rule actions cannot find
-        * them.
+        * For INSERTs, the original query is done first; for UPDATE/DELETE, it is
+        * done last.  This is needed because update and delete rule actions might
+        * not do anything if they are invoked after the update or delete is
+        * performed. The command counter increment between the query executions
+        * makes the deleted (and maybe the updated) tuples disappear so the scans
+        * for them in the rule actions cannot find them.
         *
         * If we found any unqualified INSTEAD, the original query is not done at
         * all, in any form.  Otherwise, we add the modified form if qualified
@@ -1218,14 +2013,14 @@ 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)
 {
        List       *querylist;
-       List       *results = NIL;
+       List       *results;
        ListCell   *l;
        CmdType         origCmdType;
        bool            foundOriginalQuery;
@@ -1243,50 +2038,12 @@ QueryRewrite(Query *parsetree)
         *
         * Apply all the RIR rules on each query
         */
+       results = NIL;
        foreach(l, querylist)
        {
                Query      *query = (Query *) lfirst(l);
 
-               query = fireRIRrules(query, NIL);
-
-               /*
-                * If the query target was rewritten as a view, complain.
-                */
-               if (query->resultRelation)
-               {
-                       RangeTblEntry *rte = rt_fetch(query->resultRelation,
-                                                                                 query->rtable);
-
-                       if (rte->rtekind == RTE_SUBQUERY)
-                       {
-                               switch (query->commandType)
-                               {
-                                       case CMD_INSERT:
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                errmsg("cannot insert into a view"),
-                                                                errhint("You need an unconditional ON INSERT DO INSTEAD rule.")));
-                                               break;
-                                       case CMD_UPDATE:
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                errmsg("cannot update a view"),
-                                                                errhint("You need an unconditional ON UPDATE DO INSTEAD rule.")));
-                                               break;
-                                       case CMD_DELETE:
-                                               ereport(ERROR,
-                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                errmsg("cannot delete from a view"),
-                                                                errhint("You need an unconditional ON DELETE DO INSTEAD rule.")));
-                                               break;
-                                       default:
-                                               elog(ERROR, "unrecognized commandType: %d",
-                                                        (int) query->commandType);
-                                               break;
-                               }
-                       }
-               }
-
+               query = fireRIRrules(query, NIL, false);
                results = lappend(results, query);
        }
 
@@ -1294,18 +2051,17 @@ QueryRewrite(Query *parsetree)
         * Step 3
         *
         * Determine which, if any, of the resulting queries is supposed to set
-        * the command-result tag; and update the canSetTag fields
-        * accordingly.
+        * the command-result tag; and update the canSetTag fields accordingly.
         *
         * If the original query is still in the list, it sets the command tag.
-        * Otherwise, the last INSTEAD query of the same kind as the original
-        * is allowed to set the tag.  (Note these rules can leave us with no
-        * query setting the tag.  The tcop code has to cope with this by
-        * setting up a default tag based on the original un-rewritten query.)
+        * Otherwise, the last INSTEAD query of the same kind as the original is
+        * allowed to set the tag.      (Note these rules can leave us with no query
+        * setting the tag.  The tcop code has to cope with this by setting up a
+        * default tag based on the original un-rewritten query.)
         *
         * The Asserts verify that at most one query in the result list is marked
-        * canSetTag.  If we aren't checking asserts, we can fall out of the
-        * loop as soon as we find the original query.
+        * canSetTag.  If we aren't checking asserts, we can fall out of the loop
+        * as soon as we find the original query.
         */
        origCmdType = parsetree->commandType;
        foundOriginalQuery = false;