* ExecProject - form a new tuple by projecting the given tuple
*
* NOTES
- * The more heavily used ExecEvalExpr routines, such as ExecEvalVar(),
+ * The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar,
* are hotspots. Making these faster will speed up the entire system.
*
* ExecProject() is used to make tuple projections. Rather then
static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
+static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
+ ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
+static Datum ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate,
+ ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate,
+ ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
}
/* ----------------------------------------------------------------
- * ExecEvalVar
+ * ExecEvalScalarVar
*
- * Returns a Datum whose value is the value of a range
- * variable with respect to given expression context.
+ * Returns a Datum whose value is the value of a scalar (not whole-row)
+ * range variable with respect to given expression context.
*
- * Note: ExecEvalVar is executed only the first time through in a given plan;
- * it changes the ExprState's function pointer to pass control directly to
- * ExecEvalScalarVar, ExecEvalWholeRowVar, or ExecEvalWholeRowSlow after
- * making one-time checks.
+ * Note: ExecEvalScalarVar is executed only the first time through in a given
+ * plan; it changes the ExprState's function pointer to pass control directly
+ * to ExecEvalScalarVarFast after making one-time checks.
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone)
+ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
attnum = variable->varattno;
- if (attnum != InvalidAttrNumber)
- {
- /*
- * Scalar variable case.
- *
- * If it's a user attribute, check validity (bogus system attnums will
- * be caught inside slot_getattr). What we have to check for here is
- * the possibility of an attribute having been changed in type since
- * the plan tree was created. Ideally the plan would get invalidated
- * and not re-used, but until that day arrives, we need defenses.
- * Fortunately it's sufficient to check once on the first time
- * through.
- *
- * Note: we allow a reference to a dropped attribute. slot_getattr
- * will force a NULL result in such cases.
- *
- * Note: ideally we'd check typmod as well as typid, but that seems
- * impractical at the moment: in many cases the tupdesc will have been
- * generated by ExecTypeFromTL(), and that can't guarantee to generate
- * an accurate typmod in all cases, because some expression node types
- * don't carry typmod.
- */
- if (attnum > 0)
- {
- TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
- Form_pg_attribute attr;
+ /* This was checked by ExecInitExpr */
+ Assert(attnum != InvalidAttrNumber);
- if (attnum > slot_tupdesc->natts) /* should never happen */
- elog(ERROR, "attribute number %d exceeds number of columns %d",
- attnum, slot_tupdesc->natts);
-
- attr = slot_tupdesc->attrs[attnum - 1];
-
- /* can't check type if dropped, since atttypid is probably 0 */
- if (!attr->attisdropped)
- {
- if (variable->vartype != attr->atttypid)
- ereport(ERROR,
- (errmsg("attribute %d has wrong type", attnum),
- errdetail("Table has type %s, but query expects %s.",
- format_type_be(attr->atttypid),
- format_type_be(variable->vartype))));
- }
- }
-
- /* Skip the checking on future executions of node */
- exprstate->evalfunc = ExecEvalScalarVar;
-
- /* Fetch the value from the slot */
- return slot_getattr(slot, attnum, isNull);
- }
- else
+ /*
+ * If it's a user attribute, check validity (bogus system attnums will be
+ * caught inside slot_getattr). What we have to check for here is the
+ * possibility of an attribute having been changed in type since the plan
+ * tree was created. Ideally the plan will get invalidated and not
+ * re-used, but just in case, we keep these defenses. Fortunately it's
+ * sufficient to check once on the first time through.
+ *
+ * Note: we allow a reference to a dropped attribute. slot_getattr will
+ * force a NULL result in such cases.
+ *
+ * Note: ideally we'd check typmod as well as typid, but that seems
+ * impractical at the moment: in many cases the tupdesc will have been
+ * generated by ExecTypeFromTL(), and that can't guarantee to generate an
+ * accurate typmod in all cases, because some expression node types don't
+ * carry typmod.
+ */
+ if (attnum > 0)
{
- /*
- * Whole-row variable.
- *
- * If it's a RECORD Var, we'll use the slot's type ID info. It's
- * likely that the slot's type is also RECORD; if so, make sure it's
- * been "blessed", so that the Datum can be interpreted later.
- *
- * If the Var identifies a named composite type, we must check that
- * the actual tuple type is compatible with it.
- */
TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
- bool needslow = false;
+ Form_pg_attribute attr;
- if (variable->vartype == RECORDOID)
- {
- if (slot_tupdesc->tdtypeid == RECORDOID &&
- slot_tupdesc->tdtypmod < 0)
- assign_record_type_typmod(slot_tupdesc);
- }
- else
- {
- TupleDesc var_tupdesc;
- int i;
+ if (attnum > slot_tupdesc->natts) /* should never happen */
+ elog(ERROR, "attribute number %d exceeds number of columns %d",
+ attnum, slot_tupdesc->natts);
- /*
- * We really only care about number of attributes and data type.
- * Also, we can ignore type mismatch on columns that are dropped
- * in the destination type, so long as (1) the physical storage
- * matches or (2) the actual column value is NULL. Case (1) is
- * helpful in some cases involving out-of-date cached plans, while
- * case (2) is expected behavior in situations such as an INSERT
- * into a table with dropped columns (the planner typically
- * generates an INT4 NULL regardless of the dropped column type).
- * If we find a dropped column and cannot verify that case (1)
- * holds, we have to use ExecEvalWholeRowSlow to check (2) for
- * each row. Also, we have to allow the case that the slot has
- * more columns than the Var's type, because we might be looking
- * at the output of a subplan that includes resjunk columns. (XXX
- * it would be nice to verify that the extra columns are all
- * marked resjunk, but we haven't got access to the subplan
- * targetlist here...) Resjunk columns should always be at the end
- * of a targetlist, so it's sufficient to ignore them here; but we
- * need to use ExecEvalWholeRowSlow to get rid of them in the
- * eventual output tuples.
- */
- var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+ attr = slot_tupdesc->attrs[attnum - 1];
- if (var_tupdesc->natts > slot_tupdesc->natts)
+ /* can't check type if dropped, since atttypid is probably 0 */
+ if (!attr->attisdropped)
+ {
+ if (variable->vartype != attr->atttypid)
ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail_plural("Table row contains %d attribute, but query expects %d.",
- "Table row contains %d attributes, but query expects %d.",
- slot_tupdesc->natts,
- slot_tupdesc->natts,
- var_tupdesc->natts)));
- else if (var_tupdesc->natts < slot_tupdesc->natts)
- needslow = true; /* need to trim trailing atts */
-
- for (i = 0; i < var_tupdesc->natts; i++)
- {
- Form_pg_attribute vattr = var_tupdesc->attrs[i];
- Form_pg_attribute sattr = slot_tupdesc->attrs[i];
-
- if (vattr->atttypid == sattr->atttypid)
- continue; /* no worries */
- if (!vattr->attisdropped)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Table has type %s at ordinal position %d, but query expects %s.",
- format_type_be(sattr->atttypid),
- i + 1,
- format_type_be(vattr->atttypid))));
-
- if (vattr->attlen != sattr->attlen ||
- vattr->attalign != sattr->attalign)
- needslow = true; /* need runtime check for null */
- }
-
- ReleaseTupleDesc(var_tupdesc);
+ (errmsg("attribute %d has wrong type", attnum),
+ errdetail("Table has type %s, but query expects %s.",
+ format_type_be(attr->atttypid),
+ format_type_be(variable->vartype))));
}
+ }
- /* Skip the checking on future executions of node */
- if (needslow)
- exprstate->evalfunc = ExecEvalWholeRowSlow;
- else
- exprstate->evalfunc = ExecEvalWholeRowVar;
+ /* Skip the checking on future executions of node */
+ exprstate->evalfunc = ExecEvalScalarVarFast;
- /* Fetch the value */
- return (*exprstate->evalfunc) (exprstate, econtext, isNull, isDone);
- }
+ /* Fetch the value from the slot */
+ return slot_getattr(slot, attnum, isNull);
}
/* ----------------------------------------------------------------
- * ExecEvalScalarVar
+ * ExecEvalScalarVarFast
*
* Returns a Datum for a scalar variable.
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
- bool *isNull, ExprDoneCond *isDone)
+ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
{
Var *variable = (Var *) exprstate->expr;
TupleTableSlot *slot;
/* ----------------------------------------------------------------
* ExecEvalWholeRowVar
*
- * Returns a Datum for a whole-row variable.
+ * Returns a Datum whose value is the value of a whole-row range
+ * variable with respect to given expression context.
+ *
+ * Note: ExecEvalWholeRowVar is executed only the first time through in a
+ * given plan; it changes the ExprState's function pointer to pass control
+ * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making
+ * one-time checks.
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext,
+ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
- Var *variable = (Var *) exprstate->expr;
+ Var *variable = (Var *) wrvstate->xprstate.expr;
+ TupleTableSlot *slot;
+ TupleDesc slot_tupdesc;
+ bool needslow = false;
+
+ if (isDone)
+ *isDone = ExprSingleResult;
+
+ /* This was checked by ExecInitExpr */
+ Assert(variable->varattno == InvalidAttrNumber);
+
+ /* Get the input slot we want */
+ switch (variable->varno)
+ {
+ case INNER_VAR: /* get the tuple from the inner node */
+ slot = econtext->ecxt_innertuple;
+ break;
+
+ case OUTER_VAR: /* get the tuple from the outer node */
+ slot = econtext->ecxt_outertuple;
+ break;
+
+ /* INDEX_VAR is handled by default case */
+
+ default: /* get the tuple from the relation being
+ * scanned */
+ slot = econtext->ecxt_scantuple;
+ break;
+ }
+
+ /*
+ * If the input tuple came from a subquery, it might contain "resjunk"
+ * columns (such as GROUP BY or ORDER BY columns), which we don't want to
+ * keep in the whole-row result. We can get rid of such columns by
+ * passing the tuple through a JunkFilter --- but to make one, we have to
+ * lay our hands on the subquery's targetlist. Fortunately, there are not
+ * very many cases where this can happen, and we can identify all of them
+ * by examining our parent PlanState. We assume this is not an issue in
+ * standalone expressions that don't have parent plans. (Whole-row Vars
+ * can occur in such expressions, but they will always be referencing
+ * table rows.)
+ */
+ if (wrvstate->parent)
+ {
+ PlanState *subplan = NULL;
+
+ switch (nodeTag(wrvstate->parent))
+ {
+ case T_SubqueryScanState:
+ subplan = ((SubqueryScanState *) wrvstate->parent)->subplan;
+ break;
+ case T_CteScanState:
+ subplan = ((CteScanState *) wrvstate->parent)->cteplanstate;
+ break;
+ default:
+ break;
+ }
+
+ if (subplan)
+ {
+ bool junk_filter_needed = false;
+ ListCell *tlist;
+
+ /* Detect whether subplan tlist actually has any junk columns */
+ foreach(tlist, subplan->plan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tlist);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+
+ /* If so, build the junkfilter in the query memory context */
+ if (junk_filter_needed)
+ {
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ wrvstate->wrv_junkFilter =
+ ExecInitJunkFilter(subplan->plan->targetlist,
+ ExecGetResultType(subplan)->tdhasoid,
+ ExecInitExtraTupleSlot(wrvstate->parent->state));
+ MemoryContextSwitchTo(oldcontext);
+ }
+ }
+ }
+
+ /* Apply the junkfilter if any */
+ if (wrvstate->wrv_junkFilter != NULL)
+ slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
+ slot_tupdesc = slot->tts_tupleDescriptor;
+
+ /*
+ * If it's a RECORD Var, we'll use the slot's type ID info. It's likely
+ * that the slot's type is also RECORD; if so, make sure it's been
+ * "blessed", so that the Datum can be interpreted later.
+ *
+ * If the Var identifies a named composite type, we must check that the
+ * actual tuple type is compatible with it.
+ */
+ if (variable->vartype == RECORDOID)
+ {
+ if (slot_tupdesc->tdtypeid == RECORDOID &&
+ slot_tupdesc->tdtypmod < 0)
+ assign_record_type_typmod(slot_tupdesc);
+ }
+ else
+ {
+ TupleDesc var_tupdesc;
+ int i;
+
+ /*
+ * We really only care about numbers of attributes and data types.
+ * Also, we can ignore type mismatch on columns that are dropped in
+ * the destination type, so long as (1) the physical storage matches
+ * or (2) the actual column value is NULL. Case (1) is helpful in
+ * some cases involving out-of-date cached plans, while case (2) is
+ * expected behavior in situations such as an INSERT into a table with
+ * dropped columns (the planner typically generates an INT4 NULL
+ * regardless of the dropped column type). If we find a dropped
+ * column and cannot verify that case (1) holds, we have to use
+ * ExecEvalWholeRowSlow to check (2) for each row.
+ */
+ var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+
+ if (var_tupdesc->natts != slot_tupdesc->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table row type and query-specified row type do not match"),
+ errdetail_plural("Table row contains %d attribute, but query expects %d.",
+ "Table row contains %d attributes, but query expects %d.",
+ slot_tupdesc->natts,
+ slot_tupdesc->natts,
+ var_tupdesc->natts)));
+
+ for (i = 0; i < var_tupdesc->natts; i++)
+ {
+ Form_pg_attribute vattr = var_tupdesc->attrs[i];
+ Form_pg_attribute sattr = slot_tupdesc->attrs[i];
+
+ if (vattr->atttypid == sattr->atttypid)
+ continue; /* no worries */
+ if (!vattr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table row type and query-specified row type do not match"),
+ errdetail("Table has type %s at ordinal position %d, but query expects %s.",
+ format_type_be(sattr->atttypid),
+ i + 1,
+ format_type_be(vattr->atttypid))));
+
+ if (vattr->attlen != sattr->attlen ||
+ vattr->attalign != sattr->attalign)
+ needslow = true; /* need runtime check for null */
+ }
+
+ ReleaseTupleDesc(var_tupdesc);
+ }
+
+ /* Skip the checking on future executions of node */
+ if (needslow)
+ wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
+ else
+ wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast;
+
+ /* Fetch the value */
+ return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext,
+ isNull, isDone);
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalWholeRowFast
+ *
+ * Returns a Datum for a whole-row variable.
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
HeapTuple tuple;
TupleDesc tupleDesc;
break;
}
+ /* Apply the junkfilter if any */
+ if (wrvstate->wrv_junkFilter != NULL)
+ slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
+
tuple = ExecFetchSlotTuple(slot);
tupleDesc = slot->tts_tupleDescriptor;
/* ----------------------------------------------------------------
* ExecEvalWholeRowSlow
*
- * Returns a Datum for a whole-row variable, in the "slow" cases where
+ * Returns a Datum for a whole-row variable, in the "slow" case where
* we can't just copy the subplan's output.
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext,
+ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone)
{
- Var *variable = (Var *) exprstate->expr;
+ Var *variable = (Var *) wrvstate->xprstate.expr;
TupleTableSlot *slot;
HeapTuple tuple;
+ TupleDesc tupleDesc;
TupleDesc var_tupdesc;
HeapTupleHeader dtuple;
int i;
break;
}
- /*
- * Currently, the only data modification case handled here is stripping of
- * trailing resjunk fields, which we do in a slightly chintzy way by just
- * adjusting the tuple's natts header field. Possibly there will someday
- * be a need for more-extensive rearrangements, in which case we'd
- * probably use tupconvert.c.
- */
- Assert(variable->vartype != RECORDOID);
- var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+ /* Apply the junkfilter if any */
+ if (wrvstate->wrv_junkFilter != NULL)
+ slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
tuple = ExecFetchSlotTuple(slot);
+ tupleDesc = slot->tts_tupleDescriptor;
- Assert(HeapTupleHeaderGetNatts(tuple->t_data) >= var_tupdesc->natts);
+ Assert(variable->vartype != RECORDOID);
+ var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
/* Check to see if any dropped attributes are non-null */
for (i = 0; i < var_tupdesc->natts; i++)
{
Form_pg_attribute vattr = var_tupdesc->attrs[i];
- Form_pg_attribute sattr = slot->tts_tupleDescriptor->attrs[i];
+ Form_pg_attribute sattr = tupleDesc->attrs[i];
if (!vattr->attisdropped)
continue; /* already checked non-dropped cols */
/*
* We have to make a copy of the tuple so we can safely insert the Datum
- * overhead fields, which are not set in on-disk tuples; not to mention
- * fooling with its natts field.
+ * overhead fields, which are not set in on-disk tuples.
*/
dtuple = (HeapTupleHeader) palloc(tuple->t_len);
memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len);
HeapTupleHeaderSetTypeId(dtuple, variable->vartype);
HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod);
- HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts);
-
ReleaseTupleDesc(var_tupdesc);
return PointerGetDatum(dtuple);
}
/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
- /* As in ExecEvalVar, we should but can't check typmod */
+ /* As in ExecEvalScalarVar, we should but can't check typmod */
if (fselect->resulttype != attr->atttypid)
ereport(ERROR,
(errmsg("attribute %d has wrong type", fieldnum),
switch (nodeTag(node))
{
case T_Var:
- state = (ExprState *) makeNode(ExprState);
- state->evalfunc = ExecEvalVar;
+ /* varattno == InvalidAttrNumber means it's a whole-row Var */
+ if (((Var *) node)->varattno == InvalidAttrNumber)
+ {
+ WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
+
+ wstate->parent = parent;
+ wstate->wrv_junkFilter = NULL;
+ state = (ExprState *) wstate;
+ state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
+ }
+ else
+ {
+ state = (ExprState *) makeNode(ExprState);
+ state->evalfunc = ExecEvalScalarVar;
+ }
break;
case T_Const:
state = (ExprState *) makeNode(ExprState);