]> granicus.if.org Git - postgresql/blobdiff - src/backend/rewrite/rewriteManip.c
Implement SQL-standard WITH clauses, including WITH RECURSIVE.
[postgresql] / src / backend / rewrite / rewriteManip.c
index e83ac05485304210074687d2618a2a5e9a8fbdc0..b5d82dd0e7f99f411c27750e6b52812b08c2a3fa 100644 (file)
  *
  * rewriteManip.c
  *
- * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.48 2000/09/12 21:07:04 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.114 2008/10/04 21:56:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
-#include "optimizer/tlist.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
-#include "parser/parse_clause.h"
 #include "rewrite/rewriteManip.h"
-#include "utils/lsyscache.h"
 
 
-/* macros borrowed from expression_tree_mutator */
-
-#define FLATCOPY(newnode, node, nodetype)  \
-       ( (newnode) = makeNode(nodetype), \
-         memcpy((newnode), (node), sizeof(nodetype)) )
+typedef struct
+{
+       int                     sublevels_up;
+} contain_aggs_of_level_context;
 
-#define MUTATE(newfield, oldfield, fieldtype, mutator, context)  \
-               ( (newfield) = (fieldtype) mutator((Node *) (oldfield), (context)) )
+typedef struct
+{
+       int                     agg_location;
+       int                     sublevels_up;
+} locate_agg_of_level_context;
 
-static bool checkExprHasAggs_walker(Node *node, void *context);
+static bool contain_aggs_of_level_walker(Node *node,
+                                               contain_aggs_of_level_context *context);
+static bool locate_agg_of_level_walker(Node *node,
+                                               locate_agg_of_level_context *context);
 static bool checkExprHasSubLink_walker(Node *node, void *context);
+static Relids offset_relid_set(Relids relids, int offset);
+static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
 
 
 /*
  * checkExprHasAggs -
- *     Queries marked hasAggs might not have them any longer after
- *     rewriting. Check it.
+ *     Check if an expression contains an aggregate function call of the
+ *     current query level.
  */
 bool
 checkExprHasAggs(Node *node)
 {
+       return contain_aggs_of_level(node, 0);
+}
+
+/*
+ * contain_aggs_of_level -
+ *     Check if an expression contains an aggregate function call of a
+ *     specified query level.
+ *
+ * The objective of this routine is to detect whether there are aggregates
+ * belonging to the given query level.  Aggregates belonging to subqueries
+ * or outer queries do NOT cause a true result.  We must recurse into
+ * subqueries to detect outer-reference aggregates that logically belong to
+ * the specified query level.
+ */
+bool
+contain_aggs_of_level(Node *node, int levelsup)
+{
+       contain_aggs_of_level_context context;
+
+       context.sublevels_up = levelsup;
+
        /*
-        * If a Query is passed, examine it --- but we will not recurse
-        * into sub-Queries.
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, we don't want to increment sublevels_up.
         */
-       if (node && IsA(node, Query))
-               return query_tree_walker((Query *) node, checkExprHasAggs_walker,
-                                                                NULL);
-       else
-               return checkExprHasAggs_walker(node, NULL);
+       return query_or_expression_tree_walker(node,
+                                                                                  contain_aggs_of_level_walker,
+                                                                                  (void *) &context,
+                                                                                  0);
+}
+
+static bool
+contain_aggs_of_level_walker(Node *node,
+                                                        contain_aggs_of_level_context *context)
+{
+       if (node == NULL)
+               return false;
+       if (IsA(node, Aggref))
+       {
+               if (((Aggref *) node)->agglevelsup == context->sublevels_up)
+                       return true;            /* abort the tree traversal and return true */
+               /* else fall through to examine argument */
+       }
+       if (IsA(node, Query))
+       {
+               /* Recurse into subselects */
+               bool            result;
+
+               context->sublevels_up++;
+               result = query_tree_walker((Query *) node,
+                                                                  contain_aggs_of_level_walker,
+                                                                  (void *) context, 0);
+               context->sublevels_up--;
+               return result;
+       }
+       return expression_tree_walker(node, contain_aggs_of_level_walker,
+                                                                 (void *) context);
+}
+
+/*
+ * locate_agg_of_level -
+ *       Find the parse location of any aggregate of the specified query level.
+ *
+ * Returns -1 if no such agg is in the querytree, or if they all have
+ * unknown parse location.  (The former case is probably caller error,
+ * but we don't bother to distinguish it from the latter case.)
+ *
+ * Note: it might seem appropriate to merge this functionality into
+ * contain_aggs_of_level, but that would complicate that function's API.
+ * Currently, the only uses of this function are for error reporting,
+ * and so shaving cycles probably isn't very important.
+ */
+int
+locate_agg_of_level(Node *node, int levelsup)
+{
+       locate_agg_of_level_context context;
+
+       context.agg_location = -1;              /* in case we find nothing */
+       context.sublevels_up = levelsup;
+
+       /*
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, we don't want to increment sublevels_up.
+        */
+       (void) query_or_expression_tree_walker(node,
+                                                                                  locate_agg_of_level_walker,
+                                                                                  (void *) &context,
+                                                                                  0);
+
+       return context.agg_location;
 }
 
 static bool
-checkExprHasAggs_walker(Node *node, void *context)
+locate_agg_of_level_walker(Node *node,
+                                                  locate_agg_of_level_context *context)
 {
        if (node == NULL)
                return false;
        if (IsA(node, Aggref))
-               return true;                    /* abort the tree traversal and return
-                                                                * true */
-       return expression_tree_walker(node, checkExprHasAggs_walker, context);
+       {
+               if (((Aggref *) node)->agglevelsup == context->sublevels_up &&
+                       ((Aggref *) node)->location >= 0)
+               {
+                       context->agg_location = ((Aggref *) node)->location;
+                       return true;            /* abort the tree traversal and return true */
+               }
+               /* else fall through to examine argument */
+       }
+       if (IsA(node, Query))
+       {
+               /* Recurse into subselects */
+               bool            result;
+
+               context->sublevels_up++;
+               result = query_tree_walker((Query *) node,
+                                                                  locate_agg_of_level_walker,
+                                                                  (void *) context, 0);
+               context->sublevels_up--;
+               return result;
+       }
+       return expression_tree_walker(node, locate_agg_of_level_walker,
+                                                                 (void *) context);
 }
 
 /*
  * checkExprHasSubLink -
- *     Queries marked hasSubLinks might not have them any longer after
- *     rewriting. Check it.
+ *     Check if an expression contains a SubLink.
  */
 bool
 checkExprHasSubLink(Node *node)
 {
        /*
-        * If a Query is passed, examine it --- but we will not recurse
-        * into sub-Queries.
+        * If a Query is passed, examine it --- but we should not recurse into
+        * sub-Queries that are in its rangetable or CTE list.
         */
-       if (node && IsA(node, Query))
-               return query_tree_walker((Query *) node, checkExprHasSubLink_walker,
-                                                                NULL);
-       else
-               return checkExprHasSubLink_walker(node, NULL);
+       return query_or_expression_tree_walker(node,
+                                                                                  checkExprHasSubLink_walker,
+                                                                                  NULL,
+                                                                                  QTW_IGNORE_RC_SUBQUERIES);
 }
 
 static bool
@@ -89,8 +198,7 @@ checkExprHasSubLink_walker(Node *node, void *context)
        if (node == NULL)
                return false;
        if (IsA(node, SubLink))
-               return true;                    /* abort the tree traversal and return
-                                                                * true */
+               return true;                    /* abort the tree traversal and return true */
        return expression_tree_walker(node, checkExprHasSubLink_walker, context);
 }
 
@@ -100,8 +208,8 @@ checkExprHasSubLink_walker(Node *node, void *context)
  *
  * Find all Var nodes in the given tree with varlevelsup == sublevels_up,
  * and increment their varno fields (rangetable indexes) by 'offset'.
- * The varnoold fields are adjusted similarly.  Also, RangeTblRef nodes
- * in join trees are adjusted.
+ * The varnoold fields are adjusted similarly. Also, adjust other nodes
+ * that contain rangetable indexes, such as RangeTblRef and JoinExpr.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.     The given expression tree should have been copied
@@ -130,14 +238,55 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
                }
                return false;
        }
+       if (IsA(node, CurrentOfExpr))
+       {
+               CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+               if (context->sublevels_up == 0)
+                       cexpr->cvarno += context->offset;
+               return false;
+       }
        if (IsA(node, RangeTblRef))
        {
-               RangeTblRef        *rtr = (RangeTblRef *) node;
+               RangeTblRef *rtr = (RangeTblRef *) node;
 
                if (context->sublevels_up == 0)
                        rtr->rtindex += context->offset;
+               /* the subquery itself is visited separately */
                return false;
        }
+       if (IsA(node, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) node;
+
+               if (context->sublevels_up == 0)
+                       j->rtindex += context->offset;
+               /* fall through to examine children */
+       }
+       if (IsA(node, FlattenedSubLink))
+       {
+               FlattenedSubLink *fslink = (FlattenedSubLink *) node;
+
+               if (context->sublevels_up == 0)
+               {
+                       fslink->lefthand = offset_relid_set(fslink->lefthand,
+                                                                                               context->offset);
+                       fslink->righthand = offset_relid_set(fslink->righthand,
+                                                                                                context->offset);
+               }
+               /* fall through to examine children */
+       }
+       if (IsA(node, AppendRelInfo))
+       {
+               AppendRelInfo *appinfo = (AppendRelInfo *) node;
+
+               if (context->sublevels_up == 0)
+               {
+                       appinfo->parent_relid += context->offset;
+                       appinfo->child_relid += context->offset;
+               }
+               /* fall through to examine children */
+       }
        if (IsA(node, Query))
        {
                /* Recurse into subselects */
@@ -145,7 +294,7 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 
                context->sublevels_up++;
                result = query_tree_walker((Query *) node, OffsetVarNodes_walker,
-                                                                  (void *) context);
+                                                                  (void *) context, 0);
                context->sublevels_up--;
                return result;
        }
@@ -162,24 +311,62 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
        context.sublevels_up = sublevels_up;
 
        /*
-        * Must be prepared to start with a Query or a bare expression tree;
-        * if it's a Query, go straight to query_tree_walker to make sure that
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, go straight to query_tree_walker to make sure that
         * sublevels_up doesn't get incremented prematurely.
         */
        if (node && IsA(node, Query))
-               query_tree_walker((Query *) node, OffsetVarNodes_walker,
-                                                 (void *) &context);
+       {
+               Query      *qry = (Query *) node;
+
+               /*
+                * If we are starting at a Query, and sublevels_up is zero, then we
+                * must also fix rangetable indexes in the Query itself --- namely
+                * resultRelation and rowMarks entries.  sublevels_up cannot be zero
+                * when recursing into a subquery, so there's no need to have the same
+                * logic inside OffsetVarNodes_walker.
+                */
+               if (sublevels_up == 0)
+               {
+                       ListCell   *l;
+
+                       if (qry->resultRelation)
+                               qry->resultRelation += offset;
+                       foreach(l, qry->rowMarks)
+                       {
+                               RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+                               rc->rti += offset;
+                       }
+               }
+               query_tree_walker(qry, OffsetVarNodes_walker,
+                                                 (void *) &context, 0);
+       }
        else
                OffsetVarNodes_walker(node, &context);
 }
 
+static Relids
+offset_relid_set(Relids relids, int offset)
+{
+       Relids          result = NULL;
+       Relids          tmprelids;
+       int                     rtindex;
+
+       tmprelids = bms_copy(relids);
+       while ((rtindex = bms_first_member(tmprelids)) >= 0)
+               result = bms_add_member(result, rtindex + offset);
+       bms_free(tmprelids);
+       return result;
+}
+
 /*
  * ChangeVarNodes - adjust Var nodes for a specific change of RT index
  *
  * Find all Var nodes in the given tree belonging to a specific relation
  * (identified by sublevels_up and rt_index), and change their varno fields
- * to 'new_index'.     The varnoold fields are changed too.  Also, RangeTblRef
- * nodes in join trees are adjusted.
+ * to 'new_index'.     The varnoold fields are changed too.  Also, adjust other
+ * nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.     The given expression tree should have been copied
@@ -210,15 +397,62 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
                }
                return false;
        }
+       if (IsA(node, CurrentOfExpr))
+       {
+               CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+               if (context->sublevels_up == 0 &&
+                       cexpr->cvarno == context->rt_index)
+                       cexpr->cvarno = context->new_index;
+               return false;
+       }
        if (IsA(node, RangeTblRef))
        {
-               RangeTblRef        *rtr = (RangeTblRef *) node;
+               RangeTblRef *rtr = (RangeTblRef *) node;
 
                if (context->sublevels_up == 0 &&
                        rtr->rtindex == context->rt_index)
                        rtr->rtindex = context->new_index;
+               /* the subquery itself is visited separately */
                return false;
        }
+       if (IsA(node, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) node;
+
+               if (context->sublevels_up == 0 &&
+                       j->rtindex == context->rt_index)
+                       j->rtindex = context->new_index;
+               /* fall through to examine children */
+       }
+       if (IsA(node, FlattenedSubLink))
+       {
+               FlattenedSubLink *fslink = (FlattenedSubLink *) node;
+
+               if (context->sublevels_up == 0)
+               {
+                       fslink->lefthand = adjust_relid_set(fslink->lefthand,
+                                                                                               context->rt_index,
+                                                                                               context->new_index);
+                       fslink->righthand = adjust_relid_set(fslink->righthand,
+                                                                                                context->rt_index,
+                                                                                                context->new_index);
+               }
+               /* fall through to examine children */
+       }
+       if (IsA(node, AppendRelInfo))
+       {
+               AppendRelInfo *appinfo = (AppendRelInfo *) node;
+
+               if (context->sublevels_up == 0)
+               {
+                       if (appinfo->parent_relid == context->rt_index)
+                               appinfo->parent_relid = context->new_index;
+                       if (appinfo->child_relid == context->rt_index)
+                               appinfo->child_relid = context->new_index;
+               }
+               /* fall through to examine children */
+       }
        if (IsA(node, Query))
        {
                /* Recurse into subselects */
@@ -226,7 +460,7 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 
                context->sublevels_up++;
                result = query_tree_walker((Query *) node, ChangeVarNodes_walker,
-                                                                  (void *) context);
+                                                                  (void *) context, 0);
                context->sublevels_up--;
                return result;
        }
@@ -244,17 +478,59 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
        context.sublevels_up = sublevels_up;
 
        /*
-        * Must be prepared to start with a Query or a bare expression tree;
-        * if it's a Query, go straight to query_tree_walker to make sure that
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, go straight to query_tree_walker to make sure that
         * sublevels_up doesn't get incremented prematurely.
         */
        if (node && IsA(node, Query))
-               query_tree_walker((Query *) node, ChangeVarNodes_walker,
-                                                 (void *) &context);
+       {
+               Query      *qry = (Query *) node;
+
+               /*
+                * If we are starting at a Query, and sublevels_up is zero, then we
+                * must also fix rangetable indexes in the Query itself --- namely
+                * resultRelation and rowMarks entries.  sublevels_up cannot be zero
+                * when recursing into a subquery, so there's no need to have the same
+                * logic inside ChangeVarNodes_walker.
+                */
+               if (sublevels_up == 0)
+               {
+                       ListCell   *l;
+
+                       if (qry->resultRelation == rt_index)
+                               qry->resultRelation = new_index;
+                       foreach(l, qry->rowMarks)
+                       {
+                               RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+                               if (rc->rti == rt_index)
+                                       rc->rti = new_index;
+                       }
+               }
+               query_tree_walker(qry, ChangeVarNodes_walker,
+                                                 (void *) &context, 0);
+       }
        else
                ChangeVarNodes_walker(node, &context);
 }
 
+/*
+ * Substitute newrelid for oldrelid in a Relid set
+ */
+static Relids
+adjust_relid_set(Relids relids, int oldrelid, int newrelid)
+{
+       if (bms_is_member(oldrelid, relids))
+       {
+               /* Ensure we have a modifiable copy */
+               relids = bms_copy(relids);
+               /* Remove old, add new */
+               relids = bms_del_member(relids, oldrelid);
+               relids = bms_add_member(relids, newrelid);
+       }
+       return relids;
+}
+
 /*
  * IncrementVarSublevelsUp - adjust Var nodes when pushing them down in tree
  *
@@ -267,6 +543,8 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
  * that sublink are not affected, only outer references to vars that belong
  * to the expression's original query level or parents thereof.
  *
+ * Likewise for other nodes containing levelsup fields, such as Aggref.
+ *
  * 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!
@@ -290,8 +568,34 @@ IncrementVarSublevelsUp_walker(Node *node,
 
                if (var->varlevelsup >= context->min_sublevels_up)
                        var->varlevelsup += context->delta_sublevels_up;
+               return false;                   /* done here */
+       }
+       if (IsA(node, CurrentOfExpr))
+       {
+               /* this should not happen */
+               if (context->min_sublevels_up == 0)
+                       elog(ERROR, "cannot push down CurrentOfExpr");
                return false;
        }
+       if (IsA(node, Aggref))
+       {
+               Aggref     *agg = (Aggref *) node;
+
+               if (agg->agglevelsup >= context->min_sublevels_up)
+                       agg->agglevelsup += context->delta_sublevels_up;
+               /* fall through to recurse into argument */
+       }
+       if (IsA(node, RangeTblEntry))
+       {
+               RangeTblEntry *rte = (RangeTblEntry *) node;
+
+               if (rte->rtekind == RTE_CTE)
+               {
+                       if (rte->ctelevelsup >= context->min_sublevels_up)
+                               rte->ctelevelsup += context->delta_sublevels_up;
+               }
+               return false;                   /* allow range_table_walker to continue */
+       }
        if (IsA(node, Query))
        {
                /* Recurse into subselects */
@@ -300,7 +604,8 @@ IncrementVarSublevelsUp_walker(Node *node,
                context->min_sublevels_up++;
                result = query_tree_walker((Query *) node,
                                                                   IncrementVarSublevelsUp_walker,
-                                                                  (void *) context);
+                                                                  (void *) context,
+                                                                  QTW_EXAMINE_RTES);
                context->min_sublevels_up--;
                return result;
        }
@@ -318,21 +623,38 @@ IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
        context.min_sublevels_up = min_sublevels_up;
 
        /*
-        * Must be prepared to start with a Query or a bare expression tree;
-        * if it's a Query, go straight to query_tree_walker to make sure that
-        * sublevels_up doesn't get incremented prematurely.
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, we don't want to increment sublevels_up.
         */
-       if (node && IsA(node, Query))
-               query_tree_walker((Query *) node, IncrementVarSublevelsUp_walker,
-                                                 (void *) &context);
-       else
-               IncrementVarSublevelsUp_walker(node, &context);
+       query_or_expression_tree_walker(node,
+                                                                       IncrementVarSublevelsUp_walker,
+                                                                       (void *) &context,
+                                                                       QTW_EXAMINE_RTES);
+}
+
+/*
+ * IncrementVarSublevelsUp_rtable -
+ *     Same as IncrementVarSublevelsUp, but to be invoked on a range table.
+ */
+void
+IncrementVarSublevelsUp_rtable(List *rtable, int delta_sublevels_up,
+                                                          int min_sublevels_up)
+{
+       IncrementVarSublevelsUp_context context;
+
+       context.delta_sublevels_up = delta_sublevels_up;
+       context.min_sublevels_up = min_sublevels_up;
+
+       range_table_walker(rtable,
+                                          IncrementVarSublevelsUp_walker,
+                                          (void *) &context,
+                                          QTW_EXAMINE_RTES);
 }
 
 
 /*
  * rangeTableEntry_used - detect whether an RTE is referenced somewhere
- *     in var nodes or jointree nodes of a query or expression.
+ *     in var nodes or join or setOp trees of a query or expression.
  */
 
 typedef struct
@@ -356,6 +678,15 @@ rangeTableEntry_used_walker(Node *node,
                        return true;
                return false;
        }
+       if (IsA(node, CurrentOfExpr))
+       {
+               CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+
+               if (context->sublevels_up == 0 &&
+                       cexpr->cvarno == context->rt_index)
+                       return true;
+               return false;
+       }
        if (IsA(node, RangeTblRef))
        {
                RangeTblRef *rtr = (RangeTblRef *) node;
@@ -363,8 +694,23 @@ rangeTableEntry_used_walker(Node *node,
                if (rtr->rtindex == context->rt_index &&
                        context->sublevels_up == 0)
                        return true;
+               /* the subquery itself is visited separately */
                return false;
        }
+       if (IsA(node, JoinExpr))
+       {
+               JoinExpr   *j = (JoinExpr *) node;
+
+               if (j->rtindex == context->rt_index &&
+                       context->sublevels_up == 0)
+                       return true;
+               /* fall through to examine children */
+       }
+       /* Shouldn't need to handle planner auxiliary nodes here */
+       Assert(!IsA(node, FlattenedSubLink));
+       Assert(!IsA(node, SpecialJoinInfo));
+       Assert(!IsA(node, AppendRelInfo));
+
        if (IsA(node, Query))
        {
                /* Recurse into subselects */
@@ -372,7 +718,7 @@ rangeTableEntry_used_walker(Node *node,
 
                context->sublevels_up++;
                result = query_tree_walker((Query *) node, rangeTableEntry_used_walker,
-                                                                  (void *) context);
+                                                                  (void *) context, 0);
                context->sublevels_up--;
                return result;
        }
@@ -389,15 +735,13 @@ rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
        context.sublevels_up = sublevels_up;
 
        /*
-        * Must be prepared to start with a Query or a bare expression tree;
-        * if it's a Query, go straight to query_tree_walker to make sure that
-        * sublevels_up doesn't get incremented prematurely.
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, we don't want to increment sublevels_up.
         */
-       if (node && IsA(node, Query))
-               return query_tree_walker((Query *) node, rangeTableEntry_used_walker,
-                                                                (void *) &context);
-       else
-               return rangeTableEntry_used_walker(node, &context);
+       return query_or_expression_tree_walker(node,
+                                                                                  rangeTableEntry_used_walker,
+                                                                                  (void *) &context,
+                                                                                  0);
 }
 
 
@@ -437,7 +781,7 @@ attribute_used_walker(Node *node,
 
                context->sublevels_up++;
                result = query_tree_walker((Query *) node, attribute_used_walker,
-                                                                  (void *) context);
+                                                                  (void *) context, 0);
                context->sublevels_up--;
                return result;
        }
@@ -455,234 +799,246 @@ attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
        context.sublevels_up = sublevels_up;
 
        /*
-        * Must be prepared to start with a Query or a bare expression tree;
-        * if it's a Query, go straight to query_tree_walker to make sure that
-        * sublevels_up doesn't get incremented prematurely.
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, we don't want to increment sublevels_up.
         */
-       if (node && IsA(node, Query))
-               return query_tree_walker((Query *) node, attribute_used_walker,
-                                                                (void *) &context);
-       else
-               return attribute_used_walker(node, &context);
+       return query_or_expression_tree_walker(node,
+                                                                                  attribute_used_walker,
+                                                                                  (void *) &context,
+                                                                                  0);
 }
 
 
 /*
- * Add the given qualifier condition to the query's WHERE clause
+ * If the given Query is an INSERT ... SELECT construct, extract and
+ * return the sub-Query node that represents the SELECT part.  Otherwise
+ * return the given Query.
+ *
+ * If subquery_ptr is not NULL, then *subquery_ptr is set to the location
+ * of the link to the SELECT subquery inside parsetree, or NULL if not an
+ * INSERT ... SELECT.
+ *
+ * This is a hack needed because transformations on INSERT ... SELECTs that
+ * appear in rule actions should be applied to the source SELECT, not to the
+ * INSERT part.  Perhaps this can be cleaned up with redesigned querytrees.
  */
-void
-AddQual(Query *parsetree, Node *qual)
+Query *
+getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
 {
-       Node       *copy,
-                          *old;
+       Query      *selectquery;
+       RangeTblEntry *selectrte;
+       RangeTblRef *rtr;
 
-       if (qual == NULL)
-               return;
+       if (subquery_ptr)
+               *subquery_ptr = NULL;
 
-       /* INTERSECT want's the original, but we need to copy - Jan */
-       copy = copyObject(qual);
-
-       old = parsetree->qual;
-       if (old == NULL)
-               parsetree->qual = copy;
-       else
-               parsetree->qual = (Node *) make_andclause(makeList(old, copy, -1));
+       if (parsetree == NULL)
+               return parsetree;
+       if (parsetree->commandType != CMD_INSERT)
+               return parsetree;
 
        /*
-        * Make sure query is marked correctly if added qual has sublinks or
-        * aggregates (not sure it can ever have aggs, but sublinks
-        * definitely).
+        * Currently, this is ONLY applied to rule-action queries, and so we
+        * expect to find the *OLD* and *NEW* placeholder entries in the given
+        * query.  If they're not there, it must be an INSERT/SELECT in which
+        * they've been pushed down to the SELECT.
         */
-       parsetree->hasAggs |= checkExprHasAggs(copy);
-       parsetree->hasSubLinks |= checkExprHasSubLink(copy);
+       if (list_length(parsetree->rtable) >= 2 &&
+               strcmp(rt_fetch(PRS2_OLD_VARNO, parsetree->rtable)->eref->aliasname,
+                          "*OLD*") == 0 &&
+               strcmp(rt_fetch(PRS2_NEW_VARNO, parsetree->rtable)->eref->aliasname,
+                          "*NEW*") == 0)
+               return parsetree;
+       Assert(parsetree->jointree && IsA(parsetree->jointree, FromExpr));
+       if (list_length(parsetree->jointree->fromlist) != 1)
+               elog(ERROR, "expected to find SELECT subquery");
+       rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist);
+       Assert(IsA(rtr, RangeTblRef));
+       selectrte = rt_fetch(rtr->rtindex, parsetree->rtable);
+       selectquery = selectrte->subquery;
+       if (!(selectquery && IsA(selectquery, Query) &&
+                 selectquery->commandType == CMD_SELECT))
+               elog(ERROR, "expected to find SELECT subquery");
+       if (list_length(selectquery->rtable) >= 2 &&
+               strcmp(rt_fetch(PRS2_OLD_VARNO, selectquery->rtable)->eref->aliasname,
+                          "*OLD*") == 0 &&
+               strcmp(rt_fetch(PRS2_NEW_VARNO, selectquery->rtable)->eref->aliasname,
+                          "*NEW*") == 0)
+       {
+               if (subquery_ptr)
+                       *subquery_ptr = &(selectrte->subquery);
+               return selectquery;
+       }
+       elog(ERROR, "could not find rule placeholders");
+       return NULL;                            /* not reached */
 }
 
+
 /*
- * Add the given havingQual to the one already contained in the parsetree
- * just as AddQual does for the normal 'where' qual
+ * Add the given qualifier condition to the query's WHERE clause
  */
 void
-AddHavingQual(Query *parsetree, Node *havingQual)
+AddQual(Query *parsetree, Node *qual)
 {
-       Node       *copy,
-                          *old;
+       Node       *copy;
 
-       if (havingQual == NULL)
+       if (qual == NULL)
                return;
 
+       if (parsetree->commandType == CMD_UTILITY)
+       {
+               /*
+                * There's noplace to put the qual on a utility statement.
+                *
+                * If it's a NOTIFY, silently ignore the qual; this means that the
+                * NOTIFY will execute, whether or not there are any qualifying rows.
+                * While clearly wrong, this is much more useful than refusing to
+                * execute the rule at all, and extra NOTIFY events are harmless for
+                * typical uses of NOTIFY.
+                *
+                * If it isn't a NOTIFY, error out, since unconditional execution of
+                * other utility stmts is unlikely to be wanted.  (This case is not
+                * currently allowed anyway, but keep the test for safety.)
+                */
+               if (parsetree->utilityStmt && IsA(parsetree->utilityStmt, NotifyStmt))
+                       return;
+               else
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("conditional utility statements are not implemented")));
+       }
+
+       if (parsetree->setOperations != NULL)
+       {
+               /*
+                * There's noplace to put the qual on a setop statement, either. (This
+                * could be fixed, but right now the planner simply ignores any qual
+                * condition on a setop query.)
+                */
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
+       }
+
        /* INTERSECT want's the original, but we need to copy - Jan */
-       copy = copyObject(havingQual);
+       copy = copyObject(qual);
 
-       old = parsetree->havingQual;
-       if (old == NULL)
-               parsetree->havingQual = copy;
-       else
-               parsetree->havingQual = (Node *) make_andclause(makeList(old, copy, -1));
+       parsetree->jointree->quals = make_and_qual(parsetree->jointree->quals,
+                                                                                          copy);
 
        /*
-        * Make sure query is marked correctly if added qual has sublinks or
-        * aggregates (not sure it can ever have aggs, but sublinks
-        * definitely).
+        * We had better not have stuck an aggregate into the WHERE clause.
         */
-       parsetree->hasAggs |= checkExprHasAggs(copy);
-       parsetree->hasSubLinks |= checkExprHasSubLink(copy);
-}
-
-#ifdef NOT_USED
-void
-AddNotHavingQual(Query *parsetree, Node *havingQual)
-{
-       Node       *notqual;
+       Assert(!checkExprHasAggs(copy));
 
-       if (havingQual == NULL)
-               return;
-
-       /* Need not copy input qual, because AddHavingQual will... */
-       notqual = (Node *) make_notclause((Expr *) havingQual);
-
-       AddHavingQual(parsetree, notqual);
+       /*
+        * Make sure query is marked correctly if added qual has sublinks. Need
+        * not search qual when query is already marked.
+        */
+       if (!parsetree->hasSubLinks)
+               parsetree->hasSubLinks = checkExprHasSubLink(copy);
 }
 
-#endif
 
+/*
+ * Invert the given clause and add it to the WHERE qualifications of the
+ * given querytree.  Inversion means "x IS NOT TRUE", not just "NOT x",
+ * else we will do the wrong thing when x evaluates to NULL.
+ */
 void
-AddNotQual(Query *parsetree, Node *qual)
+AddInvertedQual(Query *parsetree, Node *qual)
 {
-       Node       *notqual;
+       BooleanTest *invqual;
 
        if (qual == NULL)
                return;
 
        /* Need not copy input qual, because AddQual will... */
-       notqual = (Node *) make_notclause((Expr *) qual);
+       invqual = makeNode(BooleanTest);
+       invqual->arg = (Expr *) qual;
+       invqual->booltesttype = IS_NOT_TRUE;
 
-       AddQual(parsetree, notqual);
+       AddQual(parsetree, (Node *) invqual);
 }
 
 
 /*
- * Add all expressions used by the given GroupClause list to the
- * parsetree's targetlist and groupclause list.
+ * ResolveNew - replace Vars with corresponding items from a targetlist
+ *
+ * Vars matching target_varno and sublevels_up are replaced by the
+ * entry with matching resno from targetlist, if there is one.
+ * If not, we either change the unmatched Var's varno to update_varno
+ * (when event == CMD_UPDATE) or replace it with a constant NULL.
  *
- * tlist is the old targetlist associated with the input groupclauses.
+ * The caller must also provide target_rte, the RTE describing the target
+ * relation.  This is needed to handle whole-row Vars referencing the target.
+ * We expand such Vars into RowExpr constructs.
  *
- * XXX shouldn't we be checking to see if there are already matching
- * entries in parsetree->targetlist?
+ * Note: the business with inserted_sublink is needed to update hasSubLinks
+ * in subqueries when the replacement adds a subquery inside a subquery.
+ * Messy, isn't it?  We do not need to do similar pushups for hasAggs,
+ * because it isn't possible for this transformation to insert a level-zero
+ * aggregate reference into a subquery --- it could only insert outer aggs.
  */
-void
-AddGroupClause(Query *parsetree, List *group_by, List *tlist)
-{
-       List       *l;
 
-       foreach(l, group_by)
-       {
-               GroupClause *groupclause = (GroupClause *) copyObject(lfirst(l));
-               TargetEntry *tle = get_sortgroupclause_tle(groupclause, tlist);
-
-               /* copy the groupclause's TLE from the old tlist */
-               tle = (TargetEntry *) copyObject(tle);
-
-               /*
-                * The ressortgroupref number in the old tlist might be already
-                * taken in the new tlist, so force assignment of a new number.
-                */
-               tle->resdom->ressortgroupref = 0;
-               groupclause->tleSortGroupRef =
-                       assignSortGroupRef(tle, parsetree->targetList);
-
-               /* Also need to set the resno and mark it resjunk. */
-               tle->resdom->resno = length(parsetree->targetList) + 1;
-               tle->resdom->resjunk = true;
-
-               parsetree->targetList = lappend(parsetree->targetList, tle);
-               parsetree->groupClause = lappend(parsetree->groupClause, groupclause);
-       }
-}
+typedef struct
+{
+       int                     target_varno;
+       int                     sublevels_up;
+       RangeTblEntry *target_rte;
+       List       *targetlist;
+       int                     event;
+       int                     update_varno;
+       bool            inserted_sublink;
+} ResolveNew_context;
 
 static Node *
-make_null(Oid type)
+resolve_one_var(Var *var, ResolveNew_context *context)
 {
-       Const      *c = makeNode(Const);
+       TargetEntry *tle;
 
-       c->consttype = type;
-       c->constlen = get_typlen(type);
-       c->constvalue = PointerGetDatum(NULL);
-       c->constisnull = true;
-       c->constbyval = get_typbyval(type);
-       return (Node *) c;
-}
+       tle = get_tle_by_resno(context->targetlist, var->varattno);
 
-#ifdef NOT_USED
-void
-FixResdomTypes(List *tlist)
-{
-       List       *i;
-
-       foreach(i, tlist)
+       if (tle == NULL)
        {
-               TargetEntry *tle = lfirst(i);
-
-               if (nodeTag(tle->expr) == T_Var)
+               /* Failed to find column in insert/update tlist */
+               if (context->event == CMD_UPDATE)
                {
-                       Var                *var = (Var *) tle->expr;
-
-                       tle->resdom->restype = var->vartype;
-                       tle->resdom->restypmod = var->vartypmod;
+                       /* For update, just change unmatched var's varno */
+                       var = (Var *) copyObject(var);
+                       var->varno = context->update_varno;
+                       var->varnoold = context->update_varno;
+                       return (Node *) var;
+               }
+               else
+               {
+                       /* Otherwise replace unmatched var with a null */
+                       /* need coerce_to_domain in case of NOT NULL domain constraint */
+                       return coerce_to_domain((Node *) makeNullConst(var->vartype,
+                                                                                                                  var->vartypmod),
+                                                                       InvalidOid, -1,
+                                                                       var->vartype,
+                                                                       COERCE_IMPLICIT_CAST,
+                                                                       -1,
+                                                                       false,
+                                                                       false);
                }
        }
-}
-
-#endif
-
-/* Find a targetlist entry by resno */
-static Node *
-FindMatchingNew(List *tlist, int attno)
-{
-       List       *i;
-
-       foreach(i, tlist)
-       {
-               TargetEntry *tle = lfirst(i);
-
-               if (tle->resdom->resno == attno)
-                       return tle->expr;
-       }
-       return NULL;
-}
-
-/* Find a targetlist entry by resname */
-static Node *
-FindMatchingTLEntry(List *tlist, char *e_attname)
-{
-       List       *i;
-
-       foreach(i, tlist)
+       else
        {
-               TargetEntry *tle = lfirst(i);
-               char       *resname;
-
-               resname = tle->resdom->resname;
-               if (!strcmp(e_attname, resname))
-                       return tle->expr;
+               /* Make a copy of the tlist item to return */
+               Node       *n = copyObject(tle->expr);
+
+               /* Adjust varlevelsup if tlist item is from higher query */
+               if (var->varlevelsup > 0)
+                       IncrementVarSublevelsUp(n, var->varlevelsup, 0);
+               /* Report it if we are adding a sublink to query */
+               if (!context->inserted_sublink)
+                       context->inserted_sublink = checkExprHasSubLink(n);
+               return n;
        }
-       return NULL;
 }
 
-
-/*
- * ResolveNew - replace Vars with corresponding items from a targetlist
- *
- * Vars matching info->new_varno and sublevels_up are replaced by the
- * entry with matching resno from targetlist, if there is one.
- */
-
-typedef struct
-{
-       RewriteInfo *info;
-       List       *targetlist;
-       int                     sublevels_up;
-} ResolveNew_context;
-
 static Node *
 ResolveNew_mutator(Node *node, ResolveNew_context *context)
 {
@@ -694,250 +1050,120 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
                int                     this_varno = (int) var->varno;
                int                     this_varlevelsup = (int) var->varlevelsup;
 
-               if (this_varno == context->info->new_varno &&
+               if (this_varno == context->target_varno &&
                        this_varlevelsup == context->sublevels_up)
                {
-                       Node       *n = FindMatchingNew(context->targetlist,
-                                                                                       var->varattno);
-
-                       if (n == NULL)
-                       {
-                               if (context->info->event == CMD_UPDATE)
-                               {
-                                       /* For update, just change unmatched var's varno */
-                                       n = copyObject(node);
-                                       ((Var *) n)->varno = context->info->current_varno;
-                                       ((Var *) n)->varnoold = context->info->current_varno;
-                                       return n;
-                               }
-                               else
-                               {
-                                       /* Otherwise replace unmatched var with a null */
-                                       return make_null(var->vartype);
-                               }
-                       }
-                       else
+                       if (var->varattno == InvalidAttrNumber)
                        {
-                               /* Make a copy of the tlist item to return */
-                               n = copyObject(n);
-                               /* Adjust varlevelsup if tlist item is from higher query */
-                               if (this_varlevelsup > 0)
-                                       IncrementVarSublevelsUp(n, this_varlevelsup, 0);
-                               return n;
+                               /* Must expand whole-tuple reference into RowExpr */
+                               RowExpr    *rowexpr;
+                               List       *fields;
+
+                               /*
+                                * If generating an expansion for a var of a named rowtype
+                                * (ie, this is a plain relation RTE), then we must include
+                                * dummy items for dropped columns.  If the var is RECORD (ie,
+                                * this is a JOIN), then omit dropped columns.
+                                */
+                               expandRTE(context->target_rte,
+                                                 this_varno, this_varlevelsup, var->location,
+                                                 (var->vartype != RECORDOID),
+                                                 NULL, &fields);
+                               /* Adjust the generated per-field Vars... */
+                               fields = (List *) ResolveNew_mutator((Node *) fields,
+                                                                                                        context);
+                               rowexpr = makeNode(RowExpr);
+                               rowexpr->args = fields;
+                               rowexpr->row_typeid = var->vartype;
+                               rowexpr->row_format = COERCE_IMPLICIT_CAST;
+                               rowexpr->location = -1;
+
+                               return (Node *) rowexpr;
                        }
+
+                       /* Normal case for scalar variable */
+                       return resolve_one_var(var, context);
                }
                /* otherwise fall through to copy the var normally */
        }
-
-       /*
-        * Since expression_tree_mutator won't touch subselects, we have to
-        * handle them specially.
-        */
-       if (IsA(node, SubLink))
+       else if (IsA(node, CurrentOfExpr))
        {
-               SubLink    *sublink = (SubLink *) node;
-               SubLink    *newnode;
+               CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+               int                     this_varno = (int) cexpr->cvarno;
 
-               FLATCOPY(newnode, sublink, SubLink);
-               MUTATE(newnode->lefthand, sublink->lefthand, List *,
-                          ResolveNew_mutator, context);
-               context->sublevels_up++;
-               MUTATE(newnode->subselect, sublink->subselect, Node *,
-                          ResolveNew_mutator, context);
-               context->sublevels_up--;
-               return (Node *) newnode;
+               if (this_varno == context->target_varno &&
+                       context->sublevels_up == 0)
+               {
+                       /*
+                        * We get here if a WHERE CURRENT OF expression turns out to apply
+                        * to a view.  Someday we might be able to translate the
+                        * expression to apply to an underlying table of the view, but
+                        * right now it's not implemented.
+                        */
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                  errmsg("WHERE CURRENT OF on a view is not implemented")));
+               }
+               /* otherwise fall through to copy the expr normally */
        }
-       if (IsA(node, Query))
+       else if (IsA(node, Query))
        {
-               Query      *query = (Query *) node;
+               /* Recurse into RTE subquery or not-yet-planned sublink subquery */
                Query      *newnode;
+               bool            save_inserted_sublink;
 
-               FLATCOPY(newnode, query, Query);
-               MUTATE(newnode->targetList, query->targetList, List *,
-                          ResolveNew_mutator, context);
-               MUTATE(newnode->qual, query->qual, Node *,
-                          ResolveNew_mutator, context);
-               MUTATE(newnode->havingQual, query->havingQual, Node *,
-                          ResolveNew_mutator, context);
-               MUTATE(newnode->jointree, query->jointree, List *,
-                          ResolveNew_mutator, context);
+               context->sublevels_up++;
+               save_inserted_sublink = context->inserted_sublink;
+               context->inserted_sublink = false;
+               newnode = query_tree_mutator((Query *) node,
+                                                                        ResolveNew_mutator,
+                                                                        (void *) context,
+                                                                        0);
+               newnode->hasSubLinks |= context->inserted_sublink;
+               context->inserted_sublink = save_inserted_sublink;
+               context->sublevels_up--;
                return (Node *) newnode;
        }
        return expression_tree_mutator(node, ResolveNew_mutator,
                                                                   (void *) context);
 }
 
-static Node *
-ResolveNew(Node *node, RewriteInfo *info, List *targetlist,
-                  int sublevels_up)
+Node *
+ResolveNew(Node *node, int target_varno, int sublevels_up,
+                  RangeTblEntry *target_rte,
+                  List *targetlist, int event, int update_varno)
 {
+       Node       *result;
        ResolveNew_context context;
 
-       context.info = info;
-       context.targetlist = targetlist;
+       context.target_varno = target_varno;
        context.sublevels_up = sublevels_up;
-
-       return ResolveNew_mutator(node, &context);
-}
-
-void
-FixNew(RewriteInfo *info, Query *parsetree)
-{
-       info->rule_action->targetList = (List *)
-               ResolveNew((Node *) info->rule_action->targetList,
-                                  info, parsetree->targetList, 0);
-       info->rule_action->qual = ResolveNew(info->rule_action->qual,
-                                                                                info, parsetree->targetList, 0);
-       info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
-                                                                                          info, parsetree->targetList, 0);
-       info->rule_action->jointree = (List *)
-               ResolveNew((Node *) info->rule_action->jointree,
-                                  info, parsetree->targetList, 0);
-}
-
-/*
- * HandleRIRAttributeRule
- *     Replace Vars matching a given RT index with copies of TL expressions.
- *
- * Handles 'on retrieve to relation.attribute
- *                     do instead retrieve (attribute = expression) w/qual'
- *
- * XXX Why is this not unified with apply_RIR_view()?
- */
-
-typedef struct
-{
-       List       *rtable;
-       List       *targetlist;
-       int                     rt_index;
-       int                     attr_num;
-       int                *modified;
-       int                *badsql;
-       int                     sublevels_up;
-} HandleRIRAttributeRule_context;
-
-static Node *
-HandleRIRAttributeRule_mutator(Node *node,
-                                                          HandleRIRAttributeRule_context *context)
-{
-       if (node == NULL)
-               return NULL;
-       if (IsA(node, Var))
-       {
-               Var                *var = (Var *) node;
-               int                     this_varno = var->varno;
-               int                     this_varattno = var->varattno;
-               int                     this_varlevelsup = var->varlevelsup;
-
-               if (this_varno == context->rt_index &&
-                       this_varattno == context->attr_num &&
-                       this_varlevelsup == context->sublevels_up)
-               {
-                       if (var->vartype == 32)
-                       {                                       /* HACK: disallow SET variables */
-                               *context->modified = TRUE;
-                               *context->badsql = TRUE;
-                               return make_null(var->vartype);
-                       }
-                       else
-                       {
-                               char       *name_to_look_for;
-
-                               name_to_look_for = get_attname(getrelid(this_varno,
-                                                                                                               context->rtable),
-                                                                                          this_varattno);
-                               if (name_to_look_for)
-                               {
-                                       Node       *n;
-
-                                       *context->modified = TRUE;
-                                       n = FindMatchingTLEntry(context->targetlist,
-                                                                                       name_to_look_for);
-                                       if (n == NULL)
-                                               return make_null(var->vartype);
-                                       /* Make a copy of the tlist item to return */
-                                       n = copyObject(n);
-
-                                       /*
-                                        * Adjust varlevelsup if tlist item is from higher
-                                        * query
-                                        */
-                                       if (this_varlevelsup > 0)
-                                               IncrementVarSublevelsUp(n, this_varlevelsup, 0);
-                                       return n;
-                               }
-                       }
-               }
-               /* otherwise fall through to copy the var normally */
-       }
+       context.target_rte = target_rte;
+       context.targetlist = targetlist;
+       context.event = event;
+       context.update_varno = update_varno;
+       context.inserted_sublink = false;
 
        /*
-        * Since expression_tree_mutator won't touch subselects, we have to
-        * handle them specially.
+        * Must be prepared to start with a Query or a bare expression tree; if
+        * it's a Query, we don't want to increment sublevels_up.
         */
-       if (IsA(node, SubLink))
-       {
-               SubLink    *sublink = (SubLink *) node;
-               SubLink    *newnode;
+       result = query_or_expression_tree_mutator(node,
+                                                                                         ResolveNew_mutator,
+                                                                                         (void *) &context,
+                                                                                         0);
 
-               FLATCOPY(newnode, sublink, SubLink);
-               MUTATE(newnode->lefthand, sublink->lefthand, List *,
-                          HandleRIRAttributeRule_mutator, context);
-               context->sublevels_up++;
-               MUTATE(newnode->subselect, sublink->subselect, Node *,
-                          HandleRIRAttributeRule_mutator, context);
-               context->sublevels_up--;
-               return (Node *) newnode;
-       }
-       if (IsA(node, Query))
+       if (context.inserted_sublink)
        {
-               Query      *query = (Query *) node;
-               Query      *newnode;
+               if (IsA(result, Query))
+                       ((Query *) result)->hasSubLinks = true;
 
-               FLATCOPY(newnode, query, Query);
-               MUTATE(newnode->targetList, query->targetList, List *,
-                          HandleRIRAttributeRule_mutator, context);
-               MUTATE(newnode->qual, query->qual, Node *,
-                          HandleRIRAttributeRule_mutator, context);
-               MUTATE(newnode->havingQual, query->havingQual, Node *,
-                          HandleRIRAttributeRule_mutator, context);
-               MUTATE(newnode->jointree, query->jointree, List *,
-                          HandleRIRAttributeRule_mutator, context);
-               return (Node *) newnode;
+               /*
+                * Note: if we're called on a non-Query node then it's the caller's
+                * responsibility to update hasSubLinks in the ancestor Query. This is
+                * pretty fragile and perhaps should be rethought ...
+                */
        }
-       return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
-                                                                  (void *) context);
-}
 
-void
-HandleRIRAttributeRule(Query *parsetree,
-                                          List *rtable,
-                                          List *targetlist,
-                                          int rt_index,
-                                          int attr_num,
-                                          int *modified,
-                                          int *badsql)
-{
-       HandleRIRAttributeRule_context context;
-
-       context.rtable = rtable;
-       context.targetlist = targetlist;
-       context.rt_index = rt_index;
-       context.attr_num = attr_num;
-       context.modified = modified;
-       context.badsql = badsql;
-       context.sublevels_up = 0;
-
-       parsetree->targetList = (List *)
-               HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
-                                                                          &context);
-       parsetree->qual =
-               HandleRIRAttributeRule_mutator(parsetree->qual,
-                                                                          &context);
-       parsetree->havingQual =
-               HandleRIRAttributeRule_mutator(parsetree->havingQual,
-                                                                          &context);
-       parsetree->jointree = (List *)
-               HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
-                                                                          &context);
+       return result;
 }