Fix whole-row Var evaluation to cope with resjunk columns (again).
authorTom Lane <[email protected]>
Fri, 20 Jul 2012 17:09:32 +0000 (13:09 -0400)
committerTom Lane <[email protected]>
Fri, 20 Jul 2012 17:09:32 +0000 (13:09 -0400)
When a whole-row Var is reading the result of a subquery, we need it to
ignore any "resjunk" columns that the subquery might have evaluated for
GROUP BY or ORDER BY purposes.  We've hacked this area before, in commit
68e40998d058c1f6662800a648ff1e1ce5d99cba, but that fix only covered
whole-row Vars of named composite types, not those of RECORD type; and it
was mighty klugy anyway, since it just assumed without checking that any
extra columns in the result must be resjunk.  A proper fix requires getting
hold of the subquery's targetlist so we can actually see which columns are
resjunk (whereupon we can use a JunkFilter to get rid of them).  So bite
the bullet and add some infrastructure to make that possible.

Per report from Andrew Dunstan and additional testing by Merlin Moncure.
Back-patch to all supported branches.  In 8.3, also back-patch commit
292176a118da6979e5d368a4baf27f26896c99a5, which for some reason I had
not done at the time, but it's a prerequisite for this change.

src/backend/executor/execQual.c
src/backend/executor/execUtils.c
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/test/regress/expected/subselect.out
src/test/regress/sql/subselect.sql

index 1c792387de7012023c0d807f3eace3c70941fc76..99e04ae27a8a115dbd02294365bbf3eee44438a5 100644 (file)
@@ -20,7 +20,7 @@
  *     ExecProject     - form a new tuple by projecting the given tuple
  *
  *  NOTES
- *     The more heavily used ExecEvalExpr routines, such as ExecEvalVar(),
+ *     The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar,
  *     are hotspots. Making these faster will speed up the entire system.
  *
  *     ExecProject() is used to make tuple projections.  Rather then
@@ -63,13 +63,18 @@ static bool isAssignmentIndirectionExpr(ExprState *exprstate);
 static Datum ExecEvalAggref(AggrefExprState *aggref,
               ExprContext *econtext,
               bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
-           bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
                  bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
+static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
+                     bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
+                   ExprContext *econtext,
                    bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
+static Datum ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate,
+                    ExprContext *econtext,
+                    bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate,
+                    ExprContext *econtext,
                     bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
              bool *isNull, ExprDoneCond *isDone);
@@ -514,20 +519,19 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
 }
 
 /* ----------------------------------------------------------------
- *     ExecEvalVar
+ *     ExecEvalScalarVar
  *
- *     Returns a Datum whose value is the value of a range
- *     variable with respect to given expression context.
+ *     Returns a Datum whose value is the value of a scalar (not whole-row)
+ *     range variable with respect to given expression context.
  *
- * Note: ExecEvalVar is executed only the first time through in a given plan;
- * it changes the ExprState's function pointer to pass control directly to
- * ExecEvalScalarVar, ExecEvalWholeRowVar, or ExecEvalWholeRowSlow after
- * making one-time checks.
+ * Note: ExecEvalScalarVar is executed only the first time through in a given
+ * plan; it changes the ExprState's function pointer to pass control directly
+ * to ExecEvalScalarVarFast after making one-time checks.
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
-           bool *isNull, ExprDoneCond *isDone)
+ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
+                 bool *isNull, ExprDoneCond *isDone)
 {
    Var        *variable = (Var *) exprstate->expr;
    TupleTableSlot *slot;
@@ -564,157 +568,65 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
            break;
    }
 
-   if (attnum != InvalidAttrNumber)
-   {
-       /*
-        * Scalar variable case.
-        *
-        * If it's a user attribute, check validity (bogus system attnums will
-        * be caught inside slot_getattr).  What we have to check for here is
-        * the possibility of an attribute having been changed in type since
-        * the plan tree was created.  Ideally the plan would get invalidated
-        * and not re-used, but until that day arrives, we need defenses.
-        * Fortunately it's sufficient to check once on the first time
-        * through.
-        *
-        * Note: we allow a reference to a dropped attribute.  slot_getattr
-        * will force a NULL result in such cases.
-        *
-        * Note: ideally we'd check typmod as well as typid, but that seems
-        * impractical at the moment: in many cases the tupdesc will have been
-        * generated by ExecTypeFromTL(), and that can't guarantee to generate
-        * an accurate typmod in all cases, because some expression node types
-        * don't carry typmod.
-        */
-       if (attnum > 0)
-       {
-           TupleDesc   slot_tupdesc = slot->tts_tupleDescriptor;
-           Form_pg_attribute attr;
-
-           if (attnum > slot_tupdesc->natts)   /* should never happen */
-               elog(ERROR, "attribute number %d exceeds number of columns %d",
-                    attnum, slot_tupdesc->natts);
+   /* This was checked by ExecInitExpr */
+   Assert(attnum != InvalidAttrNumber);
 
-           attr = slot_tupdesc->attrs[attnum - 1];
-
-           /* can't check type if dropped, since atttypid is probably 0 */
-           if (!attr->attisdropped)
-           {
-               if (variable->vartype != attr->atttypid)
-                   ereport(ERROR,
-                           (errmsg("attribute %d has wrong type", attnum),
-                       errdetail("Table has type %s, but query expects %s.",
-                                 format_type_be(attr->atttypid),
-                                 format_type_be(variable->vartype))));
-           }
-       }
-
-       /* Skip the checking on future executions of node */
-       exprstate->evalfunc = ExecEvalScalarVar;
-
-       /* Fetch the value from the slot */
-       return slot_getattr(slot, attnum, isNull);
-   }
-   else
+   /*
+    * If it's a user attribute, check validity (bogus system attnums will be
+    * caught inside slot_getattr).  What we have to check for here is the
+    * possibility of an attribute having been changed in type since the plan
+    * tree was created.  Ideally the plan will get invalidated and not
+    * re-used, but just in case, we keep these defenses.  Fortunately it's
+    * sufficient to check once on the first time through.
+    *
+    * Note: we allow a reference to a dropped attribute.  slot_getattr will
+    * force a NULL result in such cases.
+    *
+    * Note: ideally we'd check typmod as well as typid, but that seems
+    * impractical at the moment: in many cases the tupdesc will have been
+    * generated by ExecTypeFromTL(), and that can't guarantee to generate an
+    * accurate typmod in all cases, because some expression node types don't
+    * carry typmod.
+    */
+   if (attnum > 0)
    {
-       /*
-        * Whole-row variable.
-        *
-        * If it's a RECORD Var, we'll use the slot's type ID info.  It's
-        * likely that the slot's type is also RECORD; if so, make sure it's
-        * been "blessed", so that the Datum can be interpreted later.
-        *
-        * If the Var identifies a named composite type, we must check that
-        * the actual tuple type is compatible with it.
-        */
        TupleDesc   slot_tupdesc = slot->tts_tupleDescriptor;
-       bool        needslow = false;
+       Form_pg_attribute attr;
 
-       if (variable->vartype == RECORDOID)
-       {
-           if (slot_tupdesc->tdtypeid == RECORDOID &&
-               slot_tupdesc->tdtypmod < 0)
-               assign_record_type_typmod(slot_tupdesc);
-       }
-       else
-       {
-           TupleDesc   var_tupdesc;
-           int         i;
+       if (attnum > slot_tupdesc->natts)       /* should never happen */
+           elog(ERROR, "attribute number %d exceeds number of columns %d",
+                attnum, slot_tupdesc->natts);
 
-           /*
-            * We really only care about number of attributes and data type.
-            * Also, we can ignore type mismatch on columns that are dropped
-            * in the destination type, so long as the physical storage
-            * matches.  This is helpful in some cases involving out-of-date
-            * cached plans.  Also, we have to allow the case that the slot
-            * has more columns than the Var's type, because we might be
-            * looking at the output of a subplan that includes resjunk
-            * columns.  (XXX it would be nice to verify that the extra
-            * columns are all marked resjunk, but we haven't got access to
-            * the subplan targetlist here...)  Resjunk columns should always
-            * be at the end of a targetlist, so it's sufficient to ignore
-            * them here; but we need to use ExecEvalWholeRowSlow to get rid
-            * of them in the eventual output tuples.
-            */
-           var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+       attr = slot_tupdesc->attrs[attnum - 1];
 
-           if (var_tupdesc->natts > slot_tupdesc->natts)
+       /* can't check type if dropped, since atttypid is probably 0 */
+       if (!attr->attisdropped)
+       {
+           if (variable->vartype != attr->atttypid)
                ereport(ERROR,
-                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                        errmsg("table row type and query-specified row type do not match"),
-                        errdetail("Table row contains %d attributes, but query expects %d.",
-                                  slot_tupdesc->natts, var_tupdesc->natts)));
-           else if (var_tupdesc->natts < slot_tupdesc->natts)
-               needslow = true;
-
-           for (i = 0; i < var_tupdesc->natts; i++)
-           {
-               Form_pg_attribute vattr = var_tupdesc->attrs[i];
-               Form_pg_attribute sattr = slot_tupdesc->attrs[i];
-
-               if (vattr->atttypid == sattr->atttypid)
-                   continue;   /* no worries */
-               if (!vattr->attisdropped)
-                   ereport(ERROR,
-                           (errcode(ERRCODE_DATATYPE_MISMATCH),
-                            errmsg("table row type and query-specified row type do not match"),
-                            errdetail("Table has type %s at ordinal position %d, but query expects %s.",
-                                      format_type_be(sattr->atttypid),
-                                      i + 1,
-                                      format_type_be(vattr->atttypid))));
-
-               if (vattr->attlen != sattr->attlen ||
-                   vattr->attalign != sattr->attalign)
-                   ereport(ERROR,
-                           (errcode(ERRCODE_DATATYPE_MISMATCH),
-                            errmsg("table row type and query-specified row type do not match"),
-                            errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
-                                      i + 1)));
-           }
-
-           ReleaseTupleDesc(var_tupdesc);
+                       (errmsg("attribute %d has wrong type", attnum),
+                        errdetail("Table has type %s, but query expects %s.",
+                                  format_type_be(attr->atttypid),
+                                  format_type_be(variable->vartype))));
        }
+   }
 
-       /* Skip the checking on future executions of node */
-       if (needslow)
-           exprstate->evalfunc = ExecEvalWholeRowSlow;
-       else
-           exprstate->evalfunc = ExecEvalWholeRowVar;
+   /* Skip the checking on future executions of node */
+   exprstate->evalfunc = ExecEvalScalarVarFast;
 
-       /* Fetch the value */
-       return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone);
-   }
+   /* Fetch the value from the slot */
+   return slot_getattr(slot, attnum, isNull);
 }
 
 /* ----------------------------------------------------------------
- *     ExecEvalScalarVar
+ *     ExecEvalScalarVarFast
  *
  *     Returns a Datum for a scalar variable.
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
-                 bool *isNull, ExprDoneCond *isDone)
+ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
+                     bool *isNull, ExprDoneCond *isDone)
 {
    Var        *variable = (Var *) exprstate->expr;
    TupleTableSlot *slot;
@@ -749,14 +661,184 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
 /* ----------------------------------------------------------------
  *     ExecEvalWholeRowVar
  *
- *     Returns a Datum for a whole-row variable.
+ *     Returns a Datum whose value is the value of a whole-row range
+ *     variable with respect to given expression context.
+ *
+ * Note: ExecEvalWholeRowVar is executed only the first time through in a
+ * given plan; it changes the ExprState's function pointer to pass control
+ * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making
+ * one-time checks.
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
+ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
                    bool *isNull, ExprDoneCond *isDone)
 {
-   Var        *variable = (Var *) exprstate->expr;
+   Var        *variable = (Var *) wrvstate->xprstate.expr;
+   TupleTableSlot *slot;
+   TupleDesc   slot_tupdesc;
+   bool        needslow = false;
+
+   if (isDone)
+       *isDone = ExprSingleResult;
+
+   /* This was checked by ExecInitExpr */
+   Assert(variable->varattno == InvalidAttrNumber);
+
+   /* Get the input slot we want */
+   Assert(variable->varno != INNER);
+   Assert(variable->varno != OUTER);
+   slot = econtext->ecxt_scantuple;
+
+   /*
+    * If the input tuple came from a subquery, it might contain "resjunk"
+    * columns (such as GROUP BY or ORDER BY columns), which we don't want to
+    * keep in the whole-row result.  We can get rid of such columns by
+    * passing the tuple through a JunkFilter --- but to make one, we have to
+    * lay our hands on the subquery's targetlist.  Fortunately, there are not
+    * very many cases where this can happen, and we can identify all of them
+    * by examining our parent PlanState.  We assume this is not an issue in
+    * standalone expressions that don't have parent plans.  (Whole-row Vars
+    * can occur in such expressions, but they will always be referencing
+    * table rows.)
+    */
+   if (wrvstate->parent)
+   {
+       PlanState  *subplan = NULL;
+
+       switch (nodeTag(wrvstate->parent))
+       {
+           case T_SubqueryScanState:
+               subplan = ((SubqueryScanState *) wrvstate->parent)->subplan;
+               break;
+           default:
+               break;
+       }
+
+       if (subplan)
+       {
+           bool        junk_filter_needed = false;
+           ListCell   *tlist;
+
+           /* Detect whether subplan tlist actually has any junk columns */
+           foreach(tlist, subplan->plan->targetlist)
+           {
+               TargetEntry *tle = (TargetEntry *) lfirst(tlist);
+
+               if (tle->resjunk)
+               {
+                   junk_filter_needed = true;
+                   break;
+               }
+           }
+
+           /* If so, build the junkfilter in the query memory context */
+           if (junk_filter_needed)
+           {
+               MemoryContext oldcontext;
+
+               oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+               wrvstate->wrv_junkFilter =
+                   ExecInitJunkFilter(subplan->plan->targetlist,
+                                      ExecGetResultType(subplan)->tdhasoid,
+                                      NULL);
+               MemoryContextSwitchTo(oldcontext);
+           }
+       }
+   }
+
+   /* Apply the junkfilter if any */
+   if (wrvstate->wrv_junkFilter != NULL)
+       slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
+   slot_tupdesc = slot->tts_tupleDescriptor;
+
+   /*
+    * If it's a RECORD Var, we'll use the slot's type ID info.  It's likely
+    * that the slot's type is also RECORD; if so, make sure it's been
+    * "blessed", so that the Datum can be interpreted later.
+    *
+    * If the Var identifies a named composite type, we must check that the
+    * actual tuple type is compatible with it.
+    */
+   if (variable->vartype == RECORDOID)
+   {
+       if (slot_tupdesc->tdtypeid == RECORDOID &&
+           slot_tupdesc->tdtypmod < 0)
+           assign_record_type_typmod(slot_tupdesc);
+   }
+   else
+   {
+       TupleDesc   var_tupdesc;
+       int         i;
+
+       /*
+        * We really only care about numbers of attributes and data types.
+        * Also, we can ignore type mismatch on columns that are dropped in
+        * the destination type, so long as (1) the physical storage matches
+        * or (2) the actual column value is NULL.  Case (1) is helpful in
+        * some cases involving out-of-date cached plans, while case (2) is
+        * expected behavior in situations such as an INSERT into a table with
+        * dropped columns (the planner typically generates an INT4 NULL
+        * regardless of the dropped column type).  If we find a dropped
+        * column and cannot verify that case (1) holds, we have to use
+        * ExecEvalWholeRowSlow to check (2) for each row.
+        */
+       var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+
+       if (var_tupdesc->natts != slot_tupdesc->natts)
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("table row type and query-specified row type do not match"),
+                    errdetail("Table row contains %d attributes, but query expects %d.",
+                              slot_tupdesc->natts, var_tupdesc->natts)));
+
+       for (i = 0; i < var_tupdesc->natts; i++)
+       {
+           Form_pg_attribute vattr = var_tupdesc->attrs[i];
+           Form_pg_attribute sattr = slot_tupdesc->attrs[i];
+
+           if (vattr->atttypid == sattr->atttypid)
+               continue;       /* no worries */
+           if (!vattr->attisdropped)
+               ereport(ERROR,
+                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                        errmsg("table row type and query-specified row type do not match"),
+                        errdetail("Table has type %s at ordinal position %d, but query expects %s.",
+                                  format_type_be(sattr->atttypid),
+                                  i + 1,
+                                  format_type_be(vattr->atttypid))));
+
+           if (vattr->attlen != sattr->attlen ||
+               vattr->attalign != sattr->attalign)
+               needslow = true;    /* need runtime check for null */
+       }
+
+       ReleaseTupleDesc(var_tupdesc);
+   }
+
+   /* Skip the checking on future executions of node */
+   if (needslow)
+       wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
+   else
+       wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast;
+
+   /* Fetch the value */
+   return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext,
+                                          isNull, isDone);
+}
+
+/* ----------------------------------------------------------------
+ *     ExecEvalWholeRowFast
+ *
+ *     Returns a Datum for a whole-row variable.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
+                    bool *isNull, ExprDoneCond *isDone)
+{
+   Var        *variable = (Var *) wrvstate->xprstate.expr;
    TupleTableSlot *slot = econtext->ecxt_scantuple;
    HeapTuple   tuple;
    TupleDesc   tupleDesc;
@@ -766,6 +848,10 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
        *isDone = ExprSingleResult;
    *isNull = false;
 
+   /* Apply the junkfilter if any */
+   if (wrvstate->wrv_junkFilter != NULL)
+       slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
    tuple = ExecFetchSlotTuple(slot);
    tupleDesc = slot->tts_tupleDescriptor;
 
@@ -804,36 +890,53 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
+ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
                     bool *isNull, ExprDoneCond *isDone)
 {
-   Var        *variable = (Var *) exprstate->expr;
+   Var        *variable = (Var *) wrvstate->xprstate.expr;
    TupleTableSlot *slot = econtext->ecxt_scantuple;
    HeapTuple   tuple;
+   TupleDesc   tupleDesc;
    TupleDesc   var_tupdesc;
    HeapTupleHeader dtuple;
+   int         i;
 
    if (isDone)
        *isDone = ExprSingleResult;
    *isNull = false;
 
-   /*
-    * Currently, the only case handled here is stripping of trailing resjunk
-    * fields, which we do in a slightly chintzy way by just adjusting the
-    * tuple's natts header field.  Possibly there will someday be a need for
-    * more-extensive rearrangements, in which case it'd be worth
-    * disassembling and reassembling the tuple (perhaps use a JunkFilter for
-    * that?)
-    */
+   /* Apply the junkfilter if any */
+   if (wrvstate->wrv_junkFilter != NULL)
+       slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
+   tuple = ExecFetchSlotTuple(slot);
+   tupleDesc = slot->tts_tupleDescriptor;
+
    Assert(variable->vartype != RECORDOID);
    var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
 
-   tuple = ExecFetchSlotTuple(slot);
+   /* Check to see if any dropped attributes are non-null */
+   for (i = 0; i < var_tupdesc->natts; i++)
+   {
+       Form_pg_attribute vattr = var_tupdesc->attrs[i];
+       Form_pg_attribute sattr = tupleDesc->attrs[i];
+
+       if (!vattr->attisdropped)
+           continue;           /* already checked non-dropped cols */
+       if (heap_attisnull(tuple, i+1))
+           continue;           /* null is always okay */
+       if (vattr->attlen != sattr->attlen ||
+           vattr->attalign != sattr->attalign)
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("table row type and query-specified row type do not match"),
+                    errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+                              i + 1)));
+   }
 
    /*
     * We have to make a copy of the tuple so we can safely insert the Datum
-    * overhead fields, which are not set in on-disk tuples; not to mention
-    * fooling with its natts field.
+    * overhead fields, which are not set in on-disk tuples.
     */
    dtuple = (HeapTupleHeader) palloc(tuple->t_len);
    memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
@@ -842,9 +945,6 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
    HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
    HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
 
-   Assert(HeapTupleHeaderGetNatts(dtuple) >= var_tupdesc->natts);
-   HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
-
    ReleaseTupleDesc(var_tupdesc);
 
    return PointerGetDatum(dtuple);
@@ -3541,7 +3641,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
    }
 
    /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
-   /* As in ExecEvalVar, we should but can't check typmod */
+   /* As in ExecEvalScalarVar, we should but can't check typmod */
    if (fselect->resulttype != attr->atttypid)
        ereport(ERROR,
                (errmsg("attribute %d has wrong type", fieldnum),
@@ -3876,8 +3976,21 @@ ExecInitExpr(Expr *node, PlanState *parent)
    switch (nodeTag(node))
    {
        case T_Var:
-           state = (ExprState *) makeNode(ExprState);
-           state->evalfunc = ExecEvalVar;
+           /* varattno == InvalidAttrNumber means it's a whole-row Var */
+           if (((Var *) node)->varattno == InvalidAttrNumber)
+           {
+               WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
+
+               wstate->parent = parent;
+               wstate->wrv_junkFilter = NULL;
+               state = (ExprState *) wstate;
+               state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
+           }
+           else
+           {
+               state = (ExprState *) makeNode(ExprState);
+               state->evalfunc = ExecEvalScalarVar;
+           }
            break;
        case T_Const:
            state = (ExprState *) makeNode(ExprState);
index d149e1280f782b425059ec11b98442a40e4855e3..b6a82566a37efd1708de07bd811b9458a0ba4940 100644 (file)
@@ -575,7 +575,7 @@ ExecBuildProjectionInfo(List *targetList,
     * Determine whether the target list consists entirely of simple Var
     * references (ie, references to non-system attributes) that match the
     * input.  If so, we can use the simpler ExecVariableList instead of
-    * ExecTargetList.  (Note: if there is a type mismatch then ExecEvalVar
+    * ExecTargetList.  (Note: if there is a type mismatch then ExecEvalScalarVar
     * will probably throw an error at runtime, but we leave that to it.)
     */
    isVarList = true;
index ba588b0767a5d7cc6b424ec1201c0bbd1a2143ec..02318794ea4a879608774fd8690e468709dad9e7 100644 (file)
@@ -491,6 +491,17 @@ typedef struct GenericExprState
    ExprState  *arg;            /* state of my child node */
 } GenericExprState;
 
+/* ----------------
+ *     WholeRowVarExprState node
+ * ----------------
+ */
+typedef struct WholeRowVarExprState
+{
+   ExprState   xprstate;
+   struct PlanState *parent;   /* parent PlanState, or NULL if none */
+   JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */
+} WholeRowVarExprState;
+
 /* ----------------
  *     AggrefExprState node
  * ----------------
index 5a6745a2141d911ff4b82a6ee51683a10d84637d..3025368e49e6992c97937e9ead04f69f6686aea9 100644 (file)
@@ -176,6 +176,7 @@ typedef enum NodeTag
    T_NullTestState,
    T_CoerceToDomainState,
    T_DomainConstraintState,
+   T_WholeRowVarExprState,     /* will be in a more natural position in 9.3 */
 
    /*
     * TAGS FOR PLANNER NODES (relation.h)
index 468829a572c665f2fa56fc1e9d5f73b154e49ccd..056e05203b9112e1447cf89f0e48f88a0c64699c 100644 (file)
@@ -478,6 +478,20 @@ group by f1,f2,fs;
 ----+----+----
 (0 rows)
 
+--
+-- Check that whole-row Vars reading the result of a subselect don't include
+-- any junk columns therein
+--
+select q from (select max(f1) from int4_tbl group by f1 order by f1) q;
+       q       
+---------------
+ (-2147483647)
+ (-123456)
+ (0)
+ (123456)
+ (2147483647)
+(5 rows)
+
 --
 -- Test case for sublinks pushed down into subselects via join alias expansion
 --
index 6e1eda157a433acd0d6e8a5a14ba018b2f1a82e2..8b706c13bd9facf4447c94cceb6a8e84dfd1e97d 100644 (file)
@@ -310,6 +310,13 @@ select * from
    from t1 up) ss
 group by f1,f2,fs;
 
+--
+-- Check that whole-row Vars reading the result of a subselect don't include
+-- any junk columns therein
+--
+
+select q from (select max(f1) from int4_tbl group by f1 order by f1) q;
+
 --
 -- Test case for sublinks pushed down into subselects via join alias expansion
 --