Commit 8e617e29 authored by Tom Lane's avatar Tom Lane

Fix whole-row Var evaluation to cope with resjunk columns (again).

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
68e40998, 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
292176a1, which for some reason I had
not done at the time, but it's a prerequisite for this change.
parent 3a0e4d36
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
* ExecProject - form a new tuple by projecting the given tuple * ExecProject - form a new tuple by projecting the given tuple
* *
* NOTES * 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. * are hotspots. Making these faster will speed up the entire system.
* *
* ExecProject() is used to make tuple projections. Rather then * ExecProject() is used to make tuple projections. Rather then
...@@ -68,13 +68,18 @@ static Datum ExecEvalAggref(AggrefExprState *aggref, ...@@ -68,13 +68,18 @@ static Datum ExecEvalAggref(AggrefExprState *aggref,
static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc, static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
ExprContext *econtext, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); 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 ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext, static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
...@@ -553,19 +558,18 @@ ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext, ...@@ -553,19 +558,18 @@ ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* ExecEvalVar * ExecEvalScalarVar
* *
* Returns a Datum whose value is the value of a range * Returns a Datum whose value is the value of a scalar (not whole-row)
* variable with respect to given expression context. * range variable with respect to given expression context.
* *
* Note: ExecEvalVar is executed only the first time through in a given plan; * Note: ExecEvalScalarVar is executed only the first time through in a given
* it changes the ExprState's function pointer to pass control directly to * plan; it changes the ExprState's function pointer to pass control directly
* ExecEvalScalarVar, ExecEvalWholeRowVar, or ExecEvalWholeRowSlow after * to ExecEvalScalarVarFast after making one-time checks.
* making one-time checks.
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
static Datum static Datum
ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone) bool *isNull, ExprDoneCond *isDone)
{ {
Var *variable = (Var *) exprstate->expr; Var *variable = (Var *) exprstate->expr;
...@@ -596,27 +600,25 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ...@@ -596,27 +600,25 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
attnum = variable->varattno; attnum = variable->varattno;
if (attnum != InvalidAttrNumber) /* This was checked by ExecInitExpr */
{ Assert(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
* If it's a user attribute, check validity (bogus system attnums will * possibility of an attribute having been changed in type since the plan
* be caught inside slot_getattr). What we have to check for here is * tree was created. Ideally the plan will get invalidated and not
* the possibility of an attribute having been changed in type since * re-used, but just in case, we keep these defenses. Fortunately it's
* the plan tree was created. Ideally the plan would get invalidated * sufficient to check once on the first time through.
* 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 * Note: we allow a reference to a dropped attribute. slot_getattr will
* will force a NULL result in such cases. * force a NULL result in such cases.
* *
* Note: ideally we'd check typmod as well as typid, but that seems * 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 * impractical at the moment: in many cases the tupdesc will have been
* generated by ExecTypeFromTL(), and that can't guarantee to generate * generated by ExecTypeFromTL(), and that can't guarantee to generate an
* an accurate typmod in all cases, because some expression node types * accurate typmod in all cases, because some expression node types don't
* don't carry typmod. * carry typmod.
*/ */
if (attnum > 0) if (attnum > 0)
{ {
...@@ -642,26 +644,174 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ...@@ -642,26 +644,174 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
} }
/* Skip the checking on future executions of node */ /* Skip the checking on future executions of node */
exprstate->evalfunc = ExecEvalScalarVar; exprstate->evalfunc = ExecEvalScalarVarFast;
/* Fetch the value from the slot */ /* Fetch the value from the slot */
return slot_getattr(slot, attnum, isNull); return slot_getattr(slot, attnum, isNull);
} }
else
/* ----------------------------------------------------------------
* ExecEvalScalarVarFast
*
* Returns a Datum for a scalar variable.
* ----------------------------------------------------------------
*/
static Datum
ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
AttrNumber attnum;
if (isDone)
*isDone = ExprSingleResult;
/* Get the input slot and attribute number we want */
switch (variable->varno)
{ {
/* case INNER_VAR: /* get the tuple from the inner node */
* Whole-row variable. 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;
}
attnum = variable->varattno;
/* Fetch the value from the slot */
return slot_getattr(slot, attnum, isNull);
}
/* ----------------------------------------------------------------
* ExecEvalWholeRowVar
* *
* If it's a RECORD Var, we'll use the slot's type ID info. It's * Returns a Datum whose value is the value of a whole-row range
* likely that the slot's type is also RECORD; if so, make sure it's * variable with respect to given expression context.
* been "blessed", so that the Datum can be interpreted later.
* *
* If the Var identifies a named composite type, we must check that * Note: ExecEvalWholeRowVar is executed only the first time through in a
* the actual tuple type is compatible with it. * given plan; it changes the ExprState's function pointer to pass control
* directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making
* one-time checks.
* ----------------------------------------------------------------
*/ */
TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; 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; 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 (variable->vartype == RECORDOID)
{ {
if (slot_tupdesc->tdtypeid == RECORDOID && if (slot_tupdesc->tdtypeid == RECORDOID &&
...@@ -674,29 +824,20 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ...@@ -674,29 +824,20 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
int i; int i;
/* /*
* We really only care about number of attributes and data type. * We really only care about numbers of attributes and data types.
* Also, we can ignore type mismatch on columns that are dropped * Also, we can ignore type mismatch on columns that are dropped in
* in the destination type, so long as (1) the physical storage * the destination type, so long as (1) the physical storage matches
* matches or (2) the actual column value is NULL. Case (1) is * or (2) the actual column value is NULL. Case (1) is helpful in
* helpful in some cases involving out-of-date cached plans, while * some cases involving out-of-date cached plans, while case (2) is
* case (2) is expected behavior in situations such as an INSERT * expected behavior in situations such as an INSERT into a table with
* into a table with dropped columns (the planner typically * dropped columns (the planner typically generates an INT4 NULL
* generates an INT4 NULL regardless of the dropped column type). * regardless of the dropped column type). If we find a dropped
* If we find a dropped column and cannot verify that case (1) * column and cannot verify that case (1) holds, we have to use
* holds, we have to use ExecEvalWholeRowSlow to check (2) for * ExecEvalWholeRowSlow to check (2) for each row.
* 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); var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
if (var_tupdesc->natts > slot_tupdesc->natts) if (var_tupdesc->natts != slot_tupdesc->natts)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("table row type and query-specified row type do not match"), errmsg("table row type and query-specified row type do not match"),
...@@ -705,8 +846,6 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ...@@ -705,8 +846,6 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
slot_tupdesc->natts, slot_tupdesc->natts,
slot_tupdesc->natts, slot_tupdesc->natts,
var_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++) for (i = 0; i < var_tupdesc->natts; i++)
{ {
...@@ -734,68 +873,26 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ...@@ -734,68 +873,26 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
/* Skip the checking on future executions of node */ /* Skip the checking on future executions of node */
if (needslow) if (needslow)
exprstate->evalfunc = ExecEvalWholeRowSlow; wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
else else
exprstate->evalfunc = ExecEvalWholeRowVar; wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast;
/* Fetch the value */ /* Fetch the value */
return (*exprstate->evalfunc) (exprstate, econtext, isNull, isDone); return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext,
} isNull, isDone);
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* ExecEvalScalarVar * ExecEvalWholeRowFast
*
* 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;
AttrNumber attnum;
if (isDone)
*isDone = ExprSingleResult;
/* Get the input slot and attribute number 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;
}
attnum = variable->varattno;
/* Fetch the value from the slot */
return slot_getattr(slot, attnum, isNull);
}
/* ----------------------------------------------------------------
* ExecEvalWholeRowVar
* *
* Returns a Datum for a whole-row variable. * Returns a Datum for a whole-row variable.
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
static Datum static Datum
ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone) bool *isNull, ExprDoneCond *isDone)
{ {
Var *variable = (Var *) exprstate->expr; Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot; TupleTableSlot *slot;
HeapTuple tuple; HeapTuple tuple;
TupleDesc tupleDesc; TupleDesc tupleDesc;
...@@ -824,6 +921,10 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, ...@@ -824,6 +921,10 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
break; break;
} }
/* Apply the junkfilter if any */
if (wrvstate->wrv_junkFilter != NULL)
slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
tuple = ExecFetchSlotTuple(slot); tuple = ExecFetchSlotTuple(slot);
tupleDesc = slot->tts_tupleDescriptor; tupleDesc = slot->tts_tupleDescriptor;
...@@ -857,17 +958,18 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, ...@@ -857,17 +958,18 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* ExecEvalWholeRowSlow * 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. * we can't just copy the subplan's output.
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
static Datum static Datum
ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone) bool *isNull, ExprDoneCond *isDone)
{ {
Var *variable = (Var *) exprstate->expr; Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot; TupleTableSlot *slot;
HeapTuple tuple; HeapTuple tuple;
TupleDesc tupleDesc;
TupleDesc var_tupdesc; TupleDesc var_tupdesc;
HeapTupleHeader dtuple; HeapTupleHeader dtuple;
int i; int i;
...@@ -895,25 +997,21 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, ...@@ -895,25 +997,21 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
break; break;
} }
/* /* Apply the junkfilter if any */
* Currently, the only data modification case handled here is stripping of if (wrvstate->wrv_junkFilter != NULL)
* trailing resjunk fields, which we do in a slightly chintzy way by just slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
* 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); 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 */ /* Check to see if any dropped attributes are non-null */
for (i = 0; i < var_tupdesc->natts; i++) for (i = 0; i < var_tupdesc->natts; i++)
{ {
Form_pg_attribute vattr = var_tupdesc->attrs[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) if (!vattr->attisdropped)
continue; /* already checked non-dropped cols */ continue; /* already checked non-dropped cols */
...@@ -930,8 +1028,7 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, ...@@ -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 * 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 * overhead fields, which are not set in on-disk tuples.
* fooling with its natts field.
*/ */
dtuple = (HeapTupleHeader) palloc(tuple->t_len); dtuple = (HeapTupleHeader) palloc(tuple->t_len);
memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len); memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
...@@ -940,8 +1037,6 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, ...@@ -940,8 +1037,6 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
HeapTupleHeaderSetTypeId(dtuple, variable->vartype); HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
ReleaseTupleDesc(var_tupdesc); ReleaseTupleDesc(var_tupdesc);
return PointerGetDatum(dtuple); return PointerGetDatum(dtuple);
...@@ -3907,7 +4002,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate, ...@@ -3907,7 +4002,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
} }
/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ /* 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) if (fselect->resulttype != attr->atttypid)
ereport(ERROR, ereport(ERROR,
(errmsg("attribute %d has wrong type", fieldnum), (errmsg("attribute %d has wrong type", fieldnum),
...@@ -4236,8 +4331,21 @@ ExecInitExpr(Expr *node, PlanState *parent) ...@@ -4236,8 +4331,21 @@ ExecInitExpr(Expr *node, PlanState *parent)
switch (nodeTag(node)) switch (nodeTag(node))
{ {
case T_Var: case T_Var:
/* 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 = (ExprState *) makeNode(ExprState);
state->evalfunc = ExecEvalVar; state->evalfunc = ExecEvalScalarVar;
}
break; break;
case T_Const: case T_Const:
state = (ExprState *) makeNode(ExprState); state = (ExprState *) makeNode(ExprState);
......
...@@ -524,8 +524,8 @@ ExecBuildProjectionInfo(List *targetList, ...@@ -524,8 +524,8 @@ ExecBuildProjectionInfo(List *targetList,
* We separate the target list elements into simple Var references and * We separate the target list elements into simple Var references and
* expressions which require the full ExecTargetList machinery. To be a * expressions which require the full ExecTargetList machinery. To be a
* simple Var, a Var has to be a user attribute and not mismatch the * 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 * inputDesc. (Note: if there is a type mismatch then ExecEvalScalarVar
* probably throw an error at runtime, but we leave that to it.) * will probably throw an error at runtime, but we leave that to it.)
*/ */
exprlist = NIL; exprlist = NIL;
numSimpleVars = 0; numSimpleVars = 0;
......
...@@ -560,6 +560,17 @@ typedef struct GenericExprState ...@@ -560,6 +560,17 @@ typedef struct GenericExprState
ExprState *arg; /* state of my child node */ ExprState *arg; /* state of my child node */
} GenericExprState; } 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 * AggrefExprState node
* ---------------- * ----------------
......
...@@ -180,6 +180,7 @@ typedef enum NodeTag ...@@ -180,6 +180,7 @@ typedef enum NodeTag
*/ */
T_ExprState = 400, T_ExprState = 400,
T_GenericExprState, T_GenericExprState,
T_WholeRowVarExprState,
T_AggrefExprState, T_AggrefExprState,
T_WindowFuncExprState, T_WindowFuncExprState,
T_ArrayRefExprState, T_ArrayRefExprState,
......
...@@ -504,6 +504,31 @@ select (select (a.*)::text) from view_a a; ...@@ -504,6 +504,31 @@ select (select (a.*)::text) from view_a a;
(42) (42)
(1 row) (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 -- Test case for sublinks pushed down into subselects via join alias expansion
-- --
......
...@@ -324,6 +324,15 @@ select (select view_a) from view_a; ...@@ -324,6 +324,15 @@ select (select view_a) from view_a;
select (select (select view_a)) from view_a; select (select (select view_a)) from view_a;
select (select (a.*)::text) from view_a 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 -- Test case for sublinks pushed down into subselects via join alias expansion
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment