]> granicus.if.org Git - postgresql/blobdiff - src/backend/rewrite/rewriteHandler.c
Code review for UPDATE tab SET col = DEFAULT patch ... whack it around
[postgresql] / src / backend / rewrite / rewriteHandler.c
index 08d93502497e0ed12c5ee3e0b5170d9932b2ea8a..a1d9122f828e9146974fa1571ed77e3dbc78078a 100644 (file)
@@ -3,11 +3,11 @@
  * rewriteHandler.c
  *             Primary module of query rewriter.
  *
- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2002, 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.101 2002/04/05 05:47:05 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.122 2003/07/03 16:34:25 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,7 +25,6 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_oper.h"
-#include "parser/parse_target.h"
 #include "parser/parse_type.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "utils/lsyscache.h"
 
 
+/* 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 Query *rewriteRuleAction(Query *parsetree,
                                  Query *rule_action,
                                  Node *rule_qual,
@@ -42,12 +47,11 @@ static Query *rewriteRuleAction(Query *parsetree,
 static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
 static void rewriteTargetList(Query *parsetree, Relation target_relation);
 static TargetEntry *process_matched_tle(TargetEntry *src_tle,
-                                                                               TargetEntry *prior_tle);
-static Node *build_column_default(Relation rel, int attrno);
+                                       TargetEntry *prior_tle);
 static void markQueryForUpdate(Query *qry, bool skipOldNew);
 static List *matchLocks(CmdType event, RuleLock *rulelocks,
                   int varno, Query *parsetree);
-static Query *fireRIRrules(Query *parsetree);
+static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
 
 
 /*
@@ -64,9 +68,11 @@ rewriteRuleAction(Query *parsetree,
 {
        int                     current_varno,
                                new_varno;
+       List       *main_rtable;
        int                     rt_length;
        Query      *sub_action;
        Query     **sub_action_ptr;
+       List       *rt;
 
        /*
         * Make modifiable copies of rule action and qual (what we're passed
@@ -101,16 +107,31 @@ rewriteRuleAction(Query *parsetree,
         * Generate expanded rtable consisting of main parsetree's rtable plus
         * rule action's rtable; this becomes the complete rtable for the rule
         * action.      Some of the entries may be unused after we finish
-        * rewriting, but if we tried to clean those out we'd have a much
+        * rewriting, but if we tried to remove them we'd have a much
         * harder job to adjust RT indexes in the query's Vars.  It's OK to
         * have unused RT entries, since planner will ignore them.
         *
         * 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.
+        *
+        * Also, we must disable write-access checking in all the RT entries
+        * copied from the main query.  This is safe since in fact the rule action
+        * won't write on them, and it's necessary because the rule action may
+        * have a different commandType than the main query, causing
+        * ExecCheckRTEPerms() to make an inappropriate check.  The read-access
+        * checks can be left enabled, although they're probably redundant.
         */
-       sub_action->rtable = nconc((List *) copyObject(parsetree->rtable),
-                                                          sub_action->rtable);
+       main_rtable = (List *) copyObject(parsetree->rtable);
+
+       foreach(rt, main_rtable)
+       {
+               RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+
+               rte->checkForWrite = false;
+       }
+
+       sub_action->rtable = nconc(main_rtable, sub_action->rtable);
 
        /*
         * Each rule action's jointree should be the main parsetree's jointree
@@ -207,9 +228,11 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
                {
                        RangeTblRef *rtr = lfirst(jjt);
 
-                       if (IsA(rtr, RangeTblRef) &&rtr->rtindex == rt_index)
+                       if (IsA(rtr, RangeTblRef) &&
+                               rtr->rtindex == rt_index)
                        {
                                newjointree = lremove(rtr, newjointree);
+                               /* foreach is safe because we exit loop after lremove... */
                                break;
                        }
                }
@@ -227,7 +250,9 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
  * 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.)
+ * down rewriter processing with extra tlist nodes.)  Also, for both INSERT
+ * and UPDATE, replace explicit DEFAULT specifications with column default
+ * expressions.
  *
  * 2. Merge multiple entries for the same target attribute, or declare error
  * if we can't.  Presently, multiple entries are only allowed for UPDATE of
@@ -240,7 +265,7 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
  * then junk fields (these in no particular order).
  *
  * We must do items 1 and 2 before firing rewrite rules, else rewritten
- * references to NEW.foo will produce wrong or incomplete results.  Item 3
+ * references to NEW.foo will produce wrong or incomplete results.     Item 3
  * is not needed for rewriting, but will be needed by the planner, and we
  * can do it essentially for free while handling items 1 and 2.
  */
@@ -262,11 +287,15 @@ rewriteTargetList(Query *parsetree, Relation target_relation)
 
        for (attrno = 1; attrno <= numattrs; attrno++)
        {
-               Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno-1];
+               Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1];
                TargetEntry *new_tle = NULL;
 
+               /* We can ignore deleted attributes */
+               if (att_tup->attisdropped)
+                       continue;
+
                /*
-                * Look for targetlist entries matching this attr.  We match by
+                * Look for targetlist entries matching this attr.      We match by
                 * resno, but the resname should match too.
                 *
                 * Junk attributes are not candidates to be matched.
@@ -285,24 +314,51 @@ rewriteTargetList(Query *parsetree, Relation target_relation)
                        }
                }
 
-               if (new_tle == NULL && commandType == CMD_INSERT)
+               /*
+                * 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)))
                {
-                       /*
-                        * Didn't find a matching tlist entry; if it's an INSERT,
-                        * look for a default value, and add a tlist entry computing
-                        * the default if we find one.
-                        */
                        Node       *new_expr;
 
                        new_expr = build_column_default(target_relation, attrno);
 
+                       /*
+                        * If there is no default (ie, default is effectively NULL),
+                        * we can omit the tlist entry in the INSERT case, since the
+                        * planner can insert a NULL for itself, and there's no point
+                        * in spending any more rewriter cycles on the entry.  But in the
+                        * UPDATE case we've got to explicitly set the column to NULL.
+                        */
+                       if (!new_expr)
+                       {
+                               if (commandType == CMD_INSERT)
+                                       new_tle = NULL;
+                               else
+                               {
+                                       new_expr = (Node *) makeConst(att_tup->atttypid,
+                                                                                                 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,
+                                                                                               att_tup->atttypid,
+                                                                                               COERCE_IMPLICIT_CAST);
+                               }
+                       }
+
                        if (new_expr)
                                new_tle = makeTargetEntry(makeResdom(attrno,
                                                                                                         att_tup->atttypid,
                                                                                                         att_tup->atttypmod,
-                                                                                                        pstrdup(NameStr(att_tup->attname)),
+                                                                         pstrdup(NameStr(att_tup->attname)),
                                                                                                         false),
-                                                                                 new_expr);
+                                                                                 (Expr *) new_expr);
                }
 
                if (new_tle)
@@ -378,8 +434,8 @@ process_matched_tle(TargetEntry *src_tle,
                ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL ||
                prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) ||
                ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL ||
-               ((ArrayRef *) src_tle->expr)->refelemtype !=
-               ((ArrayRef *) prior_tle->expr)->refelemtype)
+               ((ArrayRef *) src_tle->expr)->refrestype !=
+               ((ArrayRef *) prior_tle->expr)->refrestype)
                elog(ERROR, "Multiple assignments to same attribute \"%s\"",
                         resdom->resname);
 
@@ -387,10 +443,10 @@ process_matched_tle(TargetEntry *src_tle,
         * Prior TLE could be a nest of ArrayRefs if we do this more than
         * once.
         */
-       priorbottom = ((ArrayRef *) prior_tle->expr)->refexpr;
+       priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr;
        while (priorbottom != NULL && IsA(priorbottom, ArrayRef) &&
                   ((ArrayRef *) priorbottom)->refassgnexpr != NULL)
-               priorbottom = ((ArrayRef *) priorbottom)->refexpr;
+               priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr;
        if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr))
                elog(ERROR, "Multiple assignments to same attribute \"%s\"",
                         resdom->resname);
@@ -402,7 +458,7 @@ process_matched_tle(TargetEntry *src_tle,
        memcpy(newexpr, src_tle->expr, sizeof(ArrayRef));
        newexpr->refexpr = prior_tle->expr;
 
-       return makeTargetEntry(resdom, (Node *) newexpr);
+       return makeTargetEntry(resdom, (Expr *) newexpr);
 }
 
 
@@ -411,7 +467,7 @@ process_matched_tle(TargetEntry *src_tle,
  *
  * If there is no default, return a NULL instead.
  */
-static Node *
+Node *
 build_column_default(Relation rel, int attrno)
 {
        TupleDesc       rd_att = rel->rd_att;
@@ -445,57 +501,48 @@ build_column_default(Relation rel, int attrno)
        if (expr == NULL)
        {
                /*
-                * No per-column default, so look for a default for the type itself.
+                * No per-column default, so look for a default for the type
+                * itself.
                 */
                if (att_tup->attisset)
                {
                        /*
-                        * Set attributes are represented as OIDs no matter what the set
-                        * element type is, and the element type's default is irrelevant
-                        * too.
+                        * Set attributes are represented as OIDs no matter what the
+                        * set element type is, and the element type's default is
+                        * irrelevant too.
                         */
                }
                else
-               {
                        expr = get_typdefault(atttype);
-               }
        }
 
        if (expr == NULL)
                return NULL;                    /* No default anywhere */
 
        /*
-        * Make sure the value is coerced to the target column
-        * type (might not be right type yet if it's not a
-        * constant!)  This should match the parser's processing of
-        * non-defaulted expressions --- see
+        * Make sure the value is coerced to the target column type (might not
+        * be right type yet if it's not a constant!)  This should match the
+        * parser's processing of non-defaulted expressions --- see
         * updateTargetListEntry().
         */
        exprtype = exprType(expr);
 
-       if (exprtype != atttype)
-       {
-               expr = CoerceTargetExpr(NULL, expr, exprtype,
-                                                               atttype, atttypmod);
-
-               /*
-                * This really shouldn't fail; should have checked the
-                * default's type when it was created ...
-                */
-               if (expr == NULL)
-                       elog(ERROR, "Column \"%s\" is of type %s"
-                                " but default expression is of type %s"
-                                "\n\tYou will need to rewrite or cast the expression",
-                                NameStr(att_tup->attname),
-                                format_type_be(atttype),
-                                format_type_be(exprtype));
-       }
-
+       expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */
+                                                                expr, exprtype,
+                                                                atttype, atttypmod,
+                                                                COERCION_ASSIGNMENT,
+                                                                COERCE_IMPLICIT_CAST);
        /*
-        * If the column is a fixed-length type, it may need a
-        * length coercion as well as a type coercion.
+        * This really shouldn't fail; should have checked the default's
+        * type when it was created ...
         */
-       expr = coerce_type_typmod(NULL, expr, atttype, atttypmod);
+       if (expr == NULL)
+               elog(ERROR, "Column \"%s\" is of type %s"
+                        " but default expression is of type %s"
+                        "\n\tYou will need to rewrite or cast the expression",
+                        NameStr(att_tup->attname),
+                        format_type_be(atttype),
+                        format_type_be(exprtype));
 
        return expr;
 }
@@ -511,12 +558,12 @@ matchLocks(CmdType event,
                   int varno,
                   Query *parsetree)
 {
-       List       *real_locks = NIL;
+       List       *matching_locks = NIL;
        int                     nlocks;
        int                     i;
 
-       Assert(rulelocks != NULL);      /* we get called iff there is some lock */
-       Assert(parsetree != NULL);
+       if (rulelocks == NULL)
+               return NIL;
 
        if (parsetree->commandType != CMD_SELECT)
        {
@@ -537,11 +584,11 @@ matchLocks(CmdType event,
                                 rangeTableEntry_used((Node *) parsetree, varno, 0) :
                                 attribute_used((Node *) parsetree,
                                                                varno, oneLock->attrno, 0)))
-                               real_locks = lappend(real_locks, oneLock);
+                               matching_locks = lappend(matching_locks, oneLock);
                }
        }
 
-       return real_locks;
+       return matching_locks;
 }
 
 
@@ -551,7 +598,8 @@ ApplyRetrieveRule(Query *parsetree,
                                  int rt_index,
                                  bool relation_level,
                                  Relation relation,
-                                 bool relIsUsed)
+                                 bool relIsUsed,
+                                 List *activeRIRs)
 {
        Query      *rule_action;
        RangeTblEntry *rte,
@@ -570,7 +618,7 @@ ApplyRetrieveRule(Query *parsetree,
         */
        rule_action = copyObject(lfirst(rule->actions));
 
-       rule_action = fireRIRrules(rule_action);
+       rule_action = fireRIRrules(rule_action, activeRIRs);
 
        /*
         * VIEWs are really easy --- just plug the view query in as a
@@ -672,7 +720,7 @@ markQueryForUpdate(Query *qry, bool skipOldNew)
  * the SubLink's subselect link with the possibly-rewritten subquery.
  */
 static bool
-fireRIRonSubLink(Node *node, void *context)
+fireRIRonSubLink(Node *node, List *activeRIRs)
 {
        if (node == NULL)
                return false;
@@ -681,7 +729,8 @@ fireRIRonSubLink(Node *node, void *context)
                SubLink    *sub = (SubLink *) node;
 
                /* Do what we came for */
-               sub->subselect = (Node *) fireRIRrules((Query *) (sub->subselect));
+               sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect,
+                                                                                          activeRIRs);
                /* Fall through to process lefthand args of SubLink */
        }
 
@@ -690,7 +739,7 @@ fireRIRonSubLink(Node *node, void *context)
         * processed subselects of subselects for us.
         */
        return expression_tree_walker(node, fireRIRonSubLink,
-                                                                 (void *) context);
+                                                                 (void *) activeRIRs);
 }
 
 
@@ -699,7 +748,7 @@ fireRIRonSubLink(Node *node, void *context)
  *     Apply all RIR rules on each rangetable entry in a query
  */
 static Query *
-fireRIRrules(Query *parsetree)
+fireRIRrules(Query *parsetree, List *activeRIRs)
 {
        int                     rt_index;
 
@@ -718,7 +767,6 @@ fireRIRrules(Query *parsetree)
                LOCKMODE        lockmode;
                bool            relIsUsed;
                int                     i;
-               List       *l;
 
                ++rt_index;
 
@@ -731,7 +779,7 @@ fireRIRrules(Query *parsetree)
                 */
                if (rte->rtekind == RTE_SUBQUERY)
                {
-                       rte->subquery = fireRIRrules(rte->subquery);
+                       rte->subquery = fireRIRrules(rte->subquery, activeRIRs);
                        continue;
                }
 
@@ -803,29 +851,42 @@ fireRIRrules(Query *parsetree)
                }
 
                /*
-                * Now apply them
+                * If we found any, apply them --- but first check for recursion!
                 */
-               foreach(l, locks)
+               if (locks != NIL)
                {
-                       rule = lfirst(l);
-
-                       parsetree = ApplyRetrieveRule(parsetree,
-                                                                                 rule,
-                                                                                 rt_index,
-                                                                                 rule->attrno == -1,
-                                                                                 rel,
-                                                                                 relIsUsed);
+                       List       *newActiveRIRs;
+                       List       *l;
+
+                       if (oidMember(RelationGetRelid(rel), activeRIRs))
+                               elog(ERROR, "Infinite recursion detected in rules for relation %s",
+                                        RelationGetRelationName(rel));
+                       newActiveRIRs = lconso(RelationGetRelid(rel), activeRIRs);
+
+                       foreach(l, locks)
+                       {
+                               rule = lfirst(l);
+
+                               parsetree = ApplyRetrieveRule(parsetree,
+                                                                                         rule,
+                                                                                         rt_index,
+                                                                                         rule->attrno == -1,
+                                                                                         rel,
+                                                                                         relIsUsed,
+                                                                                         newActiveRIRs);
+                       }
                }
 
                heap_close(rel, NoLock);
        }
 
        /*
-        * Recurse into sublink subqueries, too.
+        * Recurse into sublink subqueries, too.  But we already did the ones
+        * in the rtable.
         */
        if (parsetree->hasSubLinks)
-               query_tree_walker(parsetree, fireRIRonSubLink, NULL,
-                                               false /* already handled the ones in rtable */ );
+               query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
+                                                 QTW_IGNORE_RT_SUBQUERIES);
 
        /*
         * If the query was marked having aggregates, check if this is still
@@ -849,39 +910,11 @@ fireRIRrules(Query *parsetree)
 
 
 /*
- * idea is to fire regular rules first, then qualified instead
- * rules and unqualified instead rules last. Any lemming is counted for.
- */
-static List *
-orderRules(List *locks)
-{
-       List       *regular = NIL;
-       List       *instead_rules = NIL;
-       List       *instead_qualified = NIL;
-       List       *i;
-
-       foreach(i, locks)
-       {
-               RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-
-               if (rule_lock->isInstead)
-               {
-                       if (rule_lock->qual == NULL)
-                               instead_rules = lappend(instead_rules, rule_lock);
-                       else
-                               instead_qualified = lappend(instead_qualified, rule_lock);
-               }
-               else
-                       regular = lappend(regular, rule_lock);
-       }
-       return nconc(nconc(regular, instead_qualified), instead_rules);
-}
-
-
-/*
- * Modify the given query by adding 'AND NOT rule_qual' to its qualification.
- * This is used to generate suitable "else clauses" for conditional INSTEAD
- * rules.
+ * 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.)
  *
  * 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
@@ -890,10 +923,10 @@ orderRules(List *locks)
  * of the related entries in the query's own targetlist.
  */
 static Query *
-CopyAndAddQual(Query *parsetree,
-                          Node *rule_qual,
-                          int rt_index,
-                          CmdType event)
+CopyAndAddInvertedQual(Query *parsetree,
+                                          Node *rule_qual,
+                                          int rt_index,
+                                          CmdType event)
 {
        Query      *new_tree = (Query *) copyObject(parsetree);
        Node       *new_qual = (Node *) copyObject(rule_qual);
@@ -909,80 +942,98 @@ CopyAndAddQual(Query *parsetree,
                                                          event,
                                                          rt_index);
        /* And attach the fixed qual */
-       AddNotQual(new_tree, new_qual);
+       AddInvertedQual(new_tree, new_qual);
 
        return new_tree;
 }
 
 
-
 /*
  *     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.
  *
- *        remember: reality is for dead birds -- glass
+ * 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)
+ *     *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,
-                 bool *instead_flag,
                  List *locks,
-                 List **qual_products)
+                 bool *instead_flag,
+                 Query **qual_product)
 {
        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)
        {
                RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-               Node       *event_qual;
-               List       *actions;
+               Node       *event_qual = rule_lock->qual;
+               List       *actions = rule_lock->actions;
+               QuerySource     qsrc;
                List       *r;
 
-               /* multiple rule action time */
-               *instead_flag = rule_lock->isInstead;
-               event_qual = rule_lock->qual;
-               actions = rule_lock->actions;
-
-               if (event_qual != NULL && *instead_flag)
+               /* Determine correct QuerySource value for actions */
+               if (rule_lock->isInstead)
                {
-                       Query      *qual_product;
+                       if (event_qual != NULL)
+                               qsrc = QSRC_QUAL_INSTEAD_RULE;
+                       else
+                       {
+                               qsrc = QSRC_INSTEAD_RULE;
+                               *instead_flag = true; /* report unqualified INSTEAD */
+                       }
+               }
+               else
+                       qsrc = QSRC_NON_INSTEAD_RULE;
 
+               if (qsrc == QSRC_QUAL_INSTEAD_RULE)
+               {
                        /*
-                        * If there are instead rules with qualifications, the
+                        * 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
+                        * 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_products so
-                        * deepRewriteQuery() can add it to the query list after we
+                        * 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 (*qual_products == NIL)
-                               qual_product = parsetree;
-                       else
-                               qual_product = (Query *) lfirst(*qual_products);
-
-                       qual_product = CopyAndAddQual(qual_product,
-                                                                                 event_qual,
-                                                                                 rt_index,
-                                                                                 event);
-
-                       *qual_products = makeList1(qual_product);
+                       if (! *instead_flag)
+                       {
+                               if (*qual_product == NULL)
+                                       *qual_product = parsetree;
+                               *qual_product = CopyAndAddInvertedQual(*qual_product,
+                                                                                                          event_qual,
+                                                                                                          rt_index,
+                                                                                                          event);
+                       }
                }
 
+               /* Now process the rule's actions and add them to the result list */
                foreach(r, actions)
                {
                        Query      *rule_action = lfirst(r);
@@ -993,138 +1044,121 @@ fireRules(Query *parsetree,
                        rule_action = rewriteRuleAction(parsetree, rule_action,
                                                                                        event_qual, rt_index, event);
 
+                       rule_action->querySource = qsrc;
+                       rule_action->canSetTag = false;         /* might change later */
+
                        results = lappend(results, rule_action);
                }
-
-               /*
-                * If this was an unqualified instead rule, throw away an
-                * eventually saved 'default' parsetree
-                */
-               if (event_qual == NULL && *instead_flag)
-                       *qual_products = NIL;
        }
+
        return results;
 }
 
 
-
+/*
+ * 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, bool *instead_flag, List **qual_products)
+RewriteQuery(Query *parsetree, List *rewrite_events)
 {
-       CmdType         event;
-       List       *product_queries = NIL;
-       int                     result_relation;
-       RangeTblEntry *rt_entry;
-       Relation        rt_entry_relation;
-       RuleLock   *rt_entry_locks;
-
-       Assert(parsetree != NULL);
-
-       event = parsetree->commandType;
+       CmdType         event = parsetree->commandType;
+       bool            instead = false;
+       Query      *qual_product = NULL;
+       List       *rewritten = NIL;
 
        /*
+        * If the statement is an update, insert or delete - fire rules on it.
+        *
         * SELECT rules are handled later when we have all the queries that
-        * should get executed
-        */
-       if (event == CMD_SELECT)
-               return NIL;
-
-       /*
-        * Utilities aren't rewritten at all - why is this here?
-        */
-       if (event == CMD_UTILITY)
-               return NIL;
-
-       /*
-        * the statement is an update, insert or delete - fire rules on it.
-        */
-       result_relation = parsetree->resultRelation;
-       Assert(result_relation != 0);
-       rt_entry = rt_fetch(result_relation, parsetree->rtable);
-       Assert(rt_entry->rtekind == RTE_RELATION);
-
-       /*
-        * This may well be the first access to the result relation during the
-        * current statement (it will be, if this Query was extracted from a
-        * rule or somehow got here other than via the parser). Therefore,
-        * grab the appropriate lock type for a result relation, and do not
-        * release it until end of transaction.  This protects the rewriter
-        * and planner against schema changes mid-query.
+        * should get executed.  Also, utilities aren't rewritten at all
+        * (do we still need that check?)
         */
-       rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
+       if (event != CMD_SELECT && event != CMD_UTILITY)
+       {
+               int                     result_relation;
+               RangeTblEntry *rt_entry;
+               Relation        rt_entry_relation;
+               List       *locks;
 
-       /*
-        * If it's an INSERT or UPDATE, rewrite the targetlist into standard
-        * form.  This will be needed by the planner anyway, and doing it now
-        * ensures that any references to NEW.field will behave sanely.
-        */
-       if (event == CMD_INSERT || event == CMD_UPDATE)
-               rewriteTargetList(parsetree, rt_entry_relation);
+               result_relation = parsetree->resultRelation;
+               Assert(result_relation != 0);
+               rt_entry = rt_fetch(result_relation, parsetree->rtable);
+               Assert(rt_entry->rtekind == RTE_RELATION);
 
-       /*
-        * Collect and apply the appropriate rules.
-        */
-       rt_entry_locks = rt_entry_relation->rd_rules;
-
-       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);
-       }
+               /*
+                * This may well be the first access to the result relation during the
+                * current statement (it will be, if this Query was extracted from a
+                * rule or somehow got here other than via the parser). Therefore,
+                * grab the appropriate lock type for a result relation, and do not
+                * release it until end of transaction.  This protects the rewriter
+                * and planner against schema changes mid-query.
+                */
+               rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
 
-       heap_close(rt_entry_relation, NoLock);          /* keep lock! */
+               /*
+                * If it's an INSERT or UPDATE, rewrite the targetlist into standard
+                * form.  This will be needed by the planner anyway, and doing it now
+                * ensures that any references to NEW.field will behave sanely.
+                */
+               if (event == CMD_INSERT || event == CMD_UPDATE)
+                       rewriteTargetList(parsetree, rt_entry_relation);
 
-       return product_queries;
-}
+               /*
+                * Collect and apply the appropriate rules.
+                */
+               locks = matchLocks(event, rt_entry_relation->rd_rules,
+                                                  result_relation, parsetree);
 
+               if (locks != NIL)
+               {
+                       List       *product_queries;
 
-/*
- * to avoid infinite recursion, we restrict the number of times a query
- * can be rewritten. Detecting cycles is left for the reader as an exercise.
- */
-#ifndef REWRITE_INVOKE_MAX
-#define REWRITE_INVOKE_MAX             10
-#endif
+                       product_queries = fireRules(parsetree,
+                                                                               result_relation,
+                                                                               event,
+                                                                               locks,
+                                                                               &instead,
+                                                                               &qual_product);
 
-static int     numQueryRewriteInvoked = 0;
+                       /*
+                        * If we got any product queries, recursively rewrite them
+                        * --- but first check for recursion!
+                        */
+                       if (product_queries != NIL)
+                       {
+                               List       *n;
+                               rewrite_event *rev;
 
-/*
- * deepRewriteQuery -
- *       rewrites the query and apply the rules again on the queries rewritten
- */
-static List *
-deepRewriteQuery(Query *parsetree)
-{
-       List       *n;
-       List       *rewritten = NIL;
-       List       *result;
-       bool            instead;
-       List       *qual_products = NIL;
+                               foreach(n, rewrite_events)
+                               {
+                                       rev = (rewrite_event *) lfirst(n);
+                                       if (rev->relation == RelationGetRelid(rt_entry_relation) &&
+                                               rev->event == event)
+                                               elog(ERROR, "Infinite recursion detected in rules for relation %s",
+                                                        RelationGetRelationName(rt_entry_relation));
+                               }
 
-       if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX)
-       {
-               elog(ERROR, "query rewritten %d times, may contain cycles",
-                        numQueryRewriteInvoked - 1);
-       }
+                               rev = (rewrite_event *) palloc(sizeof(rewrite_event));
+                               rev->relation = RelationGetRelid(rt_entry_relation);
+                               rev->event = event;
+                               rewrite_events = lcons(rev, rewrite_events);
 
-       instead = false;
-       result = RewriteQuery(parsetree, &instead, &qual_products);
+                               foreach(n, product_queries)
+                               {
+                                       Query      *pt = (Query *) lfirst(n);
+                                       List       *newstuff;
 
-       foreach(n, result)
-       {
-               Query      *pt = lfirst(n);
-               List       *newstuff;
+                                       newstuff = RewriteQuery(pt, rewrite_events);
+                                       rewritten = nconc(rewritten, newstuff);
+                               }
+                       }
+               }
 
-               newstuff = deepRewriteQuery(pt);
-               if (newstuff != NIL)
-                       rewritten = nconc(rewritten, newstuff);
+               heap_close(rt_entry_relation, NoLock);          /* keep lock! */
        }
 
        /*
@@ -1132,61 +1166,36 @@ deepRewriteQuery(Query *parsetree)
         * 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 execution makes the deleted (and maybe the updated) tuples
+        * 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.
         */
-       if (parsetree->commandType == CMD_INSERT)
-       {
-               /*
-                * qual_products are the original query with the negated rule
-                * qualification of an INSTEAD rule
-                */
-               if (qual_products != NIL)
-                       rewritten = nconc(qual_products, rewritten);
-
-               /*
-                * Add the unmodified original query, if no INSTEAD rule was seen.
-                */
-               if (!instead)
-                       rewritten = lcons(parsetree, rewritten);
-       }
-       else
+       if (!instead)
        {
-               /*
-                * qual_products are the original query with the negated rule
-                * qualification of an INSTEAD rule
-                */
-               if (qual_products != NIL)
-                       rewritten = nconc(rewritten, qual_products);
-
-               /*
-                * Add the unmodified original query, if no INSTEAD rule was seen.
-                */
-               if (!instead)
-                       rewritten = lappend(rewritten, parsetree);
+               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 rewritten;
 }
 
 
-/*
- * QueryRewriteOne -
- *       rewrite one query
- */
-static List *
-QueryRewriteOne(Query *parsetree)
-{
-       numQueryRewriteInvoked = 0;
-
-       /*
-        * take a deep breath and apply all the rewrite rules - ay
-        */
-       return deepRewriteQuery(parsetree);
-}
-
-
 /*
  * QueryRewrite -
  *       Primary entry point to the query rewriter.
@@ -1202,13 +1211,16 @@ QueryRewrite(Query *parsetree)
        List       *querylist;
        List       *results = NIL;
        List       *l;
+       CmdType         origCmdType;
+       bool            foundOriginalQuery;
+       Query      *lastInstead;
 
        /*
         * Step 1
         *
         * Apply all non-SELECT rules possibly getting 0 or many queries
         */
-       querylist = QueryRewriteOne(parsetree);
+       querylist = RewriteQuery(parsetree, NIL);
 
        /*
         * Step 2
@@ -1219,7 +1231,7 @@ QueryRewrite(Query *parsetree)
        {
                Query      *query = (Query *) lfirst(l);
 
-               query = fireRIRrules(query);
+               query = fireRIRrules(query, NIL);
 
                /*
                 * If the query target was rewritten as a view, complain.
@@ -1234,13 +1246,16 @@ QueryRewrite(Query *parsetree)
                                switch (query->commandType)
                                {
                                        case CMD_INSERT:
-                                               elog(ERROR, "Cannot insert into a view without an appropriate rule");
+                                               elog(ERROR, "Cannot insert into a view"
+                                                        "\n\tYou need an unconditional ON INSERT DO INSTEAD rule");
                                                break;
                                        case CMD_UPDATE:
-                                               elog(ERROR, "Cannot update a view without an appropriate rule");
+                                               elog(ERROR, "Cannot update a view"
+                                                        "\n\tYou need an unconditional ON UPDATE DO INSTEAD rule");
                                                break;
                                        case CMD_DELETE:
-                                               elog(ERROR, "Cannot delete from a view without an appropriate rule");
+                                               elog(ERROR, "Cannot delete from a view"
+                                                        "\n\tYou need an unconditional ON DELETE DO INSTEAD rule");
                                                break;
                                        default:
                                                elog(ERROR, "QueryRewrite: unexpected commandType %d",
@@ -1253,5 +1268,51 @@ QueryRewrite(Query *parsetree)
                results = lappend(results, query);
        }
 
+       /*
+        * 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;
+
+       foreach(l, results)
+       {
+               Query      *query = (Query *) lfirst(l);
+
+               if (query->querySource == QSRC_ORIGINAL)
+               {
+                       Assert(query->canSetTag);
+                       Assert(!foundOriginalQuery);
+                       foundOriginalQuery = true;
+#ifndef USE_ASSERT_CHECKING
+                       break;
+#endif
+               }
+               else
+               {
+                       Assert(!query->canSetTag);
+                       if (query->commandType == origCmdType &&
+                               (query->querySource == QSRC_INSTEAD_RULE ||
+                                query->querySource == QSRC_QUAL_INSTEAD_RULE))
+                               lastInstead = query;
+               }
+       }
+
+       if (!foundOriginalQuery && lastInstead != NULL)
+               lastInstead->canSetTag = true;
+
        return results;
 }