Extend whole-row Var evaluation to cope with the case that the sub-plan

generating the tuples has resjunk output columns.  This is not possible for
simple table scans but can happen when evaluating a whole-row Var for a view.
Per example from Patryk Kordylewski.  The problem exists back to 8.0 but
I'm not going to risk back-patching further than 8.2 because of the many
changes in this area.
This commit is contained in:
Tom Lane 2007-08-31 18:33:40 +00:00
parent 42e9e26066
commit 68e40998d0
1 changed files with 76 additions and 5 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.220 2007/06/11 22:22:40 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.221 2007/08/31 18:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -68,6 +68,8 @@ 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);
static Datum ExecEvalParam(ExprState *exprstate, ExprContext *econtext,
@ -438,7 +440,8 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
*
* 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 or ExecEvalWholeRowVar after making one-time checks.
* ExecEvalScalarVar, ExecEvalWholeRowVar, or ExecEvalWholeRowSlow after
* making one-time checks.
* ----------------------------------------------------------------
*/
static Datum
@ -544,6 +547,7 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
* the actual tuple type is compatible with it.
*/
TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
bool needslow = false;
if (variable->vartype == RECORDOID)
{
@ -561,16 +565,26 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
* Also, we can ignore type mismatch on columns that are dropped
* in the destination type, so long as the physical storage
* matches. This is helpful in some cases involving out-of-date
* cached plans.
* cached plans. Also, we have to allow the case that the slot
* has more columns than the Var's type, because we might be
* looking at the output of a subplan that includes resjunk
* columns. (XXX it would be nice to verify that the extra
* columns are all marked resjunk, but we haven't got access to
* the subplan targetlist here...) Resjunk columns should always
* be at the end of a targetlist, so it's sufficient to ignore
* them here; but we need to use ExecEvalWholeRowSlow to get
* rid of them in the eventual output tuples.
*/
var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
if (var_tupdesc->natts != slot_tupdesc->natts)
if (var_tupdesc->natts > slot_tupdesc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("table row type and query-specified row type do not match"),
errdetail("Table row contains %d attributes, but query expects %d.",
slot_tupdesc->natts, var_tupdesc->natts)));
else if (var_tupdesc->natts < slot_tupdesc->natts)
needslow = true;
for (i = 0; i < var_tupdesc->natts; i++)
{
@ -601,7 +615,10 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
}
/* Skip the checking on future executions of node */
exprstate->evalfunc = ExecEvalWholeRowVar;
if (needslow)
exprstate->evalfunc = ExecEvalWholeRowSlow;
else
exprstate->evalfunc = ExecEvalWholeRowVar;
/* Fetch the value */
return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone);
@ -698,6 +715,60 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
return PointerGetDatum(dtuple);
}
/* ----------------------------------------------------------------
* 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(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot = econtext->ecxt_scantuple;
HeapTuple tuple;
TupleDesc var_tupdesc;
HeapTupleHeader dtuple;
if (isDone)
*isDone = ExprSingleResult;
*isNull = false;
/*
* Currently, the only case handled here is stripping of trailing
* resjunk fields, which we do in a slightly chintzy way by just
* adjusting the tuple's natts header field. Possibly there will someday
* be a need for more-extensive rearrangements, in which case it'd
* be worth disassembling and reassembling the tuple (perhaps use a
* JunkFilter for that?)
*/
Assert(variable->vartype != RECORDOID);
var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
tuple = ExecFetchSlotTuple(slot);
/*
* 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);
HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len);
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
Assert(HeapTupleHeaderGetNatts(dtuple) >= var_tupdesc->natts);
HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
ReleaseTupleDesc(var_tupdesc);
return PointerGetDatum(dtuple);
}
/* ----------------------------------------------------------------
* ExecEvalConst
*