]> granicus.if.org Git - postgresql/blobdiff - src/backend/rewrite/rewriteManip.c
Implement SEMI and ANTI joins in the planner and executor. (Semijoins replace
[postgresql] / src / backend / rewrite / rewriteManip.c
index 17c13d68585a4fd3245f2c9e46231b6d4eb00c19..60492fe3d091fa52b349f5190a082531ec5277ea 100644 (file)
@@ -2,24 +2,24 @@
  *
  * rewriteManip.c
  *
- * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * 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.80 2003/10/20 20:01:59 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.108 2008/08/14 18:47:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "catalog/pg_type.h"
 #include "nodes/makefuncs.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"
 
 
 typedef struct
@@ -36,8 +36,7 @@ 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.
  *
  * The objective of this routine is to detect whether there are aggregates
  * belonging to the initial query level.  Aggregates belonging to subqueries
@@ -53,8 +52,8 @@ checkExprHasAggs(Node *node)
        context.sublevels_up = 0;
 
        /*
-        * 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.
+        * 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.
         */
        return query_or_expression_tree_walker(node,
                                                                                   checkExprHasAggs_walker,
@@ -70,8 +69,7 @@ checkExprHasAggs_walker(Node *node, checkExprHasAggs_context *context)
        if (IsA(node, Aggref))
        {
                if (((Aggref *) node)->agglevelsup == context->sublevels_up)
-                       return true;            /* abort the tree traversal and return
-                                                                * true */
+                       return true;            /* abort the tree traversal and return true */
                /* else fall through to examine argument */
        }
        if (IsA(node, Query))
@@ -92,8 +90,7 @@ checkExprHasAggs_walker(Node *node, checkExprHasAggs_context *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)
@@ -114,8 +111,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);
 }
 
@@ -155,6 +151,14 @@ 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;
@@ -172,19 +176,30 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
                        j->rtindex += context->offset;
                /* fall through to examine children */
        }
-       if (IsA(node, InClauseInfo))
+       if (IsA(node, FlattenedSubLink))
        {
-               InClauseInfo *ininfo = (InClauseInfo *) node;
+               FlattenedSubLink *fslink = (FlattenedSubLink *) node;
 
                if (context->sublevels_up == 0)
                {
-                       ininfo->lefthand = offset_relid_set(ininfo->lefthand,
+                       fslink->lefthand = offset_relid_set(fslink->lefthand,
                                                                                                context->offset);
-                       ininfo->righthand = offset_relid_set(ininfo->righthand,
+                       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 */
@@ -209,28 +224,33 @@ 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      *qry = (Query *) node;
-               List       *l;
 
                /*
-                * 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 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)
-                               lfirsti(l) += offset;
+                       {
+                               RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+                               rc->rti += offset;
+                       }
                }
                query_tree_walker(qry, OffsetVarNodes_walker,
                                                  (void *) &context, 0);
@@ -290,6 +310,15 @@ 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;
@@ -309,21 +338,34 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
                        j->rtindex = context->new_index;
                /* fall through to examine children */
        }
-       if (IsA(node, InClauseInfo))
+       if (IsA(node, FlattenedSubLink))
        {
-               InClauseInfo *ininfo = (InClauseInfo *) node;
+               FlattenedSubLink *fslink = (FlattenedSubLink *) node;
 
                if (context->sublevels_up == 0)
                {
-                       ininfo->lefthand = adjust_relid_set(ininfo->lefthand,
+                       fslink->lefthand = adjust_relid_set(fslink->lefthand,
                                                                                                context->rt_index,
                                                                                                context->new_index);
-                       ininfo->righthand = adjust_relid_set(ininfo->righthand,
+                       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 */
@@ -349,30 +391,33 @@ 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      *qry = (Query *) node;
-               List       *l;
 
                /*
-                * 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 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)
                        {
-                               if (lfirsti(l) == rt_index)
-                                       lfirsti(l) = new_index;
+                               RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+                               if (rc->rti == rt_index)
+                                       rc->rti = new_index;
                        }
                }
                query_tree_walker(qry, ChangeVarNodes_walker,
@@ -438,6 +483,13 @@ IncrementVarSublevelsUp_walker(Node *node,
                        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;
@@ -472,8 +524,8 @@ 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, we don't want to increment sublevels_up.
+        * 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.
         */
        query_or_expression_tree_walker(node,
                                                                        IncrementVarSublevelsUp_walker,
@@ -508,6 +560,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;
@@ -527,16 +588,11 @@ rangeTableEntry_used_walker(Node *node,
                        return true;
                /* fall through to examine children */
        }
-       if (IsA(node, InClauseInfo))
-       {
-               InClauseInfo *ininfo = (InClauseInfo *) node;
+       /* Shouldn't need to handle planner auxiliary nodes here */
+       Assert(!IsA(node, FlattenedSubLink));
+       Assert(!IsA(node, SpecialJoinInfo));
+       Assert(!IsA(node, AppendRelInfo));
 
-               if (context->sublevels_up == 0 &&
-                       (bms_is_member(context->rt_index, ininfo->lefthand) ||
-                        bms_is_member(context->rt_index, ininfo->righthand)))
-                       return true;
-               /* fall through to examine children */
-       }
        if (IsA(node, Query))
        {
                /* Recurse into subselects */
@@ -561,8 +617,8 @@ 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, we don't want to increment sublevels_up.
+        * 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.
         */
        return query_or_expression_tree_walker(node,
                                                                                   rangeTableEntry_used_walker,
@@ -625,8 +681,8 @@ 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, we don't want to increment sublevels_up.
+        * 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.
         */
        return query_or_expression_tree_walker(node,
                                                                                   attribute_used_walker,
@@ -669,27 +725,27 @@ getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
         * query.  If they're not there, it must be an INSERT/SELECT in which
         * they've been pushed down to the SELECT.
         */
-       if (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)
+       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 (length(parsetree->jointree->fromlist) != 1)
+       if (list_length(parsetree->jointree->fromlist) != 1)
                elog(ERROR, "expected to find SELECT subquery");
-       rtr = (RangeTblRef *) lfirst(parsetree->jointree->fromlist);
+       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 (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 (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);
@@ -717,29 +773,29 @@ AddQual(Query *parsetree, Node *qual)
                 * 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.
+                * 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.)
+                * 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")));
+                         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.)
+                * 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),
@@ -753,68 +809,14 @@ AddQual(Query *parsetree, Node *qual)
                                                                                           copy);
 
        /*
-        * Make sure query is marked correctly if added qual has sublinks or
-        * aggregates (not sure it can ever have aggs, but sublinks
-        * definitely).  Need not search qual when query is already marked.
+        * We had better not have stuck an aggregate into the WHERE clause.
         */
-       if (!parsetree->hasAggs)
-               parsetree->hasAggs = checkExprHasAggs(copy);
-       if (!parsetree->hasSubLinks)
-               parsetree->hasSubLinks = checkExprHasSubLink(copy);
-}
-
-/*
- * Add the given havingQual to the one already contained in the parsetree
- * just as AddQual does for the normal 'where' qual
- */
-void
-AddHavingQual(Query *parsetree, Node *havingQual)
-{
-       Node       *copy;
-
-       if (havingQual == NULL)
-               return;
-
-       if (parsetree->commandType == CMD_UTILITY)
-       {
-               /*
-                * There's noplace to put the qual on a utility statement.
-                *
-                * See comments in AddQual for motivation.
-                */
-               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);
-
-       parsetree->havingQual = make_and_qual(parsetree->havingQual,
-                                                                                 copy);
+       Assert(!checkExprHasAggs(copy));
 
        /*
-        * Make sure query is marked correctly if added qual has sublinks or
-        * aggregates (not sure it can ever have aggs, but sublinks
-        * definitely).  Need not search qual when query is already marked.
+        * Make sure query is marked correctly if added qual has sublinks. Need
+        * not search qual when query is already marked.
         */
-       if (!parsetree->hasAggs)
-               parsetree->hasAggs = checkExprHasAggs(copy);
        if (!parsetree->hasSubLinks)
                parsetree->hasSubLinks = checkExprHasSubLink(copy);
 }
@@ -850,6 +852,10 @@ AddInvertedQual(Query *parsetree, Node *qual)
  * If not, we either change the unmatched Var's varno to update_varno
  * (when event == CMD_UPDATE) or replace it with a constant NULL.
  *
+ * 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.
+ *
  * 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,
@@ -861,12 +867,59 @@ 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 *
+resolve_one_var(Var *var, ResolveNew_context *context)
+{
+       TargetEntry *tle;
+
+       tle = get_tle_by_resno(context->targetlist, var->varattno);
+
+       if (tle == NULL)
+       {
+               /* Failed to find column in insert/update tlist */
+               if (context->event == CMD_UPDATE)
+               {
+                       /* 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,
+                                                                       false,
+                                                                       false);
+               }
+       }
+       else
+       {
+               /* 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;
+       }
+}
+
 static Node *
 ResolveNew_mutator(Node *node, ResolveNew_context *context)
 {
@@ -881,50 +934,59 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
                if (this_varno == context->target_varno &&
                        this_varlevelsup == context->sublevels_up)
                {
-                       TargetEntry *tle;
-
-                       /* band-aid: don't do the wrong thing with a whole-tuple Var */
                        if (var->varattno == InvalidAttrNumber)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("cannot handle whole-row reference")));
-
-                       tle = get_tle_by_resno(context->targetlist, var->varattno);
-
-                       if (tle == NULL)
-                       {
-                               if (context->event == CMD_UPDATE)
-                               {
-                                       /* For update, just change unmatched var's varno */
-                                       var = (Var *) copyObject(node);
-                                       var->varno = context->update_varno;
-                                       var->varnoold = context->update_varno;
-                                       return (Node *) var;
-                               }
-                               else
-                               {
-                                       /* Otherwise replace unmatched var with a null */
-                                       return (Node *) makeNullConst(var->vartype);
-                               }
-                       }
-                       else
                        {
-                               /* Make a copy of the tlist item to return */
-                               Node       *n = copyObject(tle->expr);
-
-                               /* Adjust varlevelsup if tlist item is from higher query */
-                               if (this_varlevelsup > 0)
-                                       IncrementVarSublevelsUp(n, this_varlevelsup, 0);
-                               /* Report it if we are adding a sublink to query */
-                               if (!context->inserted_sublink)
-                                       context->inserted_sublink = checkExprHasSubLink(n);
-                               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->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;
+
+                               return (Node *) rowexpr;
                        }
+
+                       /* Normal case for scalar variable */
+                       return resolve_one_var(var, context);
                }
                /* otherwise fall through to copy the var normally */
        }
+       else if (IsA(node, CurrentOfExpr))
+       {
+               CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
+               int                     this_varno = (int) cexpr->cvarno;
 
-       if (IsA(node, Query))
+               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 */
+       }
+       else if (IsA(node, Query))
        {
                /* Recurse into RTE subquery or not-yet-planned sublink subquery */
                Query      *newnode;
@@ -948,23 +1010,40 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 
 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.target_varno = target_varno;
        context.sublevels_up = sublevels_up;
+       context.target_rte = target_rte;
        context.targetlist = targetlist;
        context.event = event;
        context.update_varno = update_varno;
        context.inserted_sublink = false;
 
        /*
-        * 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.
+        * 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.
         */
-       return query_or_expression_tree_mutator(node,
-                                                                                       ResolveNew_mutator,
-                                                                                       (void *) &context,
-                                                                                       0);
+       result = query_or_expression_tree_mutator(node,
+                                                                                         ResolveNew_mutator,
+                                                                                         (void *) &context,
+                                                                                         0);
+
+       if (context.inserted_sublink)
+       {
+               if (IsA(result, Query))
+                       ((Query *) result)->hasSubLinks = true;
+
+               /*
+                * 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 result;
 }