]> 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 ca9a2c27d8db784c9203f23a86f28f1686785489..3a50642fce8008d6b99b816515022cd10773d232 100644 (file)
 /*-------------------------------------------------------------------------
  *
  * rewriteHandler.c
+ *             Primary module of query rewriter.
  *
- * Copyright (c) 1994, Regents of the University of California
- *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.60 1999/10/07 04:23:15 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 "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
-#include "parser/parse_expr.h"
-#include "parser/parse_relation.h"
-#include "parser/parse_oper.h"
-#include "parser/parse_target.h"
-#include "parser/parse_type.h"
+#include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
-#include "rewrite/locks.h"
+#include "rewrite/rewriteDefine.h"
+#include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
-#include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "commands/trigger.h"
 
 
-extern void CheckSelectForUpdate(Query *rule_action);  /* in analyze.c */
-
-
-/* macros borrowed from expression_tree_mutator */
-
-#define FLATCOPY(newnode, node, nodetype)  \
-       ( (newnode) = makeNode(nodetype), \
-         memcpy((newnode), (node), sizeof(nodetype)) )
-
-#define MUTATE(newfield, oldfield, fieldtype, mutator, context)  \
-               ( (newfield) = (fieldtype) mutator((Node *) (oldfield), (context)) )
-
+/* We use a list of these to detect recursion in RewriteQuery */
+typedef struct rewrite_event
+{
+       Oid                     relation;               /* OID of relation having rules */
+       CmdType         event;                  /* type of rule being fired */
+} rewrite_event;
 
-static RewriteInfo *gatherRewriteMeta(Query *parsetree,
+static bool acquireLocksOnSubLinks(Node *node, void *context);
+static Query *rewriteRuleAction(Query *parsetree,
                                  Query *rule_action,
                                  Node *rule_qual,
                                  int rt_index,
                                  CmdType event,
-                                 bool *instead_flag);
-static bool rangeTableEntry_used(Node *node, int rt_index, int sublevels_up);
-static bool attribute_used(Node *node, int rt_index, int attno,
-                                                  int sublevels_up);
-static bool modifyAggrefUplevel(Node *node, void *context);
-static bool modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
-                                                                          int sublevels_up);
-static Node *modifyAggrefDropQual(Node *node, Node *targetNode);
-static SubLink *modifyAggrefMakeSublink(Expr *origexp, Query *parsetree);
-static Node *modifyAggrefQual(Node *node, Query *parsetree);
-static bool checkQueryHasAggs(Node *node);
-static bool checkQueryHasAggs_walker(Node *node, void *context);
-static bool checkQueryHasSubLink(Node *node);
-static bool checkQueryHasSubLink_walker(Node *node, void *context);
-static Query *fireRIRrules(Query *parsetree);
-static Query *Except_Intersect_Rewrite(Query *parsetree);
-static void check_targetlists_are_compatible(List *prev_target,
-                                                                                        List *current_target);
-static void create_intersect_list(Node *ptr, List **intersect_list);
-static Node *intersect_tree_analyze(Node *tree, Node *first_select,
-                                                                       Node *parsetree);
+                                 bool *returning_flag);
+static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
+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 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,
+                        bool forUpdatePushedDown);
+
 
 /*
- * gatherRewriteMeta -
- *       Gather meta information about parsetree, and rule. Fix rule body
- *       and qualifier so that they can be mixed with the parsetree and
- *       maintain semantic validity
+ * 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.)
  */
-static RewriteInfo *
-gatherRewriteMeta(Query *parsetree,
-                                 Query *rule_action,
-                                 Node *rule_qual,
-                                 int rt_index,
-                                 CmdType event,
-                                 bool *instead_flag)
+void
+AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
 {
-       RewriteInfo *info;
-       int                     rt_length;
-       int                     result_reln;
-
-       info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
-       info->rt_index = rt_index;
-       info->event = event;
-       info->instead_flag = *instead_flag;
-       info->rule_action = (Query *) copyObject(rule_action);
-       info->rule_qual = (Node *) copyObject(rule_qual);
-       if (info->rule_action == NULL)
-               info->nothing = TRUE;
-       else
+       ListCell   *l;
+       int                     rt_index;
+
+       /*
+        * First, process RTEs of the current query level.
+        */
+       rt_index = 0;
+       foreach(l, parsetree->rtable)
        {
-               info->nothing = FALSE;
-               info->action = info->rule_action->commandType;
-               info->current_varno = rt_index;
-               info->rt = parsetree->rtable;
-               rt_length = length(info->rt);
-               info->rt = nconc(info->rt, copyObject(info->rule_action->rtable));
-
-               info->new_varno = PRS2_NEW_VARNO + rt_length;
-               OffsetVarNodes(info->rule_action->qual, rt_length, 0);
-               OffsetVarNodes((Node *) info->rule_action->targetList, rt_length, 0);
-               OffsetVarNodes(info->rule_qual, rt_length, 0);
-               ChangeVarNodes((Node *) info->rule_action->qual,
-                                          PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
-               ChangeVarNodes((Node *) info->rule_action->targetList,
-                                          PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
-               ChangeVarNodes(info->rule_qual,
-                                          PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
+               RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+               Relation        rel;
+               LOCKMODE        lockmode;
+               List       *newaliasvars;
+               Index           curinputvarno;
+               RangeTblEntry *curinputrte;
+               ListCell   *ll;
 
-               /*
-                * bug here about replace CURRENT  -- sort of replace current is
-                * deprecated now so this code shouldn't really need to be so
-                * clutzy but.....
-                */
-               if (info->action != CMD_SELECT)
-               {                                               /* i.e update XXXXX */
-                       int                     new_result_reln = 0;
+               ++rt_index;
+               switch (rte->rtekind)
+               {
+                       case RTE_RELATION:
 
-                       result_reln = info->rule_action->resultRelation;
-                       switch (result_reln)
-                       {
-                               case PRS2_CURRENT_VARNO:
-                                       new_result_reln = rt_index;
-                                       break;
-                               case PRS2_NEW_VARNO:    /* XXX */
-                               default:
-                                       new_result_reln = result_reln + rt_length;
-                                       break;
-                       }
-                       info->rule_action->resultRelation = new_result_reln;
-               }
-       }
-       return info;
-}
+                               /*
+                                * 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;
 
-/*
- * rangeTableEntry_used -
- *     we need to process a RTE for RIR rules only if it is
- *     referenced somewhere in var nodes of the query.
- */
+                       case RTE_JOIN:
 
-typedef struct {
-       int                     rt_index;
-       int                     sublevels_up;
-} rangeTableEntry_used_context;
+                               /*
+                                * 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;
 
-static bool
-rangeTableEntry_used_walker (Node *node,
-                                                        rangeTableEntry_used_context *context)
-{
-       if (node == NULL)
-               return false;
-       if (IsA(node, Var))
-       {
-               Var                *var = (Var *) node;
+                       case RTE_SUBQUERY:
 
-               if (var->varlevelsup == context->sublevels_up &&
-                       var->varno == context->rt_index)
-                       return true;
-               return false;
+                               /*
+                                * 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;
+               }
        }
-       if (IsA(node, SubLink))
-       {
-               /*
-                * Standard expression_tree_walker will not recurse into subselect,
-                * but here we must do so.
-                */
-               SubLink    *sub = (SubLink *) node;
 
-               if (rangeTableEntry_used_walker((Node *) (sub->lefthand), context))
-                       return true;
-               if (rangeTableEntry_used((Node *) (sub->subselect),
-                                                                context->rt_index,
-                                                                context->sublevels_up + 1))
-                       return true;
-               return false;
-       }
-       if (IsA(node, Query))
+       /* Recurse into subqueries in WITH */
+       foreach(l, parsetree->cteList)
        {
-               /* Reach here after recursing down into subselect above... */
-               Query      *qry = (Query *) node;
-
-               if (rangeTableEntry_used_walker((Node *) (qry->targetList), context))
-                       return true;
-               if (rangeTableEntry_used_walker((Node *) (qry->qual), context))
-                       return true;
-               if (rangeTableEntry_used_walker((Node *) (qry->havingQual), context))
-                       return true;
-               return false;
-       }
-       return expression_tree_walker(node, rangeTableEntry_used_walker,
-                                                                 (void *) context);
-}
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
 
-static bool
-rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
-{
-       rangeTableEntry_used_context context;
+               AcquireRewriteLocks((Query *) cte->ctequery, false);
+       }
 
-       context.rt_index = rt_index;
-       context.sublevels_up = sublevels_up;
-       return rangeTableEntry_used_walker(node, &context);
+       /*
+        * 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);
 }
 
-
 /*
- * attribute_used -
- *     Check if a specific attribute number of a RTE is used
- *     somewhere in the query
+ * Walker to find sublink subqueries for AcquireRewriteLocks
  */
-
-typedef struct {
-       int                     rt_index;
-       int                     attno;
-       int                     sublevels_up;
-} attribute_used_context;
-
 static bool
-attribute_used_walker (Node *node,
-                                          attribute_used_context *context)
+acquireLocksOnSubLinks(Node *node, void *context)
 {
        if (node == NULL)
                return false;
-       if (IsA(node, Var))
-       {
-               Var                *var = (Var *) node;
-
-               if (var->varlevelsup == context->sublevels_up &&
-                       var->varno == context->rt_index &&
-                       var->varattno == context->attno)
-                       return true;
-               return false;
-       }
        if (IsA(node, SubLink))
        {
-               /*
-                * Standard expression_tree_walker will not recurse into subselect,
-                * but here we must do so.
-                */
                SubLink    *sub = (SubLink *) node;
 
-               if (attribute_used_walker((Node *) (sub->lefthand), context))
-                       return true;
-               if (attribute_used((Node *) (sub->subselect),
-                                                  context->rt_index,
-                                                  context->attno,
-                                                  context->sublevels_up + 1))
-                       return true;
-               return false;
-       }
-       if (IsA(node, Query))
-       {
-               /* Reach here after recursing down into subselect above... */
-               Query      *qry = (Query *) node;
-
-               if (attribute_used_walker((Node *) (qry->targetList), context))
-                       return true;
-               if (attribute_used_walker((Node *) (qry->qual), context))
-                       return true;
-               if (attribute_used_walker((Node *) (qry->havingQual), context))
-                       return true;
-               return false;
+               /* Do what we came for */
+               AcquireRewriteLocks((Query *) sub->subselect, false);
+               /* Fall through to process lefthand args of SubLink */
        }
-       return expression_tree_walker(node, attribute_used_walker,
-                                                                 (void *) context);
-}
-
-static bool
-attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
-{
-       attribute_used_context context;
 
-       context.rt_index = rt_index;
-       context.attno = attno;
-       context.sublevels_up = sublevels_up;
-       return attribute_used_walker(node, &context);
+       /*
+        * Do NOT recurse into Query nodes, because AcquireRewriteLocks already
+        * processed subselects of subselects for us.
+        */
+       return expression_tree_walker(node, acquireLocksOnSubLinks, context);
 }
 
 
 /*
- * modifyAggrefUplevel -
- *     In the newly created sublink for an aggregate column used in
- *     the qualification, we must increment the varlevelsup in all the
- *     var nodes.
+ * rewriteRuleAction -
+ *       Rewrite the rule action with appropriate qualifiers (taken from
+ *       the triggering query).
  *
- * NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place.  The given expression tree should have been copied
- * earlier to ensure that no unwanted side-effects occur!
+ * 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 bool
-modifyAggrefUplevel(Node *node, void *context)
+static Query *
+rewriteRuleAction(Query *parsetree,
+                                 Query *rule_action,
+                                 Node *rule_qual,
+                                 int rt_index,
+                                 CmdType event,
+                                 bool *returning_flag)
 {
-       if (node == NULL)
-               return false;
-       if (IsA(node, Var))
-       {
-               Var                *var = (Var *) node;
+       int                     current_varno,
+                               new_varno;
+       int                     rt_length;
+       Query      *sub_action;
+       Query     **sub_action_ptr;
 
-               var->varlevelsup++;
-               return false;
-       }
-       if (IsA(node, SubLink))
-       {
-               /*
-                * Standard expression_tree_walker will not recurse into subselect,
-                * but here we must do so.
-                */
-               SubLink    *sub = (SubLink *) node;
+       /*
+        * 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);
 
-               if (modifyAggrefUplevel((Node *) (sub->lefthand), context))
-                       return true;
-               if (modifyAggrefUplevel((Node *) (sub->subselect), context))
-                       return true;
-               return false;
-       }
-       if (IsA(node, Query))
-       {
-               /* Reach here after recursing down into subselect above... */
-               Query      *qry = (Query *) node;
-
-               if (modifyAggrefUplevel((Node *) (qry->targetList), context))
-                       return true;
-               if (modifyAggrefUplevel((Node *) (qry->qual), context))
-                       return true;
-               if (modifyAggrefUplevel((Node *) (qry->havingQual), context))
-                       return true;
-               return false;
-       }
-       return expression_tree_walker(node, modifyAggrefUplevel,
-                                                                 (void *) context);
-}
+       /*
+        * 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 = list_length(parsetree->rtable);
+       new_varno = PRS2_NEW_VARNO + rt_length;
 
-/*
- * modifyAggrefChangeVarnodes -
- *     Change the var nodes in a sublink created for an aggregate column
- *     used in the qualification to point to the correct local RTE.
- *
* NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place.  The given expression tree should have been copied
- * earlier to ensure that no unwanted side-effects occur!
- */
+       /*
+        * 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!).
+        */
+       sub_action = getInsertSelectQuery(rule_action, &sub_action_ptr);
 
-typedef struct {
-       int                     rt_index;
-       int                     new_index;
-       int                     sublevels_up;
-} modifyAggrefChangeVarnodes_context;
+       OffsetVarNodes((Node *) sub_action, rt_length, 0);
+       OffsetVarNodes(rule_qual, rt_length, 0);
+       /* 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,
+                                  PRS2_OLD_VARNO + rt_length, rt_index, 0);
 
-static bool
-modifyAggrefChangeVarnodes_walker(Node *node,
-                                                                 modifyAggrefChangeVarnodes_context *context)
-{
-       if (node == NULL)
-               return false;
-       if (IsA(node, Var))
+       /*
+        * 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:
+        *
+        * 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.
+        *
+        * 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.
+        *
+        * 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.
+        */
+       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.
+        */
+       if (parsetree->hasSubLinks && !sub_action->hasSubLinks)
        {
-               Var                *var = (Var *) node;
+               ListCell   *lc;
 
-               if (var->varlevelsup == context->sublevels_up &&
-                       var->varno == context->rt_index)
+               foreach(lc, parsetree->rtable)
                {
-                       var->varno = context->new_index;
-                       var->varnoold = context->new_index;
-                       var->varlevelsup = 0;
+                       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 */
                }
-               return false;
        }
-       if (IsA(node, SubLink))
-       {
-               /*
-                * Standard expression_tree_walker will not recurse into subselect,
-                * but here we must do so.
-                */
-               SubLink    *sub = (SubLink *) node;
 
-               if (modifyAggrefChangeVarnodes_walker((Node *) (sub->lefthand),
-                                                                                         context))
-                       return true;
-               if (modifyAggrefChangeVarnodes((Node *) (sub->subselect),
-                                                                          context->rt_index,
-                                                                          context->new_index,
-                                                                          context->sublevels_up + 1))
-                       return true;
-               return false;
-       }
-       if (IsA(node, Query))
+       /*
+        * 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.
+        *
+        * As above, the action's jointree must not share substructure with the
+        * main parsetree's.
+        */
+       if (sub_action->commandType != CMD_UTILITY)
        {
-               /* Reach here after recursing down into subselect above... */
-               Query      *qry = (Query *) node;
-
-               if (modifyAggrefChangeVarnodes_walker((Node *) (qry->targetList),
-                                                                                         context))
-                       return true;
-               if (modifyAggrefChangeVarnodes_walker((Node *) (qry->qual),
-                                                                                         context))
-                       return true;
-               if (modifyAggrefChangeVarnodes_walker((Node *) (qry->havingQual),
-                                                                                         context))
-                       return true;
-               return false;
+               bool            keeporig;
+               List       *newjointree;
+
+               Assert(sub_action->jointree != NULL);
+               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));
+               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->setOperations != NULL)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
+
+                       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);
+               }
        }
-       return expression_tree_walker(node, modifyAggrefChangeVarnodes_walker,
-                                                                 (void *) context);
-}
 
-static bool
-modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
-                                                  int sublevels_up)
-{
-       modifyAggrefChangeVarnodes_context context;
+       /*
+        * 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);
 
-       context.rt_index = rt_index;
-       context.new_index = new_index;
-       context.sublevels_up = sublevels_up;
-       return modifyAggrefChangeVarnodes_walker(node, &context);
-}
+       AddQual(sub_action, parsetree->jointree->quals);
 
+       /*
+        * Rewrite new.attribute w/ right hand side of target-list entry for
+        * appropriate field name in insert/update.
+        *
+        * KLUGE ALERT: since ResolveNew returns a mutated copy, we can't just
+        * apply it to sub_action; we have to remember to update the sublink
+        * inside rule_action, too.
+        */
+       if ((event == CMD_INSERT || event == CMD_UPDATE) &&
+               sub_action->commandType != CMD_UTILITY)
+       {
+               sub_action = (Query *) ResolveNew((Node *) sub_action,
+                                                                                 new_varno,
+                                                                                 0,
+                                                                                 rt_fetch(new_varno,
+                                                                                                  sub_action->rtable),
+                                                                                 parsetree->targetList,
+                                                                                 event,
+                                                                                 current_varno,
+                                                                                 NULL);
+               if (sub_action_ptr)
+                       *sub_action_ptr = sub_action;
+               else
+                       rule_action = sub_action;
+       }
 
-/*
- * modifyAggrefDropQual -
- *     remove the pure aggref clause from a qualification
- *
- * targetNode is a boolean expression node somewhere within the given
- * expression tree.  When we find it, replace it with a constant TRUE.
- * The return tree is a modified copy of the given tree; the given tree
- * is not altered.
- *
- * Note: we don't recurse into subselects looking for targetNode; that's
- * not necessary in the current usage, since in fact targetNode will be
- * within the same select level as the given toplevel node.
- */
-static Node *
-modifyAggrefDropQual(Node *node, Node *targetNode)
-{
-       if (node == NULL)
-               return NULL;
-       if (node == targetNode)
+       /*
+        * 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)
        {
-               Expr       *expr = (Expr *) node;
+               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);
 
-               if (! IsA(expr, Expr) || expr->typeOid != BOOLOID)
-                       elog(ERROR,
-                                "aggregate expression in qualification isn't of type bool");
-               return (Node *) makeConst(BOOLOID, 1, (Datum) true,
-                                                                 false, true, false, false);
+               /*
+                * 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 expression_tree_mutator(node, modifyAggrefDropQual,
-                                                                  (void *) targetNode);
+
+       return rule_action;
 }
 
 /*
- * modifyAggrefMakeSublink -
- *     Create a sublink node for a qualification expression that
- *     uses an aggregate column of a view
+ * Copy the query's jointree list, and optionally attempt to remove any
+ * occurrence of the given rt_index as a top-level join item (we do not look
+ * for it within join items; this is OK because we are only expecting to find
+ * it as an UPDATE or DELETE target relation, which will be at the top level
+ * of the join).  Returns modified jointree list --- this is a separate copy
+ * sharing no nodes with the original.
  */
-static SubLink *
-modifyAggrefMakeSublink(Expr *origexp, Query *parsetree)
+static List *
+adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
 {
-       SubLink    *sublink;
-       Query      *subquery;
-       RangeTblEntry *rte;
-       Aggref     *aggref;
-       Var                *target;
-       TargetEntry *tle;
-       Resdom     *resdom;
-       Expr       *exp = copyObject(origexp);
+       List       *newjointree = copyObject(parsetree->jointree->fromlist);
+       ListCell   *l;
 
-       if (IsA(nth(0, exp->args), Aggref))
+       if (removert)
        {
-               if (IsA(nth(1, exp->args), Aggref))
-                       elog(ERROR, "rewrite: comparison of 2 aggregate columns not supported");
-               else
-                       elog(ERROR, "rewrite: aggregate column of view must be at right side in qual");
-               /* XXX could try to commute operator, instead of failing */
-       }
+               foreach(l, newjointree)
+               {
+                       RangeTblRef *rtr = lfirst(l);
 
-       aggref = (Aggref *) nth(1, exp->args);
-       target = (Var *) (aggref->target);
-       if (! IsA(target, Var))
-               elog(ERROR, "rewrite: aggregates of views only allowed on simple variables for now");
-       rte = (RangeTblEntry *) nth(target->varno - 1, parsetree->rtable);
-
-       resdom = makeNode(Resdom);
-       resdom->resno = 1;
-       resdom->restype = aggref->aggtype;
-       resdom->restypmod = -1;
-       resdom->resname = pstrdup("<noname>");
-       resdom->reskey = 0;
-       resdom->reskeyop = 0;
-       resdom->resjunk = false;
-
-       tle = makeNode(TargetEntry);
-       tle->resdom = resdom;
-       tle->expr = (Node *) aggref; /* note this is from the copied expr */
-
-       sublink = makeNode(SubLink);
-       sublink->subLinkType = EXPR_SUBLINK;
-       sublink->useor = false;
-       /* note lefthand and oper are made from the copied expr */
-       sublink->lefthand = lcons(lfirst(exp->args), NIL);
-       sublink->oper = lcons(exp->oper, NIL);
-
-       subquery = makeNode(Query);
-       sublink->subselect = (Node *) subquery;
-
-       subquery->commandType = CMD_SELECT;
-       subquery->utilityStmt = NULL;
-       subquery->resultRelation = 0;
-       subquery->into = NULL;
-       subquery->isPortal = FALSE;
-       subquery->isBinary = FALSE;
-       subquery->isTemp = FALSE;
-       subquery->unionall = FALSE;
-       subquery->uniqueFlag = NULL;
-       subquery->sortClause = NULL;
-       subquery->rtable = lcons(rte, NIL);
-       subquery->targetList = lcons(tle, NIL);
-       subquery->qual = modifyAggrefDropQual((Node *) parsetree->qual,
-                                                                                 (Node *) origexp);
-       /*
-        * If there are still aggs in the subselect's qual, give up.
-        * Recursing would be a bad idea --- we'd likely produce an
-        * infinite recursion.  This whole technique is a crock, really...
-        */
-       if (checkQueryHasAggs(subquery->qual))
-               elog(ERROR, "Cannot handle aggregate function inserted at this place in WHERE clause");
-       subquery->groupClause = NIL;
-       subquery->havingQual = NULL;
-       subquery->hasAggs = TRUE;
-       subquery->hasSubLinks = FALSE;
-       subquery->unionClause = NULL;
-
-       modifyAggrefUplevel((Node *) subquery, NULL);
-       /*
-        * Note: it might appear that we should be passing target->varlevelsup+1
-        * here, since modifyAggrefUplevel has increased all the varlevelsup
-        * values in the subquery.  However, target itself is a pointer to a
-        * Var node in the subquery, so it's been incremented too!  What a kluge
-        * this all is ... we need to make subquery RTEs so it can go away...
-        */
-       modifyAggrefChangeVarnodes((Node *) subquery, target->varno,
-                                                          1, target->varlevelsup);
+                       if (IsA(rtr, RangeTblRef) &&
+                               rtr->rtindex == rt_index)
+                       {
+                               newjointree = list_delete_ptr(newjointree, rtr);
 
-       return sublink;
+                               /*
+                                * foreach is safe because we exit loop after list_delete...
+                                */
+                               break;
+                       }
+               }
+       }
+       return newjointree;
 }
 
 
 /*
- * modifyAggrefQual -
- *     Search for qualification expressions that contain aggregate
- *     functions and substitute them by sublinks. These expressions
- *     originally come from qualifications that use aggregate columns
- *     of a view.
+ * rewriteTargetListIU - rewrite INSERT/UPDATE targetlist into standard form
  *
- *     The return value is a modified copy of the given expression tree.
+ * This has the following responsibilities:
+ *
+ * 1. For an INSERT, add tlist entries to compute default values for any
+ * attributes that have defaults and are not assigned to in the given tlist.
+ * (We do not insert anything for default-less attributes, however.  The
+ * planner will later insert NULLs for them, but there's no reason to slow
+ * down rewriter processing with extra tlist nodes.)  Also, for both INSERT
+ * and UPDATE, replace explicit DEFAULT specifications with column default
+ * expressions.
+ *
+ * 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)
+ *
+ * 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,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 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 Node *
-modifyAggrefQual(Node *node, Query *parsetree)
+static void
+rewriteTargetListIU(Query *parsetree, Relation target_relation,
+                                       List **attrno_list)
 {
-       if (node == NULL)
-               return NULL;
-       if (IsA(node, Expr))
+       CmdType         commandType = parsetree->commandType;
+       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;
+
+       /*
+        * 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;
+
+       foreach(temp, parsetree->targetList)
        {
-               Expr       *expr = (Expr *) node;
+               TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+
+               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;
 
-               if (length(expr->args) == 2 &&
-                       (IsA(lfirst(expr->args), Aggref) ||
-                        IsA(lsecond(expr->args), Aggref)))
+                       /* 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
                {
-                       SubLink    *sub = modifyAggrefMakeSublink(expr,
-                                                                                                         parsetree);
-                       parsetree->hasSubLinks = true;
-                       /* check for aggs in resulting lefthand... */
-                       sub->lefthand = (List *) modifyAggrefQual((Node *) sub->lefthand,
-                                                                                                         parsetree);
-                       return (Node *) sub;
+                       /*
+                        * 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.
+                        */
+
+                       /* Get the resno right, but don't copy unnecessarily */
+                       if (old_tle->resno != next_junk_attrno)
+                       {
+                               old_tle = flatCopyTargetEntry(old_tle);
+                               old_tle->resno = next_junk_attrno;
+                       }
+                       junk_tlist = lappend(junk_tlist, old_tle);
+                       next_junk_attrno++;
                }
-               /* otherwise, fall through and copy the expr normally */
        }
-       if (IsA(node, Aggref))
+
+       for (attrno = 1; attrno <= numattrs; attrno++)
        {
-               /* Oops, found one that's not inside an Expr we can rearrange... */
-               elog(ERROR, "Cannot handle aggregate function inserted at this place in WHERE clause");
-       }
-       /* We do NOT recurse into subselects in this routine.  It's sufficient
-        * to get rid of aggregates that are in the qual expression proper.
-        */
-       return expression_tree_mutator(node, modifyAggrefQual,
-                                                                  (void *) parsetree);
-}
+               TargetEntry *new_tle = new_tles[attrno - 1];
 
+               att_tup = target_relation->rd_att->attrs[attrno - 1];
 
-/*
- * checkQueryHasAggs -
- *     Queries marked hasAggs might not have them any longer after
- *     rewriting. Check it.
- */
-static bool
-checkQueryHasAggs(Node *node)
-{
-       return checkQueryHasAggs_walker(node, NULL);
-}
+               /* We can (and must) ignore deleted attributes */
+               if (att_tup->attisdropped)
+                       continue;
 
-static bool
-checkQueryHasAggs_walker(Node *node, void *context)
-{
-       if (node == NULL)
-               return false;
-       if (IsA(node, Aggref))
-               return true;                    /* abort the tree traversal and return true */
-       return expression_tree_walker(node, checkQueryHasAggs_walker, context);
-}
+               /*
+                * 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)))
+               {
+                       Node       *new_expr;
 
-/*
- * checkQueryHasSubLink -
- *     Queries marked hasSubLinks might not have them any longer after
- *     rewriting. Check it.
- */
-static bool
-checkQueryHasSubLink(Node *node)
-{
-       return checkQueryHasSubLink_walker(node, NULL);
-}
-
-static bool
-checkQueryHasSubLink_walker(Node *node, void *context)
-{
-       if (node == NULL)
-               return false;
-       if (IsA(node, SubLink))
-               return true;                    /* abort the tree traversal and return true */
-       return expression_tree_walker(node, checkQueryHasSubLink_walker, context);
-}
+                       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 (!new_expr)
+                       {
+                               if (commandType == CMD_INSERT)
+                                       new_tle = NULL;
+                               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, -1,
+                                                                                               att_tup->atttypid,
+                                                                                               COERCE_IMPLICIT_CAST,
+                                                                                               -1,
+                                                                                               false,
+                                                                                               false);
+                               }
+                       }
 
-static Node *
-FindMatchingTLEntry(List *tlist, char *e_attname)
-{
-       List       *i;
+                       if (new_expr)
+                               new_tle = makeTargetEntry((Expr *) new_expr,
+                                                                                 attrno,
+                                                                                 pstrdup(NameStr(att_tup->attname)),
+                                                                                 false);
+               }
 
-       foreach(i, tlist)
-       {
-               TargetEntry *tle = lfirst(i);
-               char       *resname;
+               /*
+                * 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);
+               }
 
-               resname = tle->resdom->resname;
-               if (!strcmp(e_attname, resname))
-                       return (tle->expr);
+               if (new_tle)
+                       new_tlist = lappend(new_tlist, new_tle);
        }
-       return NULL;
-}
 
+       pfree(new_tles);
 
-static Node *
-make_null(Oid type)
-{
-       Const      *c = makeNode(Const);
-
-       c->consttype = type;
-       c->constlen = get_typlen(type);
-       c->constvalue = PointerGetDatum(NULL);
-       c->constisnull = true;
-       c->constbyval = get_typbyval(type);
-       return (Node *) c;
+       parsetree->targetList = list_concat(new_tlist, junk_tlist);
 }
 
 
 /*
- * apply_RIR_adjust_sublevel -
- *     Set the varlevelsup field of all Var nodes in the given expression tree
- *     to sublevels_up.  We do NOT recurse into subselects.
+ * Convert a matched TLE from the original tlist into a correct new TLE.
  *
- * NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place.  The given expression tree should have been copied
- * earlier to ensure that no unwanted side-effects occur!
+ * This routine detects and handles multiple assignments to the same target
+ * attribute.  (The attribute name is needed only for error messages.)
  */
-static bool
-apply_RIR_adjust_sublevel_walker(Node *node, int *sublevels_up)
+static TargetEntry *
+process_matched_tle(TargetEntry *src_tle,
+                                       TargetEntry *prior_tle,
+                                       const char *attrName)
 {
-       if (node == NULL)
-               return false;
-       if (IsA(node, Var))
+       TargetEntry *result;
+       Node       *src_expr;
+       Node       *prior_expr;
+       Node       *src_input;
+       Node       *prior_input;
+       Node       *priorbottom;
+       Node       *newexpr;
+
+       if (prior_tle == NULL)
        {
-               Var                *var = (Var *) node;
-
-               var->varlevelsup = *sublevels_up;
-               return false;
+               /*
+                * Normal case where this is the first assignment to the attribute.
+                */
+               return src_tle;
        }
-       return expression_tree_walker(node, apply_RIR_adjust_sublevel_walker,
-                                                                 (void *) sublevels_up);
-}
-
-static void
-apply_RIR_adjust_sublevel(Node *node, int sublevels_up)
-{
-       apply_RIR_adjust_sublevel_walker(node, &sublevels_up);
-}
 
+       /*----------
+        * Multiple assignments to same attribute.      Allow only if all are
+        * 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.
+        *----------
+        */
+       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)));
 
-/*
- * apply_RIR_view
- *     Replace Vars matching a given RT index with copies of TL expressions.
- */
+       /*
+        * Prior TLE could be a nest of assignments if we do this more than once.
+        */
+       priorbottom = prior_input;
+       for (;;)
+       {
+               Node       *newbottom = get_assignment_input(priorbottom);
 
-typedef struct {
-       int                             rt_index;
-       int                             sublevels_up;
-       RangeTblEntry  *rte;
-       List               *tlist;
-       int                        *modified;
-} apply_RIR_view_context;
+               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\"",
+                                               attrName)));
 
-static Node *
-apply_RIR_view_mutator(Node *node,
-                                          apply_RIR_view_context *context)
-{
-       if (node == NULL)
-               return NULL;
-       if (IsA(node, Var))
+       /*
+        * Looks OK to nest 'em.
+        */
+       if (IsA(src_expr, FieldStore))
        {
-               Var                *var = (Var *) node;
+               FieldStore *fstore = makeNode(FieldStore);
 
-               if (var->varlevelsup == context->sublevels_up &&
-                       var->varno == context->rt_index)
+               if (IsA(prior_expr, FieldStore))
                {
-                       Node       *expr;
-
-                       if (var->varattno < 0)
-                               elog(ERROR, "system column %s not available - %s is a view",
-                                        get_attname(context->rte->relid, var->varattno),
-                                        context->rte->relname);
-
-                       expr = FindMatchingTLEntry(context->tlist,
-                                                                          get_attname(context->rte->relid,
-                                                                                                  var->varattno));
-                       if (expr == NULL)
-                       {
-                               /* XXX shouldn't this be an error condition? */
-                               return make_null(var->vartype);
-                       }
-
-                       expr = copyObject(expr);
-                       if (var->varlevelsup > 0)
-                               apply_RIR_adjust_sublevel(expr, var->varlevelsup);
-                       *(context->modified) = true;
-                       return (Node *) expr;
+                       /* 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;
                }
-               /* otherwise fall through to copy the var normally */
+               newexpr = (Node *) fstore;
        }
-       /*
-        * Since expression_tree_mutator won't touch subselects, we have to
-        * handle them specially.
-        */
-       if (IsA(node, SubLink))
+       else if (IsA(src_expr, ArrayRef))
        {
-               SubLink   *sublink = (SubLink *) node;
-               SubLink   *newnode;
-
-               FLATCOPY(newnode, sublink, SubLink);
-               MUTATE(newnode->lefthand, sublink->lefthand, List *,
-                          apply_RIR_view_mutator, context);
-               context->sublevels_up++;
-               MUTATE(newnode->subselect, sublink->subselect, Node *,
-                          apply_RIR_view_mutator, context);
-               context->sublevels_up--;
-               return (Node *) newnode;
+               ArrayRef   *aref = makeNode(ArrayRef);
+
+               memcpy(aref, src_expr, sizeof(ArrayRef));
+               aref->refexpr = (Expr *) prior_expr;
+               newexpr = (Node *) aref;
        }
-       if (IsA(node, Query))
+       else
        {
-               Query  *query = (Query *) node;
-               Query  *newnode;
-
-               FLATCOPY(newnode, query, Query);
-               MUTATE(newnode->targetList, query->targetList, List *,
-                          apply_RIR_view_mutator, context);
-               MUTATE(newnode->qual, query->qual, Node *,
-                          apply_RIR_view_mutator, context);
-               MUTATE(newnode->havingQual, query->havingQual, Node *,
-                          apply_RIR_view_mutator, context);
-               return (Node *) newnode;
+               elog(ERROR, "cannot happen");
+               newexpr = NULL;
        }
-       return expression_tree_mutator(node, apply_RIR_view_mutator,
-                                                                  (void *) context);
+
+       result = flatCopyTargetEntry(src_tle);
+       result->expr = (Expr *) newexpr;
+       return result;
 }
 
+/*
+ * If node is an assignment node, return its input; else return NULL
+ */
 static Node *
-apply_RIR_view(Node *node, int rt_index, RangeTblEntry *rte, List *tlist,
-                          int *modified, int sublevels_up)
+get_assignment_input(Node *node)
 {
-       apply_RIR_view_context  context;
+       if (node == NULL)
+               return NULL;
+       if (IsA(node, FieldStore))
+       {
+               FieldStore *fstore = (FieldStore *) node;
 
-       context.rt_index = rt_index;
-       context.sublevels_up = sublevels_up;
-       context.rte = rte;
-       context.tlist = tlist;
-       context.modified = modified;
+               return (Node *) fstore->arg;
+       }
+       else if (IsA(node, ArrayRef))
+       {
+               ArrayRef   *aref = (ArrayRef *) node;
 
-       return apply_RIR_view_mutator(node, &context);
+               if (aref->refassgnexpr == NULL)
+                       return NULL;
+               return (Node *) aref->refexpr;
+       }
+       return NULL;
 }
 
-
-static Query *
-ApplyRetrieveRule(Query *parsetree,
-                                 RewriteRule *rule,
-                                 int rt_index,
-                                 int relation_level,
-                                 Relation relation,
-                                 bool relWasInJoinSet,
-                                 int *modified)
+/*
+ * Make an expression tree for the default value for a column.
+ *
+ * If there is no default, return a NULL instead.
+ */
+Node *
+build_column_default(Relation rel, int attrno)
 {
-       Query      *rule_action = NULL;
-       Node       *rule_qual;
-       List       *rtable,
-                          *addedrtable,
-                          *l;
-       int                     nothing,
-                               rt_length;
-       int                     badsql = false;
-
-       rule_qual = rule->qual;
-       if (rule->actions)
+       TupleDesc       rd_att = rel->rd_att;
+       Form_pg_attribute att_tup = rd_att->attrs[attrno - 1];
+       Oid                     atttype = att_tup->atttypid;
+       int32           atttypmod = att_tup->atttypmod;
+       Node       *expr = NULL;
+       Oid                     exprtype;
+
+       /*
+        * Scan to see if relation has a default for this column.
+        */
+       if (rd_att->constr && rd_att->constr->num_defval > 0)
        {
-               if (length(rule->actions) > 1)  /* ??? because we don't handle
-                                                                                * rules with more than one
-                                                                                * action? -ay */
+               AttrDefault *defval = rd_att->constr->defval;
+               int                     ndef = rd_att->constr->num_defval;
 
-                       return parsetree;
-               rule_action = copyObject(lfirst(rule->actions));
-               nothing = FALSE;
+               while (--ndef >= 0)
+               {
+                       if (attrno == defval[ndef].adnum)
+                       {
+                               /*
+                                * Found it, convert string representation to node tree.
+                                */
+                               expr = stringToNode(defval[ndef].adbin);
+                               break;
+                       }
+               }
        }
-       else
-               nothing = TRUE;
 
-       rtable = copyObject(parsetree->rtable);
-       rt_length = length(rtable);     /* original length, not counting rule */
+       if (expr == NULL)
+       {
+               /*
+                * No per-column default, so look for a default for the type itself.
+                */
+               expr = get_typdefault(atttype);
+       }
 
-       addedrtable = copyObject(rule_action->rtable);
+       if (expr == NULL)
+               return NULL;                    /* No default anywhere */
 
-       /* If the original rel wasn't in the join set, none of its spawn is.
-        * If it was, then leave the spawn's flags as they are.
+       /*
+        * 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
+        * transformAssignedExpr().
         */
-       if (! relWasInJoinSet)
+       exprtype = exprType(expr);
+
+       expr = coerce_to_target_type(NULL,      /* no UNKNOWN params here */
+                                                                expr, exprtype,
+                                                                atttype, atttypmod,
+                                                                COERCION_ASSIGNMENT,
+                                                                COERCE_IMPLICIT_CAST,
+                                                                -1);
+       if (expr == NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                errmsg("column \"%s\" is of type %s"
+                                               " but default expression is of type %s",
+                                               NameStr(att_tup->attname),
+                                               format_type_be(atttype),
+                                               format_type_be(exprtype)),
+                          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)
        {
-               foreach(l, addedrtable)
+               List       *sublist = (List *) lfirst(lc);
+               ListCell   *lc2;
+
+               foreach(lc2, sublist)
                {
-                       RangeTblEntry *rte = lfirst(l);
-                       rte->inJoinSet = false;
+                       Node       *col = (Node *) lfirst(lc2);
+
+                       if (IsA(col, SetToDefault))
+                               return true;
                }
        }
+       return false;
+}
 
-       rtable = nconc(rtable, addedrtable);
-       parsetree->rtable = rtable;
+/*
+ * 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;
 
-       /* FOR UPDATE of view... */
-       foreach(l, parsetree->rowMark)
-       {
-               if (((RowMark *) lfirst(l))->rti == rt_index)
-                       break;
-       }
-       if (l != NULL)                          /* oh, hell -:) */
-       {
-               RowMark    *newrm;
-               Index           rti = 1;
-               List       *l2;
+       /*
+        * 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 */
 
-               CheckSelectForUpdate(rule_action);
+       /* Check list lengths (we can assume all the VALUES sublists are alike) */
+       Assert(list_length(attrnos) == list_length(linitial(rte->values_lists)));
 
-               /*
-                * We believe that rt_index is VIEW - nothing should be marked for
-                * VIEW, but ACL check must be done. As for real tables of VIEW -
-                * their rows must be marked, but we have to skip ACL check for
-                * them.
-                */
-               ((RowMark *) lfirst(l))->info &= ~ROW_MARK_FOR_UPDATE;
+       newValues = NIL;
+       foreach(lc, rte->values_lists)
+       {
+               List       *sublist = (List *) lfirst(lc);
+               List       *newList = NIL;
+               ListCell   *lc2;
+               ListCell   *lc3;
 
-               foreach(l2, rule_action->rtable)
+               forboth(lc2, sublist, lc3, attrnos)
                {
+                       Node       *col = (Node *) lfirst(lc2);
+                       int                     attrno = lfirst_int(lc3);
 
-                       /*
-                        * RTable of VIEW has two entries of VIEW itself - we use
-                        * relid to skip them.
-                        */
-                       if (relation->rd_id != ((RangeTblEntry *) lfirst(l2))->relid)
+                       if (IsA(col, SetToDefault))
                        {
-                               newrm = makeNode(RowMark);
-                               newrm->rti = rti + rt_length;
-                               newrm->info = ROW_MARK_FOR_UPDATE;
-                               lnext(l) = lcons(newrm, lnext(l));
-                               l = lnext(l);
-                       }
-                       rti++;
-               }
-       }
+                               Form_pg_attribute att_tup;
+                               Node       *new_expr;
 
-       rule_action->rtable = rtable;
-       OffsetVarNodes((Node *) rule_qual, rt_length, 0);
-       OffsetVarNodes((Node *) rule_action, rt_length, 0);
+                               att_tup = target_relation->rd_att->attrs[attrno - 1];
 
-       ChangeVarNodes((Node *) rule_qual,
-                                  PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
-       ChangeVarNodes((Node *) rule_action,
-                                  PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
+                               if (!att_tup->attisdropped)
+                                       new_expr = build_column_default(target_relation, attrno);
+                               else
+                                       new_expr = NULL;        /* force a NULL if dropped */
 
-       if (relation_level)
-       {
-               RangeTblEntry  *rte = (RangeTblEntry *) nth(rt_index - 1, rtable);
-
-               parsetree = (Query *) apply_RIR_view((Node *) parsetree,
-                                                                                        rt_index, rte,
-                                                                                        rule_action->targetList,
-                                                                                        modified, 0);
-               rule_action = (Query *) apply_RIR_view((Node *) rule_action,
-                                                                                          rt_index, rte,
-                                                                                          rule_action->targetList,
-                                                                                          modified, 0);
-       }
-       else
-       {
-               HandleRIRAttributeRule(parsetree, rtable, rule_action->targetList,
-                                                          rt_index, rule->attrno, modified, &badsql);
-       }
-       if (*modified && !badsql)
-       {
-               AddQual(parsetree, rule_action->qual);
-               AddGroupClause(parsetree, rule_action->groupClause,
-                                          rule_action->targetList);
-               AddHavingQual(parsetree, rule_action->havingQual);
-               parsetree->hasAggs = (rule_action->hasAggs || parsetree->hasAggs);
-               parsetree->hasSubLinks = (rule_action->hasSubLinks || parsetree->hasSubLinks);
+                               /*
+                                * 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);
        }
-
-       return parsetree;
+       rte->values_lists = newValues;
 }
 
 
 /*
- * fireRIRonSubselect -
- *     Apply fireRIRrules() to each subselect found in the given tree.
+ * rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed
  *
- * NOTE: although this has the form of a walker, we cheat and modify the
- * SubLink nodes in-place.  It is caller's responsibility to ensure that
- * no unwanted side-effects occur!
+ * 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 bool
-fireRIRonSubselect(Node *node, void *context)
+static void
+rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
+                                       Relation target_relation)
 {
-       if (node == NULL)
-               return false;
-       if (IsA(node, SubLink))
+       Var                *var;
+       const char *attrname;
+       TargetEntry *tle;
+
+       if (target_relation->rd_rel->relkind == RELKIND_RELATION)
        {
                /*
-                * Standard expression_tree_walker will not recurse into subselect,
-                * but here we must do so.
+                * Emit CTID so that executor can find the row to update or delete.
                 */
-               SubLink    *sub = (SubLink *) node;
-               Query      *qry;
-
-               /* Process lefthand args */
-               if (fireRIRonSubselect((Node *) (sub->lefthand), context))
-                       return true;
-               /* Do what we came for */
-               qry = fireRIRrules((Query *) (sub->subselect));
-               sub->subselect = (Node *) qry;
-               /* Must recurse to handle any sub-subselects! */
-               if (fireRIRonSubselect((Node *) qry, context))
-                       return true;
-               return false;
+               var = makeVar(parsetree->resultRelation,
+                                         SelfItemPointerAttributeNumber,
+                                         TIDOID,
+                                         -1,
+                                         InvalidOid,
+                                         0);
+
+               attrname = "ctid";
        }
-       if (IsA(node, Query))
+       else
        {
-               /* Reach here after recursing down into subselect above... */
-               Query      *qry = (Query *) node;
-
-               if (fireRIRonSubselect((Node *) (qry->targetList), context))
-                       return true;
-               if (fireRIRonSubselect((Node *) (qry->qual), context))
-                       return true;
-               if (fireRIRonSubselect((Node *) (qry->havingQual), context))
-                       return true;
-               return false;
+               /*
+                * 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";
        }
-       return expression_tree_walker(node, fireRIRonSubselect,
-                                                                 (void *) context);
+
+       tle = makeTargetEntry((Expr *) var,
+                                                 list_length(parsetree->targetList) + 1,
+                                                 pstrdup(attrname),
+                                                 true);
+
+       parsetree->targetList = lappend(parsetree->targetList, tle);
 }
 
 
 /*
- * fireRIRrules -
- *     Apply all RIR rules on each rangetable entry in a query
+ * matchLocks -
+ *       match the list of locks and returns the matching rules
  */
-static Query *
-fireRIRrules(Query *parsetree)
+static List *
+matchLocks(CmdType event,
+                  RuleLock *rulelocks,
+                  int varno,
+                  Query *parsetree)
 {
-       int                     rt_index;
-       RangeTblEntry *rte;
-       Relation        rel;
-       List       *locks;
-       RuleLock   *rules;
-       RewriteRule *rule;
-       RewriteRule RIRonly;
-       bool            relWasInJoinSet;
-       int                     modified = false;
+       List       *matching_locks = NIL;
+       int                     nlocks;
        int                     i;
-       List       *l;
-
-       /* 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))
-       {
-               ++rt_index;
 
-               rte = nth(rt_index - 1, parsetree->rtable);
+       if (rulelocks == NULL)
+               return NIL;
 
-               /*
-                * If the table is not one named in the original FROM clause
-                * then it must be referenced in the query, or we ignore it.
-                * This prevents infinite expansion loop due to new rtable
-                * entries inserted by expansion of a rule.
-                */
-               if (! rte->inFromCl && rt_index != parsetree->resultRelation &&
-                       ! rangeTableEntry_used((Node *) parsetree, rt_index, 0))
-               {
-                       /* Make sure the planner ignores it too... */
-                       rte->inJoinSet = false;
-                       continue;
-               }
+       if (parsetree->commandType != CMD_SELECT)
+       {
+               if (parsetree->resultRelation != varno)
+                       return NIL;
+       }
 
-               rel = heap_openr(rte->relname, AccessShareLock);
-               rules = rel->rd_rules;
-               if (rules == NULL)
-               {
-                       heap_close(rel, AccessShareLock);
-                       continue;
-               }
+       nlocks = rulelocks->numLocks;
 
-               relWasInJoinSet = rte->inJoinSet; /* save before possibly clearing */
+       for (i = 0; i < nlocks; i++)
+       {
+               RewriteRule *oneLock = rulelocks->rules[i];
 
                /*
-                * Collect the RIR rules that we must apply
+                * 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.
                 */
-               locks = NIL;
-               for (i = 0; i < rules->numLocks; i++)
+               if (oneLock->event != CMD_SELECT)
                {
-                       rule = rules->rules[i];
-                       if (rule->event != CMD_SELECT)
-                               continue;
-
-                       if (rule->attrno > 0)
+                       if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
                        {
-                               /* per-attr rule; do we need it? */
-                               if (! attribute_used((Node *) parsetree,
-                                                                        rt_index,
-                                                                        rule->attrno, 0))
+                               if (oneLock->enabled == RULE_FIRES_ON_ORIGIN ||
+                                       oneLock->enabled == RULE_DISABLED)
                                        continue;
                        }
-                       else
+                       else    /* ORIGIN or LOCAL ROLE */
                        {
-                               /* Rel-wide ON SELECT DO INSTEAD means this is a view.
-                                * Remove the view from the planner's join target set,
-                                * or we'll get no rows out because view itself is empty!
-                                */
-                               if (rule->isInstead)
-                                       rte->inJoinSet = false;
+                               if (oneLock->enabled == RULE_FIRES_ON_REPLICA ||
+                                       oneLock->enabled == RULE_DISABLED)
+                                       continue;
                        }
-
-                       locks = lappend(locks, rule);
                }
 
-               /*
-                * Check permissions
-                */
-               checkLockPerms(locks, parsetree, rt_index);
-
-               /*
-                * Now apply them
-                */
-               foreach(l, locks)
+               if (oneLock->event == event)
                {
-                       rule = lfirst(l);
-
-                       RIRonly.event = rule->event;
-                       RIRonly.attrno = rule->attrno;
-                       RIRonly.qual = rule->qual;
-                       RIRonly.actions = rule->actions;
-
-                       parsetree = ApplyRetrieveRule(parsetree,
-                                                                                 &RIRonly,
-                                                                                 rt_index,
-                                                                                 RIRonly.attrno == -1,
-                                                                                 rel,
-                                                                                 relWasInJoinSet,
-                                                                                 &modified);
+                       if (parsetree->commandType != CMD_SELECT ||
+                               (oneLock->attrno == -1 ?
+                                rangeTableEntry_used((Node *) parsetree, varno, 0) :
+                                attribute_used((Node *) parsetree,
+                                                               varno, oneLock->attrno, 0)))
+                               matching_locks = lappend(matching_locks, oneLock);
                }
-
-               heap_close(rel, AccessShareLock);
        }
 
-       fireRIRonSubselect((Node *) parsetree, NULL);
-
-       parsetree->qual = modifyAggrefQual(parsetree->qual, parsetree);
-
-       return parsetree;
+       return matching_locks;
 }
 
 
 /*
- * idea is to fire regular rules first, then qualified instead
- * rules and unqualified instead rules last. Any lemming is counted for.
+ * ApplyRetrieveRule - expand an ON SELECT rule
  */
-static List *
-orderRules(List *locks)
+static Query *
+ApplyRetrieveRule(Query *parsetree,
+                                 RewriteRule *rule,
+                                 int rt_index,
+                                 bool relation_level,
+                                 Relation relation,
+                                 List *activeRIRs,
+                                 bool forUpdatePushedDown)
 {
-       List       *regular = NIL;
-       List       *instead_rules = NIL;
-       List       *instead_qualified = NIL;
-       List       *i;
-
-       foreach(i, locks)
+       Query      *rule_action;
+       RangeTblEntry *rte,
+                          *subrte;
+       RowMarkClause *rc;
+
+       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)
        {
-               RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-
-               if (rule_lock->isInstead)
+               /*
+                * 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)
                {
-                       if (rule_lock->qual == NULL)
-                               instead_rules = lappend(instead_rules, rule_lock);
-                       else
-                               instead_qualified = lappend(instead_qualified, rule_lock);
+                       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
-                       regular = lappend(regular, rule_lock);
+                       elog(ERROR, "unrecognized commandType: %d",
+                                (int) parsetree->commandType);
        }
-       regular = nconc(regular, instead_qualified);
-       return nconc(regular, instead_rules);
-}
 
+       /*
+        * 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 acquire needed locks on
+        * the relations it mentions.
+        */
+       rule_action = copyObject(linitial(rule->actions));
 
-static Query *
-CopyAndAddQual(Query *parsetree,
-                          List *actions,
-                          Node *rule_qual,
-                          int rt_index,
-                          CmdType event)
-{
-       Query      *new_tree = (Query *) copyObject(parsetree);
-       Node       *new_qual = NULL;
-       Query      *rule_action = NULL;
-
-       if (actions)
-               rule_action = lfirst(actions);
-       if (rule_qual != NULL)
-               new_qual = (Node *) copyObject(rule_qual);
-       if (rule_action != NULL)
-       {
-               List       *rtable;
-               int                     rt_length;
-
-               rtable = new_tree->rtable;
-               rt_length = length(rtable);
-               rtable = nconc(rtable, copyObject(rule_action->rtable));
-               new_tree->rtable = rtable;
-               OffsetVarNodes(new_qual, rt_length, 0);
-               ChangeVarNodes(new_qual, PRS2_CURRENT_VARNO + rt_length, rt_index, 0);
-       }
-       /* XXX -- where current doesn't work for instead nothing.... yet */
-       AddNotQual(new_tree, new_qual);
+       AcquireRewriteLocks(rule_action, forUpdatePushedDown);
 
-       return new_tree;
-}
+       /*
+        * Recursively expand any view references inside the view.
+        */
+       rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
 
+       /*
+        * Now, plug the view query in as a subselect, replacing the relation's
+        * original RTE.
+        */
+       rte = rt_fetch(rt_index, parsetree->rtable);
 
+       rte->rtekind = RTE_SUBQUERY;
+       rte->relid = InvalidOid;
+       rte->subquery = rule_action;
+       rte->inh = false;                       /* must not be set for a subquery */
 
-/*
- *     fireRules -
- *        Iterate through rule locks applying rules.
- *        All rules create their own parsetrees. Instead rules
- *        with rule qualification save the original parsetree
- *        and add their negated qualification to it. Real instead
- *        rules finally throw away the original parsetree.
+       /*
+        * 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 = InvalidOid;
+       rte->selectedCols = NULL;
+       rte->modifiedCols = NULL;
+
+       /*
+        * 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 (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/SHARE.
  *
- *        remember: reality is for dead birds -- glass
+ * 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 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 List *
-fireRules(Query *parsetree,
-                 int rt_index,
-                 CmdType event,
-                 bool *instead_flag,
-                 List *locks,
-                 List **qual_products)
+static void
+markQueryForLocking(Query *qry, Node *jtnode,
+                                       bool forUpdate, bool noWait, bool pushedDown)
 {
-       RewriteInfo *info;
-       List       *results = NIL;
-       List       *i;
-
-       /* choose rule to fire from list of rules */
-       if (locks == NIL)
-               return NIL;
-
-       locks = orderRules(locks);      /* real instead rules last */
-       foreach(i, locks)
+       if (jtnode == NULL)
+               return;
+       if (IsA(jtnode, RangeTblRef))
        {
-               RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-               Node       *qual,
-                                  *event_qual;
-               List       *actions;
-               List       *r;
+               int                     rti = ((RangeTblRef *) jtnode)->rtindex;
+               RangeTblEntry *rte = rt_fetch(rti, qry->rtable);
 
-               /*
-                * Instead rules change the resultRelation of the query. So the
-                * permission checks on the initial resultRelation would never be
-                * done (this is normally done in the executor deep down). So we
-                * must do it here. The result relations resulting from earlier
-                * rewrites are already checked against the rules eventrelation
-                * owner (during matchLocks) and have the skipAcl flag set.
-                */
-               if (rule_lock->isInstead &&
-                       parsetree->commandType != CMD_SELECT)
+               if (rte->rtekind == RTE_RELATION)
                {
-                       RangeTblEntry *rte;
-                       int32           acl_rc;
-                       int32           reqperm;
-
-                       switch (parsetree->commandType)
+                       /* ignore foreign tables */
+                       if (get_rel_relkind(rte->relid) != RELKIND_FOREIGN_TABLE)
                        {
-                               case CMD_INSERT:
-                                       reqperm = ACL_AP;
-                                       break;
-                               default:
-                                       reqperm = ACL_WR;
-                                       break;
-                       }
-
-                       rte = (RangeTblEntry *) nth(parsetree->resultRelation - 1,
-                                                                               parsetree->rtable);
-                       if (!rte->skipAcl)
-                       {
-                               acl_rc = pg_aclcheck(rte->relname,
-                                                                        GetPgUserName(), reqperm);
-                               if (acl_rc != ACLCHECK_OK)
-                               {
-                                       elog(ERROR, "%s: %s",
-                                                rte->relname,
-                                                aclcheck_error_strings[acl_rc]);
-                               }
+                               applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
+                               rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
                        }
                }
-
-               /* multiple rule action time */
-               *instead_flag = rule_lock->isInstead;
-               event_qual = rule_lock->qual;
-               actions = rule_lock->actions;
-               if (event_qual != NULL && *instead_flag)
+               else if (rte->rtekind == RTE_SUBQUERY)
                {
-                       Query      *qual_product;
-                       RewriteInfo qual_info;
-
-                       /* ----------
-                        * 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 it's 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_products
-                        * so deepRewriteQuery() can add it to the query
-                        * list after we mangled it up enough.
-                        * ----------
-                        */
-                       if (*qual_products == NIL)
-                               qual_product = parsetree;
-                       else
-                               qual_product = (Query *) nth(0, *qual_products);
-
-                       qual_info.event = qual_product->commandType;
-                       qual_info.new_varno = length(qual_product->rtable) + 2;
-                       qual_product = CopyAndAddQual(qual_product,
-                                                                                 actions,
-                                                                                 event_qual,
-                                                                                 rt_index,
-                                                                                 event);
-
-                       qual_info.rule_action = qual_product;
-
-                       if (event == CMD_INSERT || event == CMD_UPDATE)
-                               FixNew(&qual_info, qual_product);
-
-                       *qual_products = lappend(NIL, qual_product);
+                       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(r, actions)
-               {
-                       Query      *rule_action = lfirst(r);
-                       Node       *rule_qual = copyObject(event_qual);
-
-                       if (rule_action->commandType == CMD_NOTHING)
-                               continue;
-
-                       /*--------------------------------------------------
-                        * We copy the qualifications of the parsetree
-                        * to the action and vice versa. So force
-                        * hasSubLinks if one of them has it.
-                        *
-                        * As of 6.4 only parsetree qualifications can
-                        * have sublinks. If this changes, we must make
-                        * this a node lookup at the end of rewriting.
-                        *
-                        * Jan
-                        *--------------------------------------------------
-                        */
-                       if (parsetree->hasSubLinks && !rule_action->hasSubLinks)
-                       {
-                               rule_action = copyObject(rule_action);
-                               rule_action->hasSubLinks = TRUE;
-                       }
-                       if (!parsetree->hasSubLinks && rule_action->hasSubLinks)
-                               parsetree->hasSubLinks = TRUE;
-
-                       /*--------------------------------------------------
-                        * Step 1:
-                        *        Rewrite current.attribute or current to tuple variable
-                        *        this appears to be done in parser?
-                        *--------------------------------------------------
-                        */
-                       info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
-                                                                        rt_index, event, instead_flag);
-
-                       /* handle escapable cases, or those handled by other code */
-                       if (info->nothing)
-                       {
-                               if (*instead_flag)
-                                       return NIL;
-                               else
-                                       continue;
-                       }
-
-                       if (info->action == info->event &&
-                               info->event == CMD_SELECT)
-                               continue;
-
-                       /*
-                        * 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
-                        */
-                       qual = parsetree->qual;
-                       AddQual(info->rule_action, qual);
-
-                       if (info->rule_qual != NULL)
-                               AddQual(info->rule_action, info->rule_qual);
-
-                       /*--------------------------------------------------
-                        * Step 2:
-                        *        Rewrite new.attribute w/ right hand side of target-list
-                        *        entry for appropriate field name in insert/update
-                        *--------------------------------------------------
-                        */
-                       if ((info->event == CMD_INSERT) || (info->event == CMD_UPDATE))
-                               FixNew(info, parsetree);
-
-                       /*--------------------------------------------------
-                        * Step 3:
-                        *        rewriting due to retrieve rules
-                        *--------------------------------------------------
-                        */
-                       info->rule_action->rtable = info->rt;
-
-                       /*
-                        * ProcessRetrieveQuery(info->rule_action, info->rt,
-                        * &orig_instead_flag, TRUE);
-                        */
-
-                       /*--------------------------------------------------
-                        * Step 4
-                        *        Simplify? hey, no algorithm for simplification... let
-                        *        the planner do it.
-                        *--------------------------------------------------
-                        */
-                       results = lappend(results, info->rule_action);
-
-                       pfree(info);
-               }
+               foreach(l, f->fromlist)
+                       markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown);
+       }
+       else if (IsA(jtnode, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) jtnode;
 
-               /* ----------
-                * If this was an unqualified instead rule,
-                * throw away an eventually saved 'default' parsetree
-                * ----------
-                */
-               if (event_qual == NULL && *instead_flag)
-                       *qual_products = NIL;
+               markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown);
+               markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown);
        }
-       return results;
+       else
+               elog(ERROR, "unrecognized node type: %d",
+                        (int) nodeTag(jtnode));
 }
 
 
-
-static List *
-RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products)
+/*
+ * fireRIRonSubLink -
+ *     Apply fireRIRrules() to each SubLink (subselect in expression) found
+ *     in the given tree.
+ *
+ * NOTE: although this has the form of a walker, we cheat and modify the
+ * SubLink nodes in-place.     It is caller's responsibility to ensure that
+ * no unwanted side-effects occur!
+ *
+ * This is unlike most of the other routines that recurse into subselects,
+ * because we must take control at the SubLink node in order to replace
+ * the SubLink's subselect link with the possibly-rewritten subquery.
+ */
+static bool
+fireRIRonSubLink(Node *node, List *activeRIRs)
 {
-       CmdType         event;
-       List       *product_queries = NIL;
-       int                     result_relation = 0;
-       RangeTblEntry *rt_entry;
-       Relation        rt_entry_relation = NULL;
-       RuleLock   *rt_entry_locks = NULL;
-
-       Assert(parsetree != NULL);
-
-       event = parsetree->commandType;
-
-       /*
-        * SELECT rules are handled later when we have all the queries that
-        * should get executed
-        */
-       if (event == CMD_SELECT)
-               return NIL;
+       if (node == NULL)
+               return false;
+       if (IsA(node, SubLink))
+       {
+               SubLink    *sub = (SubLink *) node;
 
-       /*
-        * Utilities aren't rewritten at all - why is this here?
-        */
-       if (event == CMD_UTILITY)
-               return NIL;
+               /* Do what we came for */
+               sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect,
+                                                                                          activeRIRs, false);
+               /* Fall through to process lefthand args of SubLink */
+       }
 
        /*
-        * the statement is an update, insert or delete - fire rules on it.
+        * Do NOT recurse into Query nodes, because fireRIRrules already processed
+        * subselects of subselects for us.
         */
-       result_relation = parsetree->resultRelation;
-       rt_entry = rt_fetch(result_relation, parsetree->rtable);
-       rt_entry_relation = heap_openr(rt_entry->relname, AccessShareLock);
-       rt_entry_locks = rt_entry_relation->rd_rules;
-       heap_close(rt_entry_relation, AccessShareLock);
-
-       if (rt_entry_locks != NULL)
-       {
-               List       *locks = matchLocks(event, rt_entry_locks, result_relation, parsetree);
-
-               product_queries = fireRules(parsetree,
-                                                                       result_relation,
-                                                                       event,
-                                                                       instead_flag,
-                                                                       locks,
-                                                                       qual_products);
-       }
-
-       return product_queries;
+       return expression_tree_walker(node, fireRIRonSubLink,
+                                                                 (void *) activeRIRs);
 }
 
 
 /*
- * to avoid infinite recursion, we restrict the number of times a query
- * can be rewritten. Detecting cycles is left for the reader as an excercise.
- */
-#ifndef REWRITE_INVOKE_MAX
-#define REWRITE_INVOKE_MAX             10
-#endif
-
-static int     numQueryRewriteInvoked = 0;
-
-/*
- * deepRewriteQuery -
- *       rewrites the query and apply the rules again on the queries rewritten
+ * fireRIRrules -
+ *     Apply all RIR rules on each rangetable entry in a query
  */
-static List *
-deepRewriteQuery(Query *parsetree)
+static Query *
+fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
 {
-       List       *n;
-       List       *rewritten = NIL;
-       List       *result = NIL;
-       bool            instead;
-       List       *qual_products = NIL;
-
-
+       int                     origResultRelation = parsetree->resultRelation;
+       int                     rt_index;
+       ListCell   *lc;
 
-       if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX)
+       /*
+        * 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 < list_length(parsetree->rtable))
        {
-               elog(ERROR, "query rewritten %d times, may contain cycles",
-                        numQueryRewriteInvoked - 1);
-       }
-
-       instead = FALSE;
-       result = RewriteQuery(parsetree, &instead, &qual_products);
+               RangeTblEntry *rte;
+               Relation        rel;
+               List       *locks;
+               RuleLock   *rules;
+               RewriteRule *rule;
+               int                     i;
 
-       foreach(n, result)
-       {
-               Query      *pt = lfirst(n);
-               List       *newstuff = NIL;
+               ++rt_index;
 
-               newstuff = deepRewriteQuery(pt);
-               if (newstuff != NIL)
-                       rewritten = nconc(rewritten, newstuff);
-       }
+               rte = rt_fetch(rt_index, parsetree->rtable);
 
-       /* ----------
-        * qual_products are the original query with the negated
-        * rule qualification of an instead rule
-        * ----------
-        */
-       if (qual_products != NIL)
-               rewritten = nconc(rewritten, qual_products);
-
-       /* ----------
-        * The original query is appended last if not instead
-        * 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 execution makes the deleted (and
-        * maybe the updated) tuples disappear so the scans
-        * for them in the rule actions cannot find them.
-        * ----------
-        */
-       if (!instead)
-               rewritten = lappend(rewritten, parsetree);
+               /*
+                * 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,
+                                                                                (forUpdatePushedDown ||
+                                                       get_parse_rowmark(parsetree, rt_index) != NULL));
+                       continue;
+               }
 
-       return rewritten;
-}
+               /*
+                * Joins and other non-relation RTEs can be ignored completely.
+                */
+               if (rte->rtekind != RTE_RELATION)
+                       continue;
 
+               /*
+                * 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.
+                */
+               if (rt_index != parsetree->resultRelation &&
+                       !rangeTableEntry_used((Node *) parsetree, rt_index, 0))
+                       continue;
 
-/*
- * QueryOneRewrite -
- *       rewrite one query
- */
-static List *
-QueryRewriteOne(Query *parsetree)
-{
-       numQueryRewriteInvoked = 0;
+               /*
+                * 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 &&
+                       rt_index != origResultRelation)
+                       continue;
 
-       /*
-        * take a deep breath and apply all the rewrite rules - ay
-        */
-       return deepRewriteQuery(parsetree);
-}
+               /*
+                * 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
+                */
+               rules = rel->rd_rules;
+               if (rules == NULL)
+               {
+                       heap_close(rel, NoLock);
+                       continue;
+               }
+               locks = NIL;
+               for (i = 0; i < rules->numLocks; i++)
+               {
+                       rule = rules->rules[i];
+                       if (rule->event != CMD_SELECT)
+                               continue;
 
-/* ----------
- * RewritePreprocessQuery -
- *     adjust details in the parsetree, the rule system
- *     depends on
- * ----------
- */
-static void
-RewritePreprocessQuery(Query *parsetree)
-{
-       /* ----------
-        * if the query has a resultRelation, reassign the
-        * result domain numbers to the attribute numbers in the
-        * target relation. FixNew() depends on it when replacing
-        * *new* references in a rule action by the expressions
-        * from the rewritten query.
-        * resjunk targets are somewhat arbitrarily given a resno of 0;
-        * this is to prevent FixNew() from matching them to var nodes.
-        * ----------
-        */
-       if (parsetree->resultRelation > 0)
-       {
-               RangeTblEntry *rte;
-               Relation        rd;
-               List       *tl;
+                       if (rule->attrno > 0)
+                       {
+                               /* per-attr rule; do we need it? */
+                               if (!attribute_used((Node *) parsetree, rt_index,
+                                                                       rule->attrno, 0))
+                                       continue;
+                       }
 
-               rte = (RangeTblEntry *) nth(parsetree->resultRelation - 1,
-                                                                       parsetree->rtable);
-               rd = heap_openr(rte->relname, AccessShareLock);
+                       locks = lappend(locks, rule);
+               }
 
-               foreach(tl, parsetree->targetList)
+               /*
+                * If we found any, apply them --- but first check for recursion!
+                */
+               if (locks != NIL)
                {
-                       TargetEntry *tle = (TargetEntry *) lfirst(tl);
+                       ListCell   *l;
 
-                       if (! tle->resdom->resjunk)
-                               tle->resdom->resno = attnameAttNum(rd, tle->resdom->resname);
-                       else
-                               tle->resdom->resno = 0;
-               }
+                       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 = lcons_oid(RelationGetRelid(rel), activeRIRs);
 
-               heap_close(rd, AccessShareLock);
-       }
-}
+                       foreach(l, locks)
+                       {
+                               rule = lfirst(l);
+
+                               parsetree = ApplyRetrieveRule(parsetree,
+                                                                                         rule,
+                                                                                         rt_index,
+                                                                                         rule->attrno == -1,
+                                                                                         rel,
+                                                                                         activeRIRs,
+                                                                                         forUpdatePushedDown);
+                       }
 
+                       activeRIRs = list_delete_first(activeRIRs);
+               }
 
-/*
- * BasicQueryRewrite -
- *       rewrite one query via query rewrite system, possibly returning 0
- *       or many queries
- */
-static List *
-BasicQueryRewrite(Query *parsetree)
-{
-       List       *querylist;
-       List       *results = NIL;
-       List       *l;
-       Query      *query;
+               heap_close(rel, NoLock);
+       }
 
-       /*
-        * Step 1
-        *
-        * There still seems something broken with the resdom numbers so we
-        * reassign them first.
-        */
-       RewritePreprocessQuery(parsetree);
+       /* Recurse into subqueries in WITH */
+       foreach(lc, parsetree->cteList)
+       {
+               CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
 
-       /*
-        * Step 2
-        *
-        * Apply all non-SELECT rules possibly getting 0 or many queries
-        */
-       querylist = QueryRewriteOne(parsetree);
+               cte->ctequery = (Node *)
+                       fireRIRrules((Query *) cte->ctequery, activeRIRs, false);
+       }
 
        /*
-        * Step 3
-        *
-        * Apply all the RIR rules on each query
+        * Recurse into sublink subqueries, too.  But we already did the ones in
+        * the rtable and cteList.
         */
-       foreach(l, querylist)
-       {
-               query = fireRIRrules((Query *) lfirst(l));
+       if (parsetree->hasSubLinks)
+               query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
+                                                 QTW_IGNORE_RC_SUBQUERIES);
 
-               /*
-                * If the query was marked having aggregates, check if this is
-                * still true after rewriting. This check must get expanded when
-                * someday aggregates can appear somewhere else than in the
-                * targetlist or the having qual.
-                */
-               if (query->hasAggs)
-                       query->hasAggs = checkQueryHasAggs((Node *) (query->targetList))
-                               || checkQueryHasAggs((Node *) (query->havingQual));
-               query->hasSubLinks = checkQueryHasSubLink((Node *) (query->qual))
-                       || checkQueryHasSubLink((Node *) (query->havingQual));
-               results = lappend(results, query);
-       }
-       return results;
+       return parsetree;
 }
 
+
 /*
- * QueryRewrite -
- *       Primary entry point to the query rewriter.
- *       Rewrite one query via query rewrite system, possibly returning 0
- *       or many queries.
+ * Modify the given query by adding 'AND rule_qual IS NOT TRUE' to its
+ * qualification.  This is used to generate suitable "else clauses" for
+ * conditional INSTEAD rules.  (Unfortunately we must use "x IS NOT TRUE",
+ * not just "NOT x" which the planner is much smarter about, else we will
+ * do the wrong thing when the qual evaluates to NULL.)
  *
- * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was
- * moved here so that it would be invoked during EXPLAIN.  The division of
- * labor between this routine and BasicQueryRewrite is not obviously correct
- * ... at least not to me ... tgl 5/99.
+ * The rule_qual may contain references to OLD or NEW. OLD references are
+ * replaced by references to the specified rt_index (the relation that the
+ * rule applies to).  NEW references are only possible for INSERT and UPDATE
+ * queries on the relation itself, and so they should be replaced by copies
+ * of the related entries in the query's own targetlist.
  */
-List *
-QueryRewrite(Query *parsetree)
+static Query *
+CopyAndAddInvertedQual(Query *parsetree,
+                                          Node *rule_qual,
+                                          int rt_index,
+                                          CmdType event)
 {
-       List       *rewritten,
-                          *rewritten_item;
+       /* Don't scribble on the passed qual (it's in the relcache!) */
+       Node       *new_qual = (Node *) copyObject(rule_qual);
 
        /*
-        * Rewrite Union, Intersect and Except Queries to normal Union Queries
-        * using IN and NOT IN subselects
+        * 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.)
         */
-       if (parsetree->intersectClause)
-               parsetree = Except_Intersect_Rewrite(parsetree);
+       (void) acquireLocksOnSubLinks(new_qual, NULL);
+
+       /* Fix references to OLD */
+       ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
+       /* Fix references to NEW */
+       if (event == CMD_INSERT || event == CMD_UPDATE)
+               new_qual = ResolveNew(new_qual,
+                                                         PRS2_NEW_VARNO,
+                                                         0,
+                                                         rt_fetch(rt_index, parsetree->rtable),
+                                                         parsetree->targetList,
+                                                         event,
+                                                         rt_index,
+                                                         &parsetree->hasSubLinks);
+       /* And attach the fixed qual */
+       AddInvertedQual(parsetree, new_qual);
 
-       /* Rewrite basic queries (retrieve, append, delete, replace) */
-       rewritten = BasicQueryRewrite(parsetree);
-
-       /*
-        * Rewrite the UNIONS.
-        */
-       foreach(rewritten_item, rewritten)
-       {
-               Query      *qry = (Query *) lfirst(rewritten_item);
-               List       *union_result = NIL;
-               List       *union_item;
-
-               foreach(union_item, qry->unionClause)
-               {
-                       union_result = nconc(union_result,
-                                               BasicQueryRewrite((Query *) lfirst(union_item)));
-               }
-               qry->unionClause = union_result;
-       }
-
-       return rewritten;
+       return parsetree;
 }
 
-/* This function takes two targetlists as arguments and checks if the
- * targetlists are compatible (i.e. both select for the same number of
- * attributes and the types are compatible */
-static void
-check_targetlists_are_compatible(List *prev_target, List *current_target)
-{
-       List       *tl,
-                          *next_target;
-       int                     prev_len = 0,
-                               next_len = 0;
-
-       foreach(tl, prev_target)
-               if (!((TargetEntry *) lfirst(tl))->resdom->resjunk)
-               prev_len++;
 
-       foreach(next_target, current_target)
-               if (!((TargetEntry *) lfirst(next_target))->resdom->resjunk)
-               next_len++;
-
-       if (prev_len != next_len)
-               elog(ERROR, "Each UNION | EXCEPT | INTERSECT query must have the same number of columns.");
+/*
+ *     fireRules -
+ *        Iterate through rule locks applying rules.
+ *
+ * Input arguments:
+ *     parsetree - original query
+ *     rt_index - RT index of result relation in original query
+ *     event - type of rule event
+ *     locks - list of rules to fire
+ * 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:
+ *     list of rule actions adjusted for use with this query
+ *
+ * Qualified INSTEAD rules generate their action with the qualification
+ * condition added.  They also generate a modified version of the original
+ * query with the negated qualification added, so that it will run only for
+ * rows that the qualified action doesn't act on.  (If there are multiple
+ * qualified INSTEAD rules, we AND all the negated quals onto a single
+ * modified original query.)  We won't execute the original, unmodified
+ * query if we find either qualified or unqualified INSTEAD rules.     If
+ * we find both, the modified original query is discarded too.
+ */
+static List *
+fireRules(Query *parsetree,
+                 int rt_index,
+                 CmdType event,
+                 List *locks,
+                 bool *instead_flag,
+                 bool *returning_flag,
+                 Query **qual_product)
+{
+       List       *results = NIL;
+       ListCell   *l;
 
-       foreach(next_target, current_target)
+       foreach(l, locks)
        {
-               Oid                     itype;
-               Oid                     otype;
-
-               otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype;
-               itype = ((TargetEntry *) lfirst(next_target))->resdom->restype;
+               RewriteRule *rule_lock = (RewriteRule *) lfirst(l);
+               Node       *event_qual = rule_lock->qual;
+               List       *actions = rule_lock->actions;
+               QuerySource qsrc;
+               ListCell   *r;
 
-               /* one or both is a NULL column? then don't convert... */
-               if (otype == InvalidOid)
+               /* Determine correct QuerySource value for actions */
+               if (rule_lock->isInstead)
                {
-                       /* propagate a known type forward, if available */
-                       if (itype != InvalidOid)
-                               ((TargetEntry *) lfirst(prev_target))->resdom->restype = itype;
-#ifdef NOT_USED
+                       if (event_qual != NULL)
+                               qsrc = QSRC_QUAL_INSTEAD_RULE;
                        else
                        {
-                               ((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID;
-                               ((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID;
+                               qsrc = QSRC_INSTEAD_RULE;
+                               *instead_flag = true;   /* report unqualified INSTEAD */
                        }
-#endif
-               }
-               else if (itype == InvalidOid)
-               {
                }
-               /* they don't match in type? then convert... */
-               else if (itype != otype)
-               {
-                       Node       *expr;
+               else
+                       qsrc = QSRC_NON_INSTEAD_RULE;
 
-                       expr = ((TargetEntry *) lfirst(next_target))->expr;
-                       expr = CoerceTargetExpr(NULL, expr, itype, otype);
-                       if (expr == NULL)
+               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 we have already found an unqualified INSTEAD rule, then
+                        * *qual_product won't be used, so don't bother building it.
+                        */
+                       if (!*instead_flag)
                        {
-                               elog(ERROR, "Unable to transform %s to %s"
-                                        "\n\tEach UNION | EXCEPT | INTERSECT clause must have compatible target types",
-                                        typeidTypeName(itype),
-                                        typeidTypeName(otype));
+                               if (*qual_product == NULL)
+                                       *qual_product = copyObject(parsetree);
+                               *qual_product = CopyAndAddInvertedQual(*qual_product,
+                                                                                                          event_qual,
+                                                                                                          rt_index,
+                                                                                                          event);
                        }
-                       ((TargetEntry *) lfirst(next_target))->expr = expr;
-                       ((TargetEntry *) lfirst(next_target))->resdom->restype = otype;
                }
 
-               /* both are UNKNOWN? then evaluate as text... */
-               else if (itype == UNKNOWNOID)
+               /* Now process the rule's actions and add them to the result list */
+               foreach(r, actions)
                {
-                       ((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID;
-                       ((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID;
-               }
-               prev_target = lnext(prev_target);
-       }
-}
-
-/* Rewrites UNION INTERSECT and EXCEPT queries to semantiacally equivalent
- * queries that use IN and NOT IN subselects.
- *
- * The operator tree is attached to 'intersectClause' (see rule
- * 'SelectStmt' in gram.y) of the 'parsetree' given as an
- * argument. First we remember some clauses (the sortClause, the
- * unique flag etc.)  Then we translate the operator tree to DNF
- * (disjunctive normal form) by 'cnfify'. (Note that 'cnfify' produces
- * CNF but as we exchanged ANDs with ORs in function A_Expr_to_Expr()
- * earlier we get DNF after exchanging ANDs and ORs again in the
- * result.) Now we create a new query by evaluating the new operator
- * tree which is in DNF now. For every AND we create an entry in the
- * union list and for every OR we create an IN subselect. (NOT IN
- * subselects are created for OR NOT nodes). The first entry of the
- * union list is handed back but before that the remembered clauses
- * (sortClause etc) are attached to the new top Node (Note that the
- * new top Node can differ from the parsetree given as argument because of
- * the translation to DNF. That's why we have to remember the sortClause or
- * unique flag!) */
-static Query *
-Except_Intersect_Rewrite(Query *parsetree)
-{
-
-       SubLink    *n;
-       Query      *result,
-                          *intersect_node;
-       List       *elist,
-                          *intersect_list = NIL,
-                          *intersect,
-                          *intersectClause;
-       List       *union_list = NIL,
-                          *sortClause;
-       List       *left_expr,
-                          *right_expr,
-                          *resnames = NIL;
-       char       *op,
-                          *uniqueFlag,
-                          *into;
-       bool            isBinary,
-                               isPortal,
-                               isTemp;
-       CmdType         commandType = CMD_SELECT;
-       List       *rtable_insert = NIL;
-
-       List       *prev_target = NIL;
+                       Query      *rule_action = lfirst(r);
 
-       /*
-        * Remember the Resnames of the given parsetree's targetlist (these
-        * are the resnames of the first Select Statement of the query
-        * formulated by the user and he wants the columns named by these
-        * strings. The transformation to DNF can cause another Select
-        * Statment to be the top one which uses other names for its columns.
-        * Therefore we remeber the original names and attach them to the
-        * targetlist of the new topmost Node at the end of this function
-        */
-       foreach(elist, parsetree->targetList)
-       {
-               TargetEntry *tent = (TargetEntry *) lfirst(elist);
+                       if (rule_action->commandType == CMD_NOTHING)
+                               continue;
 
-               resnames = lappend(resnames, tent->resdom->resname);
-       }
+                       rule_action = rewriteRuleAction(parsetree, rule_action,
+                                                                                       event_qual, rt_index, event,
+                                                                                       returning_flag);
 
-       /*
-        * If the Statement is an INSERT INTO ... (SELECT...) statement using
-        * UNIONs, INTERSECTs or EXCEPTs and the transformation to DNF makes
-        * another Node to the top node we have to transform the new top node
-        * to an INSERT node and the original INSERT node to a SELECT node
-        */
-       if (parsetree->commandType == CMD_INSERT)
-       {
-               parsetree->commandType = CMD_SELECT;
-               commandType = CMD_INSERT;
-               parsetree->resultRelation = 0;
+                       rule_action->querySource = qsrc;
+                       rule_action->canSetTag = false;         /* might change later */
 
-               /*
-                * The result relation ( = the one to insert into) has to be
-                * attached to the rtable list of the new top node
-                */
-               rtable_insert = nth(length(parsetree->rtable) - 1, parsetree->rtable);
+                       results = lappend(results, rule_action);
+               }
        }
 
-       /*
-        * Save some items, to be able to attach them to the resulting top
-        * node at the end of the function
-        */
-       sortClause = parsetree->sortClause;
-       uniqueFlag = parsetree->uniqueFlag;
-       into = parsetree->into;
-       isBinary = parsetree->isBinary;
-       isPortal = parsetree->isPortal;
-       isTemp = parsetree->isTemp;
-
-       /*
-        * The operator tree attached to parsetree->intersectClause is still
-        * 'raw' ( = the leaf nodes are still SelectStmt nodes instead of
-        * Query nodes) So step through the tree and transform the nodes using
-        * parse_analyze().
-        *
-        * The parsetree (given as an argument to Except_Intersect_Rewrite()) has
-        * already been transformed and transforming it again would cause
-        * troubles.  So we give the 'raw' version (of the cooked parsetree)
-        * to the function to prevent an additional transformation. Instead we
-        * hand back the 'cooked' version also given as an argument to
-        * intersect_tree_analyze()
-        */
-       intersectClause =
-               (List *) intersect_tree_analyze((Node *) parsetree->intersectClause,
-                                                                (Node *) lfirst(parsetree->unionClause),
-                                                                               (Node *) parsetree);
+       return results;
+}
 
-       /* intersectClause is no longer needed so set it to NIL */
-       parsetree->intersectClause = NIL;
 
-       /*
-        * unionClause will be needed later on but the list it delivered is no
-        * longer needed, so set it to NIL
-        */
-       parsetree->unionClause = NIL;
+/*
+ * RewriteQuery -
+ *       rewrites the query and apply the rules again on the queries rewritten
+ *
+ * rewrite_events is a list of open query-rewrite actions, so we can detect
+ * infinite recursion.
+ */
+static List *
+RewriteQuery(Query *parsetree, List *rewrite_events)
+{
+       CmdType         event = parsetree->commandType;
+       bool            instead = false;
+       bool            returning = false;
+       Query      *qual_product = NULL;
+       List       *rewritten = NIL;
 
        /*
-        * Transform the operator tree to DNF (remember ANDs and ORs have been
-        * exchanged, that's why we get DNF by using cnfify)
+        * If the statement is an insert, update, or delete, adjust its targetlist
+        * as needed, and then fire INSERT/UPDATE/DELETE rules on it.
         *
-        * After the call, explicit ANDs are removed and all AND operands are
-        * simply items in the intersectClause list
+        * 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?)
         */
-       intersectClause = cnfify((Expr *) intersectClause, true);
-
-       /*
-        * For every entry of the intersectClause list we generate one entry
-        * in the union_list
-        */
-       foreach(intersect, intersectClause)
+       if (event != CMD_SELECT && event != CMD_UTILITY)
        {
+               int                     result_relation;
+               RangeTblEntry *rt_entry;
+               Relation        rt_entry_relation;
+               List       *locks;
 
-               /*
-                * for every OR we create an IN subselect and for every OR NOT we
-                * create a NOT IN subselect, so first extract all the Select
-                * Query nodes from the tree (that contains only OR or OR NOTs any
-                * more because we did a transformation to DNF
-                *
-                * There must be at least one node that is not negated (i.e. just OR
-                * and not OR NOT) and this node will be the first in the list
-                * returned
-                */
-               intersect_list = NIL;
-               create_intersect_list((Node *) lfirst(intersect), &intersect_list);
-
-               /*
-                * This one will become the Select Query node, all other nodes are
-                * transformed into subselects under this node!
-                */
-               intersect_node = (Query *) lfirst(intersect_list);
-               intersect_list = lnext(intersect_list);
+               result_relation = parsetree->resultRelation;
+               Assert(result_relation != 0);
+               rt_entry = rt_fetch(result_relation, parsetree->rtable);
+               Assert(rt_entry->rtekind == RTE_RELATION);
 
                /*
-                * Check if all Select Statements use the same number of
-                * attributes and if all corresponding attributes are of the same
-                * type
+                * We can use NoLock here since either the parser or
+                * AcquireRewriteLocks should have locked the rel already.
                 */
-               if (prev_target)
-                       check_targetlists_are_compatible(prev_target, intersect_node->targetList);
-               prev_target = intersect_node->targetList;
-               /* End of check for corresponding targetlists */
+               rt_entry_relation = heap_open(rt_entry->relid, NoLock);
 
                /*
-                * Transform all nodes remaining into subselects and add them to
-                * the qualifications of the Select Query node
+                * Rewrite the targetlist as needed for the command type.
                 */
-               while (intersect_list != NIL)
+               if (event == CMD_INSERT)
                {
+                       RangeTblEntry *values_rte = NULL;
 
-                       n = makeNode(SubLink);
-
-                       /* Here we got an OR so transform it to an IN subselect */
-                       if (IsA(lfirst(intersect_list), Query))
+                       /*
+                        * 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);
 
-                               /*
-                                * Check if all Select Statements use the same number of
-                                * attributes and if all corresponding attributes are of
-                                * the same type
-                                */
-                               check_targetlists_are_compatible(prev_target,
-                                                ((Query *) lfirst(intersect_list))->targetList);
-                               /* End of check for corresponding targetlists */
-
-                               n->subselect = lfirst(intersect_list);
-                               op = "=";
-                               n->subLinkType = ANY_SUBLINK;
-                               n->useor = false;
+                               if (IsA(rtr, RangeTblRef))
+                               {
+                                       RangeTblEntry *rte = rt_fetch(rtr->rtindex,
+                                                                                                 parsetree->rtable);
+
+                                       if (rte->rtekind == RTE_VALUES)
+                                               values_rte = rte;
+                               }
                        }
 
-                       /*
-                        * Here we got an OR NOT node so transform it to a NOT IN
-                        * subselect
-                        */
-                       else
+                       if (values_rte)
                        {
+                               List       *attrnos;
 
-                               /*
-                                * Check if all Select Statements use the same number of
-                                * attributes and if all corresponding attributes are of
-                                * the same type
-                                */
-                               check_targetlists_are_compatible(prev_target,
-                                                                                                ((Query *) lfirst(((Expr *) lfirst(intersect_list))->args))->targetList);
-                               /* End of check for corresponding targetlists */
-
-                               n->subselect = (Node *) lfirst(((Expr *) lfirst(intersect_list))->args);
-                               op = "<>";
-                               n->subLinkType = ALL_SUBLINK;
-                               n->useor = true;
+                               /* 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.
+                */
+               locks = matchLocks(event, rt_entry_relation->rd_rules,
+                                                  result_relation, parsetree);
+
+               if (locks != NIL)
+               {
+                       List       *product_queries;
+
+                       product_queries = fireRules(parsetree,
+                                                                               result_relation,
+                                                                               event,
+                                                                               locks,
+                                                                               &instead,
+                                                                               &returning,
+                                                                               &qual_product);
 
                        /*
-                        * Prepare the lefthand side of the Sublinks: All the entries
-                        * of the targetlist must be (IN) or must not be (NOT IN) the
-                        * subselect
+                        * If we got any product queries, recursively rewrite them --- but
+                        * first check for recursion!
                         */
-                       n->lefthand = NIL;
-                       foreach(elist, intersect_node->targetList)
+                       if (product_queries != NIL)
                        {
-                               TargetEntry *tent = (TargetEntry *) lfirst(elist);
+                               ListCell   *n;
+                               rewrite_event *rev;
 
-                               n->lefthand = lappend(n->lefthand, tent->expr);
-                       }
+                               foreach(n, rewrite_events)
+                               {
+                                       rev = (rewrite_event *) lfirst(n);
+                                       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))));
+                               }
 
-                       /*
-                        * Also prepare the list of Opers that must be used for the
-                        * comparisons (they depend on the specific datatypes involved!)
-                        */
-                       left_expr = n->lefthand;
-                       right_expr = ((Query *) (n->subselect))->targetList;
-                       n->oper = NIL;
+                               rev = (rewrite_event *) palloc(sizeof(rewrite_event));
+                               rev->relation = RelationGetRelid(rt_entry_relation);
+                               rev->event = event;
+                               rewrite_events = lcons(rev, rewrite_events);
 
-                       foreach(elist, left_expr)
-                       {
-                               Node       *lexpr = lfirst(elist);
-                               TargetEntry *tent = (TargetEntry *) lfirst(right_expr);
-                               Operator        optup;
-                               Form_pg_operator opform;
-                               Oper       *newop;
-
-                               optup = oper(op,
-                                                        exprType(lexpr),
-                                                        exprType(tent->expr),
-                                                        FALSE);
-                               opform = (Form_pg_operator) GETSTRUCT(optup);
-
-                               if (opform->oprresult != BOOLOID)
-                                       elog(ERROR, "parser: '%s' must return 'bool' to be used with quantified predicate subquery", op);
-
-                               newop = makeOper(oprid(optup),/* opno */
-                                                                InvalidOid, /* opid */
-                                                                opform->oprresult,
-                                                                0,
-                                                                NULL);
-
-                               n->oper = lappend(n->oper, newop);
-
-                               right_expr = lnext(right_expr);
-                       }
+                               foreach(n, product_queries)
+                               {
+                                       Query      *pt = (Query *) lfirst(n);
+                                       List       *newstuff;
 
-                       /*
-                        * If the Select Query node has aggregates in use add all the
-                        * subselects to the HAVING qual else to the WHERE qual
-                        */
-                       if (intersect_node->hasAggs)
-                               AddHavingQual(intersect_node, (Node *) n);
-                       else
-                               AddQual(intersect_node, (Node *) n);
+                                       newstuff = RewriteQuery(pt, rewrite_events);
+                                       rewritten = list_concat(rewritten, newstuff);
+                               }
 
-                       /* Now we got sublinks */
-                       intersect_node->hasSubLinks = true;
-                       intersect_list = lnext(intersect_list);
+                               rewrite_events = list_delete_first(rewrite_events);
+                       }
                }
-               intersect_node->intersectClause = NIL;
-               union_list = lappend(union_list, intersect_node);
-       }
 
-       /* The first entry to union_list is our new top node */
-       result = (Query *) lfirst(union_list);
-       /* attach the rest to unionClause */
-       result->unionClause = lnext(union_list);
-       /* Attach all the items remembered in the beginning of the function */
-       result->sortClause = sortClause;
-       result->uniqueFlag = uniqueFlag;
-       result->into = into;
-       result->isPortal = isPortal;
-       result->isBinary = isBinary;
-       result->isTemp = isTemp;
+               /*
+                * 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;
+                       }
+               }
 
-       /*
-        * The relation to insert into is attached to the range table of the
-        * new top node
-        */
-       if (commandType == CMD_INSERT)
-       {
-               result->rtable = lappend(result->rtable, rtable_insert);
-               result->resultRelation = length(result->rtable);
-               result->commandType = commandType;
+               heap_close(rt_entry_relation, NoLock);
        }
 
        /*
-        * The resnames of the originally first SelectStatement are attached
-        * to the new first SelectStatement
+        * 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
+        * INSTEADs were found, else the unmodified form.
         */
-       foreach(elist, result->targetList)
+       if (!instead)
        {
-               TargetEntry *tent = (TargetEntry *) lfirst(elist);
-
-               tent->resdom->resname = lfirst(resnames);
-               resnames = lnext(resnames);
+               if (parsetree->commandType == CMD_INSERT)
+               {
+                       if (qual_product != NULL)
+                               rewritten = lcons(qual_product, rewritten);
+                       else
+                               rewritten = lcons(parsetree, rewritten);
+               }
+               else
+               {
+                       if (qual_product != NULL)
+                               rewritten = lappend(rewritten, qual_product);
+                       else
+                               rewritten = lappend(rewritten, parsetree);
+               }
        }
-       return result;
+
+       return rewritten;
 }
 
-/* Create a list of nodes that are either Query nodes of NOT Expr
- * nodes followed by a Query node. The tree given in ptr contains at
- * least one non negated Query node. This node is attached to the
- * beginning of the list */
 
-static void
-create_intersect_list(Node *ptr, List **intersect_list)
+/*
+ * QueryRewrite -
+ *       Primary entry point to the query rewriter.
+ *       Rewrite one query via query rewrite system, possibly returning 0
+ *       or many queries.
+ *
+ * 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       *arg;
+       List       *querylist;
+       List       *results;
+       ListCell   *l;
+       CmdType         origCmdType;
+       bool            foundOriginalQuery;
+       Query      *lastInstead;
 
-       if (IsA(ptr, Query))
-       {
-               /* The non negated node is attached at the beginning (lcons) */
-               *intersect_list = lcons(ptr, *intersect_list);
-               return;
-       }
+       /*
+        * Step 1
+        *
+        * Apply all non-SELECT rules possibly getting 0 or many queries
+        */
+       querylist = RewriteQuery(parsetree, NIL);
 
-       if (IsA(ptr, Expr))
+       /*
+        * Step 2
+        *
+        * Apply all the RIR rules on each query
+        */
+       results = NIL;
+       foreach(l, querylist)
        {
-               if (((Expr *) ptr)->opType == NOT_EXPR)
-               {
-                       /* negated nodes are appended to the end (lappend) */
-                       *intersect_list = lappend(*intersect_list, ptr);
-                       return;
-               }
-               else
-               {
-                       foreach(arg, ((Expr *) ptr)->args)
-                               create_intersect_list(lfirst(arg), intersect_list);
-                       return;
-               }
-               return;
+               Query      *query = (Query *) lfirst(l);
+
+               query = fireRIRrules(query, NIL, false);
+               results = lappend(results, query);
        }
-}
 
-/* The nodes given in 'tree' are still 'raw' so 'cook' them using parse_analyze().
- * The node given in first_select has already been cooked, so don't transform
- * it again but return a pointer to the previously cooked version given in 'parsetree'
- * instead. */
-static Node *
-intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree)
-{
-       Node       *result = (Node *) NIL;
-       List       *arg;
+       /*
+        * Step 3
+        *
+        * Determine which, if any, of the resulting queries is supposed to set
+        * 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.)
+        *
+        * 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.
+        */
+       origCmdType = parsetree->commandType;
+       foundOriginalQuery = false;
+       lastInstead = NULL;
 
-       if (IsA(tree, SelectStmt))
+       foreach(l, results)
        {
+               Query      *query = (Query *) lfirst(l);
 
-               /*
-                * If we get to the tree given in first_select return parsetree
-                * instead of performing parse_analyze()
-                */
-               if (tree == first_select)
-                       result = parsetree;
+               if (query->querySource == QSRC_ORIGINAL)
+               {
+                       Assert(query->canSetTag);
+                       Assert(!foundOriginalQuery);
+                       foundOriginalQuery = true;
+#ifndef USE_ASSERT_CHECKING
+                       break;
+#endif
+               }
                else
                {
-                       /* transform the 'raw' nodes to 'cooked' Query nodes */
-                       List       *qtree = parse_analyze(lcons(tree, NIL), NULL);
-
-                       result = (Node *) lfirst(qtree);
+                       Assert(!query->canSetTag);
+                       if (query->commandType == origCmdType &&
+                               (query->querySource == QSRC_INSTEAD_RULE ||
+                                query->querySource == QSRC_QUAL_INSTEAD_RULE))
+                               lastInstead = query;
                }
        }
 
-       if (IsA(tree, Expr))
-       {
-               /* Call recursively for every argument of the node */
-               foreach(arg, ((Expr *) tree)->args)
-                       lfirst(arg) = intersect_tree_analyze(lfirst(arg), first_select, parsetree);
-               result = tree;
-       }
-       return result;
+       if (!foundOriginalQuery && lastInstead != NULL)
+               lastInstead->canSetTag = true;
+
+       return results;
 }