I wrote:
> I think the way to solve this is to do whatever it takes to get access
> to the subplan targetlist. We could then do something a bit cleaner
> than what the named-rowtype code is currently doing: if there are
> resjunk columns in the subplan targetlist, use the tlist to create a
> JunkFilter, and then pass the tuples through that. After that we can
> insist that the tuples don't have any extra columns.
Here's a draft patch for that. It wasn't quite as ugly as I feared.
A lot of the apparent bulk of the patch is because I chose to split
ExecEvalVar into separate functions for the scalar and whole-row
cases, which seemed appropriate because they now get different
ExprState node types.
regards, tom lane
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 0ea21ca5f91a704ccde2c765bd92d0c5af7ead8b..ae7987ee97482e1224912f3e96d5a90eaa1f10f7 100644
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 20,26 ****
* ExecProject - form a new tuple by projecting the given tuple
*
* NOTES
! * The more heavily used ExecEvalExpr routines, such as ExecEvalVar(),
* are hotspots. Making these faster will speed up the entire system.
*
* ExecProject() is used to make tuple projections. Rather then
--- 20,26 ----
* ExecProject - form a new tuple by projecting the given tuple
*
* NOTES
! * 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
*************** static Datum ExecEvalAggref(AggrefExprSt
*** 68,80 ****
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,
bool *isNull, ExprDoneCond *isDone);
! static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
--- 68,85 ----
static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
! static Datum ExecEvalScalarVar2(ExprState *exprstate, ExprContext *econtext,
! bool *isNull, ExprDoneCond *isDone);
! static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
! ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
! 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);
*************** ExecEvalWindowFunc(WindowFuncExprState *
*** 553,572 ****
}
/* ----------------------------------------------------------------
! * ExecEvalVar
*
! * Returns a Datum whose value is the value of a 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.
* ----------------------------------------------------------------
*/
static Datum
! ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
! bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
--- 558,576 ----
}
/* ----------------------------------------------------------------
! * ExecEvalScalarVar
*
! * Returns a Datum whose value is the value of a scalar (not whole-row)
! * range variable with respect to given expression context.
*
! * 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 ExecEvalScalarVar2 after making one-time checks.
* ----------------------------------------------------------------
*/
static Datum
! ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
! bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
*************** ExecEvalVar(ExprState *exprstate, ExprCo
*** 596,757 ****
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;
!
! 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
{
- /*
- * 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;
! 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 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);
! 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)));
! 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);
}
! /* Skip the checking on future executions of node */
! if (needslow)
! exprstate->evalfunc = ExecEvalWholeRowSlow;
! else
! exprstate->evalfunc = ExecEvalWholeRowVar;
! /* Fetch the value */
! return (*exprstate->evalfunc) (exprstate, econtext, isNull, isDone);
! }
}
/* ----------------------------------------------------------------
! * ExecEvalScalarVar
*
* Returns a Datum for a scalar variable.
* ----------------------------------------------------------------
*/
static Datum
! ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
! bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
--- 600,664 ----
attnum = variable->varattno;
! /* This was checked by ExecInitExpr */
! Assert(attnum != InvalidAttrNumber);
! /*
! * 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)
{
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);
! 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 = ExecEvalScalarVar2;
! /* Fetch the value from the slot */
! return slot_getattr(slot, attnum, isNull);
}
/* ----------------------------------------------------------------
! * ExecEvalScalarVar2
*
* Returns a Datum for a scalar variable.
* ----------------------------------------------------------------
*/
static Datum
! ExecEvalScalarVar2(ExprState *exprstate, ExprContext *econtext,
! bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
*************** ExecEvalScalarVar(ExprState *exprstate,
*** 788,801 ****
/* ----------------------------------------------------------------
* ExecEvalWholeRowVar
*
! * Returns a Datum for a whole-row variable.
* ----------------------------------------------------------------
*/
static Datum
! ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
! Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
HeapTuple tuple;
TupleDesc tupleDesc;
--- 695,895 ----
/* ----------------------------------------------------------------
* ExecEvalWholeRowVar
*
! * 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(WholeRowVarExprState *wrvstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
! 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.
! */
! 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 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.
! */
! 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;
*************** ExecEvalWholeRowVar(ExprState *exprstate
*** 824,829 ****
--- 918,927 ----
break;
}
+ /* Apply the junkfilter if any */
+ if (wrvstate->wrv_junkFilter != NULL)
+ slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
tuple = ExecFetchSlotTuple(slot);
tupleDesc = slot->tts_tupleDescriptor;
*************** ExecEvalWholeRowVar(ExprState *exprstate
*** 857,871 ****
/* ----------------------------------------------------------------
* ExecEvalWholeRowSlow
*
! * Returns a Datum for a whole-row variable, in the "slow" cases where
* we can't just copy the subplan's output.
* ----------------------------------------------------------------
*/
static Datum
! ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
! Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
HeapTuple tuple;
TupleDesc var_tupdesc;
--- 955,969 ----
/* ----------------------------------------------------------------
* ExecEvalWholeRowSlow
*
! * 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(WholeRowVarExprState *wrvstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
! Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
HeapTuple tuple;
TupleDesc var_tupdesc;
*************** ExecEvalWholeRowSlow(ExprState *exprstat
*** 895,913 ****
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);
tuple = ExecFetchSlotTuple(slot);
! Assert(HeapTupleHeaderGetNatts(tuple->t_data) >= var_tupdesc->natts);
/* Check to see if any dropped attributes are non-null */
for (i = 0; i < var_tupdesc->natts; i++)
--- 993,1006 ----
break;
}
! /* Apply the junkfilter if any */
! if (wrvstate->wrv_junkFilter != NULL)
! slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
tuple = ExecFetchSlotTuple(slot);
! 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++)
*************** ExecEvalWholeRowSlow(ExprState *exprstat
*** 930,937 ****
/*
* 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.
*/
dtuple = (HeapTupleHeader) palloc(tuple->t_len);
memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
--- 1023,1029 ----
/*
* 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.
*/
dtuple = (HeapTupleHeader) palloc(tuple->t_len);
memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
*************** ExecEvalWholeRowSlow(ExprState *exprstat
*** 940,947 ****
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
- HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
-
ReleaseTupleDesc(var_tupdesc);
return PointerGetDatum(dtuple);
--- 1032,1037 ----
*************** ExecEvalFieldSelect(FieldSelectState *fs
*** 3907,3913 ****
}
/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
! /* As in ExecEvalVar, we should but can't check typmod */
if (fselect->resulttype != attr->atttypid)
ereport(ERROR,
(errmsg("attribute %d has wrong type", fieldnum),
--- 3997,4003 ----
}
/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
! /* As in ExecEvalScalarVar, we should but can't check typmod */
if (fselect->resulttype != attr->atttypid)
ereport(ERROR,
(errmsg("attribute %d has wrong type", fieldnum),
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4236,4243 ****
switch (nodeTag(node))
{
case T_Var:
! state = (ExprState *) makeNode(ExprState);
! state->evalfunc = ExecEvalVar;
break;
case T_Const:
state = (ExprState *) makeNode(ExprState);
--- 4326,4345 ----
switch (nodeTag(node))
{
case T_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);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index aa3916998718e88d6e0a4472c96f8a13f19864b3..09ed2cd01f1780d82181777a3bbb542ba71f01a0 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct GenericExprState
*** 561,566 ****
--- 561,577 ----
} 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
* ----------------
*/
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a51657df0d43487820b5c946b370458c2b9321ef..5e65922bcf8e34429e93acf8d7ed46ec0758f8a7 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 180,185 ****
--- 180,186 ----
*/
T_ExprState = 400,
T_GenericExprState,
+ T_WholeRowVarExprState,
T_AggrefExprState,
T_WindowFuncExprState,
T_ArrayRefExprState,
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index ad870f665fc1ec6a367bb7c32a037e2df986d450..30cff04cb707b7610c14819e4232819f724e2d16 100644
*** a/src/test/regress/expected/subselect.out
--- b/src/test/regress/expected/subselect.out
*************** select (select (a.*)::text) from view_a
*** 505,510 ****
--- 505,535 ----
(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
--
select
diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql
index 3cecbc1d41b1eb1134b525ca4a848cee50adf422..e07a30ed03baca56bf3c740f5d9fc616c75370a7 100644
*** a/src/test/regress/sql/subselect.sql
--- b/src/test/regress/sql/subselect.sql
*************** select (select (select view_a)) from vie
*** 325,330 ****
--- 325,339 ----
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
--