]> granicus.if.org Git - postgresql/commitdiff
Fix whole-row Var evaluation to cope with resjunk columns (again).
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 20 Jul 2012 17:09:10 +0000 (13:09 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 20 Jul 2012 17:09:10 +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 0ea21ca5f91a704ccde2c765bd92d0c5af7ead8b..85c8d15560f6c040ffd4ac769fd5c266e923cb8a 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
@@ -68,13 +68,18 @@ static Datum ExecEvalAggref(AggrefExprState *aggref,
 static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
                                   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);
@@ -553,20 +558,19 @@ ExecEvalWindowFunc(WindowFuncExprState *wfunc, 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;
@@ -596,162 +600,65 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
 
        attnum = variable->varattno;
 
-       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;
+       /* This was checked by ExecInitExpr */
+       Assert(attnum != InvalidAttrNumber);
 
-                       if (attnum > slot_tupdesc->natts)       /* should never happen */
-                               elog(ERROR, "attribute number %d exceeds number of columns %d",
-                                        attnum, slot_tupdesc->natts);
-
-                       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 (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.  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_plural("Table row contains %d attribute, but query expects %d.",
-                                  "Table row contains %d attributes, but query expects %d.",
-                                                                                 slot_tupdesc->natts,
-                                                                                 slot_tupdesc->natts,
-                                                                                 var_tupdesc->natts)));
-                       else if (var_tupdesc->natts < slot_tupdesc->natts)
-                               needslow = true;        /* need to trim trailing atts */
-
-                       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);
+                                               (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 (*exprstate->evalfunc) (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;
@@ -788,14 +695,204 @@ 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 */
+       switch (variable->varno)
+       {
+               case INNER_VAR: /* get the tuple from the inner node */
+                       slot = econtext->ecxt_innertuple;
+                       break;
+
+               case OUTER_VAR: /* get the tuple from the outer node */
+                       slot = econtext->ecxt_outertuple;
+                       break;
+
+                       /* INDEX_VAR is handled by default case */
+
+               default:                                /* get the tuple from the relation being
+                                                                * scanned */
+                       slot = econtext->ecxt_scantuple;
+                       break;
+       }
+
+       /*
+        * 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;
+                       case T_CteScanState:
+                               subplan = ((CteScanState *) wrvstate->parent)->cteplanstate;
+                               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,
+                                                       ExecInitExtraTupleSlot(wrvstate->parent->state));
+                               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_plural("Table row contains %d attribute, but query expects %d.",
+                                  "Table row contains %d attributes, but query expects %d.",
+                                                                         slot_tupdesc->natts,
+                                                                         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;
        HeapTuple       tuple;
        TupleDesc       tupleDesc;
@@ -824,6 +921,10 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
                        break;
        }
 
+       /* Apply the junkfilter if any */
+       if (wrvstate->wrv_junkFilter != NULL)
+               slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
        tuple = ExecFetchSlotTuple(slot);
        tupleDesc = slot->tts_tupleDescriptor;
 
@@ -857,17 +958,18 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
 /* ----------------------------------------------------------------
  *             ExecEvalWholeRowSlow
  *
- *             Returns a Datum for a whole-row variable, in the "slow" cases where
+ *             Returns a Datum for a whole-row variable, in the "slow" case where
  *             we can't just copy the subplan's output.
  * ----------------------------------------------------------------
  */
 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;
        HeapTuple       tuple;
+       TupleDesc       tupleDesc;
        TupleDesc       var_tupdesc;
        HeapTupleHeader dtuple;
        int                     i;
@@ -895,25 +997,21 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
                        break;
        }
 
-       /*
-        * Currently, the only data modification 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 we'd
-        * probably use tupconvert.c.
-        */
-       Assert(variable->vartype != RECORDOID);
-       var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+       /* Apply the junkfilter if any */
+       if (wrvstate->wrv_junkFilter != NULL)
+               slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
 
        tuple = ExecFetchSlotTuple(slot);
+       tupleDesc = slot->tts_tupleDescriptor;
 
-       Assert(HeapTupleHeaderGetNatts(tuple->t_data) >= var_tupdesc->natts);
+       Assert(variable->vartype != RECORDOID);
+       var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
 
        /* 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 = slot->tts_tupleDescriptor->attrs[i];
+               Form_pg_attribute sattr = tupleDesc->attrs[i];
 
                if (!vattr->attisdropped)
                        continue;                       /* already checked non-dropped cols */
@@ -930,8 +1028,7 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
 
        /*
         * 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);
@@ -940,8 +1037,6 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
        HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
        HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
 
-       HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
-
        ReleaseTupleDesc(var_tupdesc);
 
        return PointerGetDatum(dtuple);
@@ -3907,7 +4002,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),
@@ -4236,8 +4331,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 2bd8b4283598ab8fe474a8ee3745205921638a52..0bbd0d464025c71dac05d72119c5a4d3052ecbf7 100644 (file)
@@ -524,8 +524,8 @@ ExecBuildProjectionInfo(List *targetList,
         * We separate the target list elements into simple Var references and
         * expressions which require the full ExecTargetList machinery.  To be a
         * simple Var, a Var has to be a user attribute and not mismatch the
-        * inputDesc.  (Note: if there is a type mismatch then ExecEvalVar will
-        * probably throw an error at runtime, but we leave that to it.)
+        * inputDesc.  (Note: if there is a type mismatch then ExecEvalScalarVar
+        * will probably throw an error at runtime, but we leave that to it.)
         */
        exprlist = NIL;
        numSimpleVars = 0;
index aa3916998718e88d6e0a4472c96f8a13f19864b3..fec07b8e426e479e10511404bbd198bae7e26282 100644 (file)
@@ -560,6 +560,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 1e16088b7ef738438f14b6118f3fd94b05461e40..72972ac220c3817ef8a99740b4bab9b1447b33e7 100644 (file)
@@ -204,6 +204,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 4ea8211c692f6d37e20b68f18b0eb8da0ad7c371..a4c3f992a953fd981ba1adca3ac3de3ad92bf0e1 100644 (file)
@@ -507,6 +507,31 @@ select (select (a.*)::text) from view_a a;
  (42)
 (1 row)
 
+--
+-- 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)
+
+with q as (select max(f1) from int4_tbl group by f1 order by f1)
+  select q from q;
+       q       
+---------------
+ (-2147483647)
+ (-123456)
+ (0)
+ (123456)
+ (2147483647)
+(5 rows)
+
 --
 -- Test case for sublinks pushed down into subselects via join alias expansion
 --
index 3cecbc1d41b1eb1134b525ca4a848cee50adf422..e07a30ed03baca56bf3c740f5d9fc616c75370a7 100644 (file)
@@ -324,6 +324,15 @@ select (select view_a) from view_a;
 select (select (select view_a)) from view_a;
 select (select (a.*)::text) from view_a a;
 
+--
+-- 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;
+with q as (select max(f1) from int4_tbl group by f1 order by f1)
+  select q from q;
+
 --
 -- Test case for sublinks pushed down into subselects via join alias expansion
 --