mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-24 18:55:04 +08:00
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 commit68e40998d0
, 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 commit292176a118
, which for some reason I had not done at the time, but it's a prerequisite for this change.
This commit is contained in:
parent
7587286f66
commit
0b71d8e855
@ -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,
|
|||||||
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);
|
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);
|
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);
|
||||||
@ -551,20 +556,19 @@ 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;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
@ -592,162 +596,65 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
|
|||||||
|
|
||||||
attnum = variable->varattno;
|
attnum = variable->varattno;
|
||||||
|
|
||||||
if (attnum != InvalidAttrNumber)
|
/* 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)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* 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;
|
TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
|
||||||
bool needslow = false;
|
Form_pg_attribute attr;
|
||||||
|
|
||||||
if (variable->vartype == RECORDOID)
|
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 (slot_tupdesc->tdtypeid == RECORDOID &&
|
if (variable->vartype != attr->atttypid)
|
||||||
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,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
(errmsg("attribute %d has wrong type", attnum),
|
||||||
errmsg("table row type and query-specified row type do not match"),
|
errdetail("Table has type %s, but query expects %s.",
|
||||||
errdetail_plural("Table row contains %d attribute, but query expects %d.",
|
format_type_be(attr->atttypid),
|
||||||
"Table row contains %d attributes, but query expects %d.",
|
format_type_be(variable->vartype))));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Skip the checking on future executions of node */
|
||||||
|
exprstate->evalfunc = ExecEvalScalarVarFast;
|
||||||
|
|
||||||
|
/* Fetch the value from the slot */
|
||||||
|
return slot_getattr(slot, attnum, isNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
* ExecEvalScalarVar
|
* ExecEvalScalarVarFast
|
||||||
*
|
*
|
||||||
* Returns a Datum for a scalar variable.
|
* Returns a Datum for a scalar variable.
|
||||||
* ----------------------------------------------------------------
|
* ----------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
static Datum
|
static Datum
|
||||||
ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
|
ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
|
||||||
bool *isNull, ExprDoneCond *isDone)
|
bool *isNull, ExprDoneCond *isDone)
|
||||||
{
|
{
|
||||||
Var *variable = (Var *) exprstate->expr;
|
Var *variable = (Var *) exprstate->expr;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
@ -782,14 +689,202 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
|
|||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
* ExecEvalWholeRowVar
|
* 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: /* get the tuple from the inner node */
|
||||||
|
slot = econtext->ecxt_innertuple;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OUTER: /* get the tuple from the outer node */
|
||||||
|
slot = econtext->ecxt_outertuple;
|
||||||
|
break;
|
||||||
|
|
||||||
|
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.
|
* 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;
|
||||||
@ -816,6 +911,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;
|
||||||
|
|
||||||
@ -849,17 +948,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;
|
||||||
@ -885,25 +985,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 */
|
||||||
@ -920,8 +1016,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);
|
||||||
@ -930,8 +1025,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);
|
||||||
@ -3900,7 +3993,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),
|
||||||
@ -4229,8 +4322,21 @@ ExecInitExpr(Expr *node, PlanState *parent)
|
|||||||
switch (nodeTag(node))
|
switch (nodeTag(node))
|
||||||
{
|
{
|
||||||
case T_Var:
|
case T_Var:
|
||||||
state = (ExprState *) makeNode(ExprState);
|
/* varattno == InvalidAttrNumber means it's a whole-row Var */
|
||||||
state->evalfunc = ExecEvalVar;
|
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;
|
break;
|
||||||
case T_Const:
|
case T_Const:
|
||||||
state = (ExprState *) makeNode(ExprState);
|
state = (ExprState *) makeNode(ExprState);
|
||||||
|
@ -525,8 +525,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;
|
||||||
|
@ -565,6 +565,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
|
||||||
* ----------------
|
* ----------------
|
||||||
|
@ -196,6 +196,7 @@ typedef enum NodeTag
|
|||||||
T_NullTestState,
|
T_NullTestState,
|
||||||
T_CoerceToDomainState,
|
T_CoerceToDomainState,
|
||||||
T_DomainConstraintState,
|
T_DomainConstraintState,
|
||||||
|
T_WholeRowVarExprState, /* will be in a more natural position in 9.3 */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TAGS FOR PLANNER NODES (relation.h)
|
* TAGS FOR PLANNER NODES (relation.h)
|
||||||
|
@ -507,6 +507,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;
|
|||||||
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
|
||||||
--
|
--
|
||||||
|
Loading…
Reference in New Issue
Block a user