X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fexecutor%2FexecQual.c;h=c7bfe7c76ca6b926f2dcbe1eee05b3a0b8b6ecc0;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=8052f8b2d76590715ce199eb29c32fd2830a2709;hpb=8b35795362be5fd06fe22575a64bce81002a22c2;p=postgresql diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 8052f8b2d7..c7bfe7c76c 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -3,12 +3,12 @@ * execQual.c * Routines to evaluate qualification and targetlist expressions * - * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.206 2007/01/12 21:47:26 petere Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.247 2009/06/04 18:33:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,14 +29,13 @@ * instead of doing needless copying. -cim 5/31/91 * * During expression evaluation, we check_stack_depth only in - * ExecMakeFunctionResult rather than at every single node. This - * is a compromise that trades off precision of the stack limit setting - * to gain speed. + * ExecMakeFunctionResult (and substitute routines) rather than at every + * single node. This is a compromise that trades off precision of the + * stack limit setting to gain speed. */ #include "postgres.h" -#include "access/heapam.h" #include "access/nbtree.h" #include "catalog/pg_type.h" #include "commands/typecmds.h" @@ -45,8 +44,9 @@ #include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" -#include "optimizer/planmain.h" -#include "parser/parse_expr.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/planner.h" +#include "pgstat.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -62,20 +62,38 @@ static Datum ExecEvalArrayRef(ArrayRefExprState *astate, static Datum ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +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); static Datum ExecEvalParam(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static void init_fcache(Oid foid, FuncExprState *fcache, + MemoryContext fcacheCxt, bool needDescForSets); static void ShutdownFuncExpr(Datum arg); static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, TupleDesc *cache_field, ExprContext *econtext); static void ShutdownTupleDescRef(Datum arg); static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, List *argList, ExprContext *econtext); +static void ExecPrepareTuplestoreResult(FuncExprState *fcache, + ExprContext *econtext, + Tuplestorestate *resultStore, + TupleDesc resultDesc); +static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); +static Datum ExecMakeFunctionResult(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone); static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -118,7 +136,7 @@ static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -143,6 +161,14 @@ static Datum ExecEvalFieldStore(FieldStoreState *fstate, static Datum ExecEvalRelabelType(GenericExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); /* ---------------------------------------------------------------- @@ -420,11 +446,37 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, return econtext->ecxt_aggvalues[aggref->aggno]; } +/* ---------------------------------------------------------------- + * ExecEvalWindowFunc + * + * Returns a Datum whose value is the value of the precomputed + * window function found in the given expression context. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + if (isDone) + *isDone = ExprSingleResult; + + if (econtext->ecxt_aggvalues == NULL) /* safety check */ + elog(ERROR, "no window functions in this expression context"); + + *isNull = econtext->ecxt_aggnulls[wfunc->wfuncno]; + return econtext->ecxt_aggvalues[wfunc->wfuncno]; +} + /* ---------------------------------------------------------------- * 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 @@ -439,7 +491,7 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, *isDone = ExprSingleResult; /* - * Get the slot and attribute number we want + * Get the input slot and attribute number we want * * The asserts check that references to system attributes only appear at * the level of a relation scan; at higher levels, system attributes must @@ -466,35 +518,188 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, break; } -#ifdef USE_ASSERT_CHECKING - - /* - * Some checks that are only applied for user attribute numbers (bogus - * system attnums will be caught inside slot_getattr). - */ - if (attnum > 0) + if (attnum != InvalidAttrNumber) { - TupleDesc tuple_type = slot->tts_tupleDescriptor; - /* - * This assert checks that the attnum is valid. + * 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. */ - Assert(attnum <= tuple_type->natts); + 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 + { /* - * This assert checks that the datatype the plan expects to get (as - * told by our "variable" argument) is in fact the datatype of the - * attribute being fetched (as seen in the current context, identified - * by our "econtext" argument). Otherwise crashes are likely. + * 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. * - * Note that we can't check dropped columns, since their atttypid has - * been zeroed. + * If the Var identifies a named composite type, we must check that + * the actual tuple type is compatible with it. */ - Assert(variable->vartype == tuple_type->attrs[attnum - 1]->atttypid || - tuple_type->attrs[attnum - 1]->attisdropped); + 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 the physical storage + * matches. This is helpful in some cases involving out-of-date + * 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) + 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; + + 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) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } + + ReleaseTupleDesc(var_tupdesc); + } + + /* Skip the checking on future executions of node */ + if (needslow) + exprstate->evalfunc = ExecEvalWholeRowSlow; + else + exprstate->evalfunc = ExecEvalWholeRowVar; + + /* Fetch the value */ + return ExecEvalWholeRowVar(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; + AttrNumber attnum; + + if (isDone) + *isDone = ExprSingleResult; + + /* Get the input slot and attribute number 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; } -#endif /* USE_ASSERT_CHECKING */ + attnum = variable->varattno; + + /* Fetch the value from the slot */ return slot_getattr(slot, attnum, isNull); } @@ -502,10 +707,6 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, * ExecEvalWholeRowVar * * Returns a Datum for a whole-row variable. - * - * This could be folded into ExecEvalVar, but we make it a separate - * routine so as not to slow down ExecEvalVar with tests for this - * uncommon case. * ---------------------------------------------------------------- */ static Datum @@ -513,7 +714,7 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; - TupleTableSlot *slot; + TupleTableSlot *slot = econtext->ecxt_scantuple; HeapTuple tuple; TupleDesc tupleDesc; HeapTupleHeader dtuple; @@ -522,16 +723,6 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, *isDone = ExprSingleResult; *isNull = false; - Assert(variable->varattno == InvalidAttrNumber); - - /* - * Whole-row Vars can only appear at the level of a relation scan, never - * in a join. - */ - Assert(variable->varno != INNER); - Assert(variable->varno != OUTER); - slot = econtext->ecxt_scantuple; - tuple = ExecFetchSlotTuple(slot); tupleDesc = slot->tts_tupleDescriptor; @@ -547,9 +738,6 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, /* * If the Var identifies a named composite type, label the tuple with that * type; otherwise use what is in the tupleDesc. - * - * It's likely that the slot's tupleDesc is a record type; if so, make - * sure it's been "blessed", so that the Datum can be interpreted later. */ if (variable->vartype != RECORDOID) { @@ -558,9 +746,6 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, } else { - if (tupleDesc->tdtypeid == RECORDOID && - tupleDesc->tdtypmod < 0) - assign_record_type_typmod(tupleDesc); HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid); HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod); } @@ -568,6 +753,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 * @@ -785,8 +1024,9 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) /* * init_fcache - initialize a FuncExprState node during first use */ -void -init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) +static void +init_fcache(Oid foid, FuncExprState *fcache, + MemoryContext fcacheCxt, bool needDescForSets) { AclResult aclresult; @@ -804,16 +1044,67 @@ init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) if (list_length(fcache->args) > FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("cannot pass more than %d arguments to a function", - FUNC_MAX_ARGS))); + errmsg_plural("cannot pass more than %d argument to a function", + "cannot pass more than %d arguments to a function", + FUNC_MAX_ARGS, + FUNC_MAX_ARGS))); /* Set up the primary fmgr lookup information */ fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); + fcache->func.fn_expr = (Node *) fcache->xprstate.expr; + + /* If function returns set, prepare expected tuple descriptor */ + if (fcache->func.fn_retset && needDescForSets) + { + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + MemoryContext oldcontext; + + functypclass = get_expr_result_type(fcache->func.fn_expr, + &funcrettype, + &tupdesc); + + /* Must save tupdesc in fcache's context */ + oldcontext = MemoryContextSwitchTo(fcacheCxt); + + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + /* Must copy it out of typcache for safety */ + fcache->funcResultDesc = CreateTupleDescCopy(tupdesc); + fcache->funcReturnsTuple = true; + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + NULL, + funcrettype, + -1, + 0); + fcache->funcResultDesc = tupdesc; + fcache->funcReturnsTuple = false; + } + else + { + /* Else, we will complain if function wants materialize mode */ + fcache->funcResultDesc = NULL; + } - /* Initialize additional info */ + MemoryContextSwitchTo(oldcontext); + } + else + fcache->funcResultDesc = NULL; + + /* Initialize additional state */ + fcache->funcResultStore = NULL; + fcache->funcResultSlot = NULL; fcache->setArgsValid = false; fcache->shutdown_reg = false; - fcache->func.fn_expr = (Node *) fcache->xprstate.expr; } /* @@ -825,6 +1116,15 @@ ShutdownFuncExpr(Datum arg) { FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg); + /* If we have a slot, make sure it's let go of any tuplestore pointer */ + if (fcache->funcResultSlot) + ExecClearTuple(fcache->funcResultSlot); + + /* Release any open tuplestore */ + if (fcache->funcResultStore) + tuplestore_end(fcache->funcResultStore); + fcache->funcResultStore = NULL; + /* Clear any active set-argument state */ fcache->setArgsValid = false; @@ -933,39 +1233,215 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, return argIsDone; } +/* + * ExecPrepareTuplestoreResult + * + * Subroutine for ExecMakeFunctionResult: prepare to extract rows from a + * tuplestore function result. We must set up a funcResultSlot (unless + * already done in a previous call cycle) and verify that the function + * returned the expected tuple descriptor. + */ +static void +ExecPrepareTuplestoreResult(FuncExprState *fcache, + ExprContext *econtext, + Tuplestorestate *resultStore, + TupleDesc resultDesc) +{ + fcache->funcResultStore = resultStore; + + if (fcache->funcResultSlot == NULL) + { + /* Create a slot so we can read data out of the tuplestore */ + MemoryContext oldcontext; + + /* We must have been able to determine the result rowtype */ + if (fcache->funcResultDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning setof record called in " + "context that cannot accept type record"))); + + oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt); + fcache->funcResultSlot = + MakeSingleTupleTableSlot(fcache->funcResultDesc); + MemoryContextSwitchTo(oldcontext); + } + + /* + * If function provided a tupdesc, cross-check it. We only really + * need to do this for functions returning RECORD, but might as well + * do it always. + */ + if (resultDesc) + { + if (fcache->funcResultDesc) + tupledesc_match(fcache->funcResultDesc, resultDesc); + + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we must avoid + * leaking it across multiple usages. + */ + if (resultDesc->tdrefcount == -1) + FreeTupleDesc(resultDesc); + } + + /* Register cleanup callback if we didn't already */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownFuncExpr, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } +} + +/* + * Check that function result tuple type (src_tupdesc) matches or can + * be considered to match what the query expects (dst_tupdesc). If + * they don't match, ereport. + * + * 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 the physical storage matches. This is + * helpful in some cases involving out-of-date cached plans. + */ +static void +tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) +{ + int i; + + if (dst_tupdesc->natts != src_tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail_plural("Returned row contains %d attribute, but query expects %d.", + "Returned row contains %d attributes, but query expects %d.", + src_tupdesc->natts, + src_tupdesc->natts, dst_tupdesc->natts))); + + for (i = 0; i < dst_tupdesc->natts; i++) + { + Form_pg_attribute dattr = dst_tupdesc->attrs[i]; + Form_pg_attribute sattr = src_tupdesc->attrs[i]; + + if (dattr->atttypid == sattr->atttypid) + continue; /* no worries */ + if (!dattr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail("Returned type %s at ordinal position %d, but query expects %s.", + format_type_be(sattr->atttypid), + i + 1, + format_type_be(dattr->atttypid)))); + + if (dattr->attlen != sattr->attlen || + dattr->attalign != sattr->attalign) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } +} + /* * ExecMakeFunctionResult * * Evaluate the arguments to a function and then the function itself. + * init_fcache is presumed already run on the FuncExprState. + * + * This function handles the most general case, wherein the function or + * one of its arguments might (or might not) return a set. If we find + * no sets involved, we will change the FuncExprState's function pointer + * to use a simpler method on subsequent calls. */ -Datum +static Datum ExecMakeFunctionResult(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - List *arguments = fcache->args; + List *arguments; Datum result; - FunctionCallInfoData fcinfo; + FunctionCallInfoData fcinfo_data; + FunctionCallInfo fcinfo; + PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; /* for functions returning sets */ ExprDoneCond argDone; bool hasSetArg; int i; +restart: + /* Guard against stack overflow due to overly complex expressions */ check_stack_depth(); + /* + * If a previous call of the function returned a set result in the form + * of a tuplestore, continue reading rows from the tuplestore until it's + * empty. + */ + if (fcache->funcResultStore) + { + Assert(isDone); /* it was provided before ... */ + if (tuplestore_gettupleslot(fcache->funcResultStore, true, false, + fcache->funcResultSlot)) + { + *isDone = ExprMultipleResult; + if (fcache->funcReturnsTuple) + { + /* We must return the whole tuple as a Datum. */ + *isNull = false; + return ExecFetchSlotTupleDatum(fcache->funcResultSlot); + } + else + { + /* Extract the first column and return it as a scalar. */ + return slot_getattr(fcache->funcResultSlot, 1, isNull); + } + } + /* Exhausted the tuplestore, so clean up */ + tuplestore_end(fcache->funcResultStore); + fcache->funcResultStore = NULL; + /* We are done unless there was a set-valued argument */ + if (!fcache->setHasSetArg) + { + *isDone = ExprEndResult; + *isNull = true; + return (Datum) 0; + } + /* If there was, continue evaluating the argument values */ + Assert(!fcache->setArgsValid); + } + + /* + * For non-set-returning functions, we just use a local-variable + * FunctionCallInfoData. For set-returning functions we keep the callinfo + * record in fcache->setArgs so that it can survive across multiple + * value-per-call invocations. (The reason we don't just do the latter + * all the time is that plpgsql expects to be able to use simple expression + * trees re-entrantly. Which might not be a good idea, but the penalty + * for not doing so is high.) + */ + if (fcache->func.fn_retset) + fcinfo = &fcache->setArgs; + else + fcinfo = &fcinfo_data; + /* * arguments is a list of expressions to evaluate before passing to the * function manager. We skip the evaluation if it was already done in the * previous call (ie, we are continuing the evaluation of a set-valued * function). Otherwise, collect the current argument values into fcinfo. */ + arguments = fcache->args; if (!fcache->setArgsValid) { /* Need to prep callinfo structure */ - InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL); - argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext); + InitFunctionCallInfoData(*fcinfo, &(fcache->func), 0, NULL, NULL); + argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext); if (argDone == ExprEndResult) { /* input is an empty set, so return an empty set. */ @@ -982,32 +1458,14 @@ ExecMakeFunctionResult(FuncExprState *fcache, } else { - /* Copy callinfo from previous evaluation */ - memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo)); + /* Re-use callinfo from previous evaluation */ hasSetArg = fcache->setHasSetArg; /* Reset flag (we may set it again below) */ fcache->setArgsValid = false; } /* - * If function returns set, prepare a resultinfo node for communication - */ - if (fcache->func.fn_retset) - { - fcinfo.resultinfo = (Node *) &rsinfo; - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = NULL; - rsinfo.allowedModes = (int) SFRM_ValuePerCall; - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - } - - /* - * now return the value gotten by calling the function manager, passing - * the function the evaluated parameter values. + * Now call the function, passing the evaluated parameter values. */ if (fcache->func.fn_retset || hasSetArg) { @@ -1020,6 +1478,23 @@ ExecMakeFunctionResult(FuncExprState *fcache, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); + /* + * Prepare a resultinfo node for communication. If the function + * doesn't itself return set, we don't pass the resultinfo to the + * function, but we need to fill it in anyway for internal use. + */ + if (fcache->func.fn_retset) + fcinfo->resultinfo = (Node *) &rsinfo; + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = fcache->funcResultDesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + /* note we do not set SFRM_Materialize_Random or _Preferred */ + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + /* * This loop handles the situation where we have both a set argument * and a set-valued function. Once we have exhausted the function's @@ -1038,9 +1513,9 @@ ExecMakeFunctionResult(FuncExprState *fcache, if (fcache->func.fn_strict) { - for (i = 0; i < fcinfo.nargs; i++) + for (i = 0; i < fcinfo->nargs; i++) { - if (fcinfo.argnull[i]) + if (fcinfo->argnull[i]) { callit = false; break; @@ -1050,11 +1525,16 @@ ExecMakeFunctionResult(FuncExprState *fcache, if (callit) { - fcinfo.isnull = false; + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; rsinfo.isDone = ExprSingleResult; - result = FunctionCallInvoke(&fcinfo); - *isNull = fcinfo.isnull; + result = FunctionCallInvoke(fcinfo); + *isNull = fcinfo->isnull; *isDone = rsinfo.isDone; + + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); } else { @@ -1063,43 +1543,76 @@ ExecMakeFunctionResult(FuncExprState *fcache, *isDone = ExprEndResult; } - if (*isDone != ExprEndResult) + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) { - /* - * Got a result from current argument. If function itself - * returns set, save the current argument values to re-use on - * the next call. - */ - if (fcache->func.fn_retset && *isDone == ExprMultipleResult) + if (*isDone != ExprEndResult) { - memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo)); - fcache->setHasSetArg = hasSetArg; - fcache->setArgsValid = true; - /* Register cleanup callback if we didn't already */ - if (!fcache->shutdown_reg) + /* + * Got a result from current argument. If function itself + * returns set, save the current argument values to re-use + * on the next call. + */ + if (fcache->func.fn_retset && + *isDone == ExprMultipleResult) { - RegisterExprContextCallback(econtext, - ShutdownFuncExpr, - PointerGetDatum(fcache)); - fcache->shutdown_reg = true; + Assert(fcinfo == &fcache->setArgs); + fcache->setHasSetArg = hasSetArg; + fcache->setArgsValid = true; + /* Register cleanup callback if we didn't already */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownFuncExpr, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } } - } - /* - * Make sure we say we are returning a set, even if the - * function itself doesn't return sets. - */ - if (hasSetArg) - *isDone = ExprMultipleResult; - break; + /* + * Make sure we say we are returning a set, even if the + * function itself doesn't return sets. + */ + if (hasSetArg) + *isDone = ExprMultipleResult; + break; + } } + else if (rsinfo.returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (rsinfo.isDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("table-function protocol for materialize mode was not followed"))); + if (rsinfo.setResult != NULL) + { + /* prepare to return values from the tuplestore */ + ExecPrepareTuplestoreResult(fcache, econtext, + rsinfo.setResult, + rsinfo.setDesc); + /* remember whether we had set arguments */ + fcache->setHasSetArg = hasSetArg; + /* loop back to top to start returning from tuplestore */ + goto restart; + } + /* if setResult was left null, treat it as empty set */ + *isDone = ExprEndResult; + *isNull = true; + result = (Datum) 0; + } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo.returnMode))); /* Else, done with this argument */ if (!hasSetArg) break; /* input not a set, so done */ /* Re-eval args to get the next element of the input set */ - argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext); + argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext); if (argDone != ExprMultipleResult) { @@ -1137,18 +1650,23 @@ ExecMakeFunctionResult(FuncExprState *fcache, */ if (fcache->func.fn_strict) { - for (i = 0; i < fcinfo.nargs; i++) + for (i = 0; i < fcinfo->nargs; i++) { - if (fcinfo.argnull[i]) + if (fcinfo->argnull[i]) { *isNull = true; return (Datum) 0; } } } - fcinfo.isnull = false; - result = FunctionCallInvoke(&fcinfo); - *isNull = fcinfo.isnull; + + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + result = FunctionCallInvoke(fcinfo); + *isNull = fcinfo->isnull; + + pgstat_end_function_usage(&fcusage, true); } return result; @@ -1169,6 +1687,7 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache, ListCell *arg; Datum result; FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; int i; /* Guard against stack overflow due to overly complex expressions */ @@ -1207,10 +1726,15 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache, } } } + + pgstat_init_function_usage(&fcinfo, &fcusage); + /* fcinfo.isnull = false; */ /* handled by InitFunctionCallInfoData */ result = FunctionCallInvoke(&fcinfo); *isNull = fcinfo.isnull; + pgstat_end_function_usage(&fcusage, true); + return result; } @@ -1219,14 +1743,13 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache, * ExecMakeTableFunctionResult * * Evaluate a table function, producing a materialized result in a Tuplestore - * object. *returnDesc is set to the tupledesc actually returned by the - * function, or NULL if it didn't provide one. + * object. */ Tuplestorestate * ExecMakeTableFunctionResult(ExprState *funcexpr, ExprContext *econtext, TupleDesc expectedDesc, - TupleDesc *returnDesc) + bool randomAccess) { Tuplestorestate *tupstore = NULL; TupleDesc tupdesc = NULL; @@ -1234,6 +1757,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, bool returnsTuple; bool returnsSet = false; FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; HeapTupleData tmptup; MemoryContext callerContext; @@ -1258,7 +1782,9 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; rsinfo.expectedDesc = expectedDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); + if (randomAccess) + rsinfo.allowedModes |= (int) SFRM_Materialize_Random; rsinfo.returnMode = SFRM_ValuePerCall; /* isDone is filled below */ rsinfo.setResult = NULL; @@ -1292,7 +1818,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, { FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + init_fcache(func->funcid, fcache, + econtext->ecxt_per_query_memory, false); } returnsSet = fcache->func.fn_retset; @@ -1346,7 +1873,6 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, for (;;) { Datum result; - HeapTuple tuple; CHECK_FOR_INTERRUPTS(); @@ -1360,9 +1886,14 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, /* Call the function or expression one time */ if (direct_function_call) { + pgstat_init_function_usage(&fcinfo, &fcusage); + fcinfo.isnull = false; rsinfo.isDone = ExprSingleResult; result = FunctionCallInvoke(&fcinfo); + + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); } else { @@ -1427,7 +1958,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, -1, 0); } - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); MemoryContextSwitchTo(oldcontext); rsinfo.setResult = tupstore; rsinfo.setDesc = tupdesc; @@ -1448,15 +1979,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - tuple = &tmptup; + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_puttuple(tupstore, &tmptup); } else { - tuple = heap_form_tuple(tupdesc, &result, &fcinfo.isnull); + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull); } - - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tuplestore_puttuple(tupstore, tuple); MemoryContextSwitchTo(oldcontext); /* @@ -1494,29 +2025,44 @@ no_function_result: if (rsinfo.setResult == NULL) { MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); rsinfo.setResult = tupstore; if (!returnsSet) { int natts = expectedDesc->natts; Datum *nulldatums; bool *nullflags; - HeapTuple tuple; MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); nulldatums = (Datum *) palloc0(natts * sizeof(Datum)); nullflags = (bool *) palloc(natts * sizeof(bool)); memset(nullflags, true, natts * sizeof(bool)); - tuple = heap_form_tuple(expectedDesc, nulldatums, nullflags); MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tuplestore_puttuple(tupstore, tuple); + tuplestore_putvalues(tupstore, expectedDesc, nulldatums, nullflags); } } + /* + * If function provided a tupdesc, cross-check it. We only really + * need to do this for functions returning RECORD, but might as well + * do it always. + */ + if (rsinfo.setDesc) + { + tupledesc_match(expectedDesc, rsinfo.setDesc); + + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we must avoid + * leaking it across multiple usages. + */ + if (rsinfo.setDesc->tdrefcount == -1) + FreeTupleDesc(rsinfo.setDesc); + } + MemoryContextSwitchTo(callerContext); - /* The returned pointers are those in rsinfo */ - *returnDesc = rsinfo.setDesc; + /* All done, pass back the tuplestore */ return rsinfo.setResult; } @@ -1544,7 +2090,7 @@ ExecEvalFunc(FuncExprState *fcache, FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; /* Initialize function lookup info */ - init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory, true); /* Go directly to ExecMakeFunctionResult on subsequent uses */ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; @@ -1566,7 +2112,7 @@ ExecEvalOper(FuncExprState *fcache, OpExpr *op = (OpExpr *) fcache->xprstate.expr; /* Initialize function lookup info */ - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory, true); /* Go directly to ExecMakeFunctionResult on subsequent uses */ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; @@ -1608,7 +2154,8 @@ ExecEvalDistinct(FuncExprState *fcache, { DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr; - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + init_fcache(op->opfuncid, fcache, + econtext->ecxt_per_query_memory, true); Assert(!fcache->func.fn_retset); } @@ -1688,7 +2235,7 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, if (sstate->fxprstate.func.fn_oid == InvalidOid) { init_fcache(opexpr->opfuncid, &sstate->fxprstate, - econtext->ecxt_per_query_memory); + econtext->ecxt_per_query_memory, true); Assert(!sstate->fxprstate.func.fn_retset); } @@ -1771,8 +2318,8 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, else { elt = fetch_att(s, typbyval, typlen); - s = att_addlength(s, typlen, PointerGetDatum(s)); - s = (char *) att_align(s, typalign); + s = att_addlength_pointer(s, typlen, s); + s = (char *) att_align_nominal(s, typalign); fcinfo.arg[1] = elt; fcinfo.argnull[1] = false; } @@ -2376,9 +2923,9 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, /* * If all items were null or empty arrays, return an empty array; - * otherwise, if some were and some weren't, raise error. (Note: - * we must special-case this somehow to avoid trying to generate - * a 1-D array formed from empty arrays. It's not ideal...) + * otherwise, if some were and some weren't, raise error. (Note: we + * must special-case this somehow to avoid trying to generate a 1-D + * array formed from empty arrays. It's not ideal...) */ if (haveempty) { @@ -2411,7 +2958,7 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, } result = (ArrayType *) palloc(nbytes); - result->size = nbytes; + SET_VARSIZE(result, nbytes); result->ndim = ndims; result->dataoffset = dataoffset; result->elemtype = element_type; @@ -2646,15 +3193,11 @@ static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; - text *result; - StringInfoData buf; - Datum value; - bool isnull; - char *str; - ListCell *arg; + XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; + Datum value; + bool isnull; + ListCell *arg; ListCell *narg; - int i; if (isDone) *isDone = ExprSingleResult; @@ -2663,31 +3206,37 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, switch (xexpr->op) { case IS_XMLCONCAT: - initStringInfo(&buf); - foreach(arg, xmlExpr->args) { - ExprState *e = (ExprState *) lfirst(arg); + List *values = NIL; + + foreach(arg, xmlExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); - value = ExecEvalExpr(e, econtext, &isnull, NULL); - if (!isnull) + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (!isnull) + values = lappend(values, DatumGetPointer(value)); + } + + if (list_length(values) > 0) { - /* we know the value is XML type */ - str = DatumGetCString(DirectFunctionCall1(xml_out, - value)); - appendStringInfoString(&buf, str); - pfree(str); *isNull = false; + return PointerGetDatum(xmlconcat(values)); } + else + return (Datum) 0; } break; case IS_XMLFOREST: + { + StringInfoData buf; + initStringInfo(&buf); - i = 0; forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names) { - ExprState *e = (ExprState *) lfirst(arg); - char *argname = strVal(lfirst(narg)); + ExprState *e = (ExprState *) lfirst(arg); + char *argname = strVal(lfirst(narg)); value = ExecEvalExpr(e, econtext, &isnull, NULL); if (!isnull) @@ -2698,11 +3247,25 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, argname); *isNull = false; } - i++; } + + if (*isNull) + { + pfree(buf.data); + return (Datum) 0; + } + else + { + text *result; + + result = cstring_to_text_with_len(buf.data, buf.len); + pfree(buf.data); + + return PointerGetDatum(result); + } + } break; - /* The remaining cases don't need to set up buf */ case IS_XMLELEMENT: *isNull = false; return PointerGetDatum(xmlelement(xmlExpr, econtext)); @@ -2710,13 +3273,12 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, case IS_XMLPARSE: { - ExprState *e; - text *data; - bool is_document; + ExprState *e; + text *data; bool preserve_whitespace; - /* arguments are known to be text, bool, bool */ - Assert(list_length(xmlExpr->args) == 3); + /* arguments are known to be text, bool */ + Assert(list_length(xmlExpr->args) == 2); e = (ExprState *) linitial(xmlExpr->args); value = ExecEvalExpr(e, econtext, &isnull, NULL); @@ -2726,12 +3288,6 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, e = (ExprState *) lsecond(xmlExpr->args); value = ExecEvalExpr(e, econtext, &isnull, NULL); - if (isnull) /* probably can't happen */ - return (Datum) 0; - is_document = DatumGetBool(value); - - e = (ExprState *) lthird(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull, NULL); if (isnull) /* probably can't happen */ return (Datum) 0; preserve_whitespace = DatumGetBool(value); @@ -2739,15 +3295,15 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, *isNull = false; return PointerGetDatum(xmlparse(data, - is_document, + xexpr->xmloption, preserve_whitespace)); } break; case IS_XMLPI: { - ExprState *e; - text *arg; + ExprState *e; + text *arg; /* optional argument is known to be text */ Assert(list_length(xmlExpr->args) <= 1); @@ -2773,12 +3329,12 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, case IS_XMLROOT: { - ExprState *e; - xmltype *data; - text *version; + ExprState *e; + xmltype *data; + text *version; int standalone; - /* arguments are known to be xml, text, bool */ + /* arguments are known to be xml, text, int */ Assert(list_length(xmlExpr->args) == 3); e = (ExprState *) linitial(xmlExpr->args); @@ -2796,10 +3352,7 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, e = (ExprState *) lthird(xmlExpr->args); value = ExecEvalExpr(e, econtext, &isnull, NULL); - if (isnull) - standalone = 0; - else - standalone = (DatumGetBool(value) ? 1 : -1); + standalone = DatumGetInt32(value); *isNull = false; @@ -2808,21 +3361,47 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, standalone)); } break; - } - if (*isNull) - result = NULL; - else - { - int len = buf.len + VARHDRSZ; + case IS_XMLSERIALIZE: + { + ExprState *e; + + /* argument type is known to be xml */ + Assert(list_length(xmlExpr->args) == 1); + + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + + *isNull = false; + + return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption)); + } + break; - result = palloc(len); - VARATT_SIZEP(result) = len; - memcpy(VARDATA(result), buf.data, buf.len); + case IS_DOCUMENT: + { + ExprState *e; + + /* optional argument is known to be xml */ + Assert(list_length(xmlExpr->args) == 1); + + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + else + { + *isNull = false; + return BoolGetDatum(xml_is_document(DatumGetXmlP(value))); + } + } + break; } - pfree(buf.data); - return PointerGetDatum(result); + elog(ERROR, "unrecognized XML operation"); + return (Datum) 0; } /* ---------------------------------------------------------------- @@ -2853,7 +3432,8 @@ ExecEvalNullIf(FuncExprState *nullIfExpr, { NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr; - init_fcache(op->opfuncid, nullIfExpr, econtext->ecxt_per_query_memory); + init_fcache(op->opfuncid, nullIfExpr, + econtext->ecxt_per_query_memory, true); Assert(!nullIfExpr->func.fn_retset); } @@ -3175,12 +3755,14 @@ ExecEvalFieldSelect(FieldSelectState *fstate, ExprDoneCond *isDone) { FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr; + AttrNumber fieldnum = fselect->fieldnum; Datum result; Datum tupDatum; HeapTupleHeader tuple; Oid tupType; int32 tupTypmod; TupleDesc tupDesc; + Form_pg_attribute attr; HeapTupleData tmptup; tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); @@ -3198,6 +3780,27 @@ ExecEvalFieldSelect(FieldSelectState *fstate, tupDesc = get_cached_rowtype(tupType, tupTypmod, &fstate->argdesc, econtext); + /* Check for dropped column, and force a NULL result if so */ + if (fieldnum <= 0 || + fieldnum > tupDesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + fieldnum, tupDesc->natts); + attr = tupDesc->attrs[fieldnum - 1]; + if (attr->attisdropped) + { + *isNull = true; + return (Datum) 0; + } + + /* 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), + errdetail("Table has type %s, but query expects %s.", + format_type_be(attr->atttypid), + format_type_be(fselect->resulttype)))); + /* * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all * the fields in the struct just in case user tries to inspect system @@ -3209,7 +3812,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate, tmptup.t_data = tuple; result = heap_getattr(&tmptup, - fselect->fieldnum, + fieldnum, tupDesc, isNull); return result; @@ -3328,6 +3931,133 @@ ExecEvalRelabelType(GenericExprState *exprstate, return ExecEvalExpr(exprstate->arg, econtext, isNull, isDone); } +/* ---------------------------------------------------------------- + * ExecEvalCoerceViaIO + * + * Evaluate a CoerceViaIO node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCoerceViaIO(CoerceViaIOState *iostate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Datum result; + Datum inputval; + char *string; + + inputval = ExecEvalExpr(iostate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return inputval; /* nothing to do */ + + if (*isNull) + string = NULL; /* output functions are not called on nulls */ + else + string = OutputFunctionCall(&iostate->outfunc, inputval); + + result = InputFunctionCall(&iostate->infunc, + string, + iostate->intypioparam, + -1); + + /* The input function cannot change the null/not-null status */ + return result; +} + +/* ---------------------------------------------------------------- + * ExecEvalArrayCoerceExpr + * + * Evaluate an ArrayCoerceExpr node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr; + Datum result; + ArrayType *array; + FunctionCallInfoData locfcinfo; + + result = ExecEvalExpr(astate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return result; /* nothing to do */ + if (*isNull) + return result; /* nothing to do */ + + /* + * If it's binary-compatible, modify the element type in the array header, + * but otherwise leave the array as we received it. + */ + if (!OidIsValid(acoerce->elemfuncid)) + { + /* Detoast input array if necessary, and copy in any case */ + array = DatumGetArrayTypePCopy(result); + ARR_ELEMTYPE(array) = astate->resultelemtype; + PG_RETURN_ARRAYTYPE_P(array); + } + + /* Detoast input array if necessary, but don't make a useless copy */ + array = DatumGetArrayTypeP(result); + + /* Initialize function cache if first time through */ + if (astate->elemfunc.fn_oid == InvalidOid) + { + AclResult aclresult; + + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(acoerce->elemfuncid, GetUserId(), + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_PROC, + get_func_name(acoerce->elemfuncid)); + + /* Set up the primary fmgr lookup information */ + fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc), + econtext->ecxt_per_query_memory); + + /* Initialize additional info */ + astate->elemfunc.fn_expr = (Node *) acoerce; + } + + /* + * Use array_map to apply the function to each array element. + * + * We pass on the desttypmod and isExplicit flags whether or not the + * function wants them. + */ + InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3, + NULL, NULL); + locfcinfo.arg[0] = PointerGetDatum(array); + locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod); + locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit); + locfcinfo.argnull[0] = false; + locfcinfo.argnull[1] = false; + locfcinfo.argnull[2] = false; + + return array_map(&locfcinfo, ARR_ELEMTYPE(array), astate->resultelemtype, + astate->amstate); +} + +/* ---------------------------------------------------------------- + * ExecEvalCurrentOfExpr + * + * The planner must convert CURRENT OF into a TidScan qualification. + * So, we have to be able to do ExecInitExpr on a CurrentOfExpr, + * but we shouldn't ever actually execute it. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + elog(ERROR, "CURRENT OF cannot be executed"); + return 0; /* keep compiler quiet */ +} + /* * ExecEvalExprSwitchContext @@ -3363,12 +4093,12 @@ ExecEvalExprSwitchContext(ExprState *expression, * executions of the expression are needed. Typically the context will be * the same as the per-query context of the associated ExprContext. * - * Any Aggref and SubPlan nodes found in the tree are added to the lists - * of such nodes held by the parent PlanState. Otherwise, we do very little - * initialization here other than building the state-node tree. Any nontrivial - * work associated with initializing runtime info for a node should happen - * during the first actual evaluation of that node. (This policy lets us - * avoid work if the node is never actually evaluated.) + * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the + * lists of such nodes held by the parent PlanState. Otherwise, we do very + * little initialization here other than building the state-node tree. Any + * nontrivial work associated with initializing runtime info for a node should + * happen during the first actual evaluation of that node. (This policy lets + * us avoid work if the node is never actually evaluated.) * * Note: there is no ExecEndExpr function; we assume that any resource * cleanup needed will be handled by just releasing the memory context @@ -3396,15 +4126,8 @@ ExecInitExpr(Expr *node, PlanState *parent) switch (nodeTag(node)) { case T_Var: - { - Var *var = (Var *) node; - - state = (ExprState *) makeNode(ExprState); - if (var->varattno != InvalidAttrNumber) - state->evalfunc = ExecEvalVar; - else - state->evalfunc = ExecEvalWholeRowVar; - } + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalVar; break; case T_Const: state = (ExprState *) makeNode(ExprState); @@ -3448,16 +4171,54 @@ ExecInitExpr(Expr *node, PlanState *parent) if (naggs != aggstate->numaggs) ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), - errmsg("aggregate function calls may not be nested"))); + errmsg("aggregate function calls cannot be nested"))); } else { /* planner messed up */ - elog(ERROR, "aggref found in non-Agg plan node"); + elog(ERROR, "Aggref found in non-Agg plan node"); } state = (ExprState *) astate; } break; + case T_WindowFunc: + { + WindowFunc *wfunc = (WindowFunc *) node; + WindowFuncExprState *wfstate = makeNode(WindowFuncExprState); + + wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc; + if (parent && IsA(parent, WindowAggState)) + { + WindowAggState *winstate = (WindowAggState *) parent; + int nfuncs; + + winstate->funcs = lcons(wfstate, winstate->funcs); + nfuncs = ++winstate->numfuncs; + if (wfunc->winagg) + winstate->numaggs++; + + wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args, + parent); + + /* + * Complain if the windowfunc's arguments contain any + * windowfuncs; nested window functions are semantically + * nonsensical. (This should have been caught earlier, + * but we defend against it here anyway.) + */ + if (nfuncs != winstate->numfuncs) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("window function calls cannot be nested"))); + } + else + { + /* planner messed up */ + elog(ERROR, "WindowFunc found in non-WindowAgg plan node"); + } + state = (ExprState *) wfstate; + } + break; case T_ArrayRef: { ArrayRef *aref = (ArrayRef *) node; @@ -3557,31 +4318,33 @@ ExecInitExpr(Expr *node, PlanState *parent) break; case T_SubPlan: { - /* Keep this in sync with ExecInitExprInitPlan, below */ SubPlan *subplan = (SubPlan *) node; - SubPlanState *sstate = makeNode(SubPlanState); - - sstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecSubPlan; + SubPlanState *sstate; if (!parent) elog(ERROR, "SubPlan found with no parent plan"); - /* - * Here we just add the SubPlanState nodes to parent->subPlan. - * The subplans will be initialized later. - */ - parent->subPlan = lcons(sstate, parent->subPlan); - sstate->sub_estate = NULL; - sstate->planstate = NULL; + sstate = ExecInitSubPlan(subplan, parent); - sstate->testexpr = - ExecInitExpr((Expr *) subplan->testexpr, parent); - sstate->args = (List *) - ExecInitExpr((Expr *) subplan->args, parent); + /* Add SubPlanState nodes to parent->subPlan */ + parent->subPlan = lappend(parent->subPlan, sstate); state = (ExprState *) sstate; } break; + case T_AlternativeSubPlan: + { + AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; + AlternativeSubPlanState *asstate; + + if (!parent) + elog(ERROR, "AlternativeSubPlan found with no parent plan"); + + asstate = ExecInitAlternativeSubPlan(asplan, parent); + + state = (ExprState *) asstate; + } + break; case T_FieldSelect: { FieldSelect *fselect = (FieldSelect *) node; @@ -3615,6 +4378,46 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) gstate; } break; + case T_CoerceViaIO: + { + CoerceViaIO *iocoerce = (CoerceViaIO *) node; + CoerceViaIOState *iostate = makeNode(CoerceViaIOState); + Oid iofunc; + bool typisvarlena; + + iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO; + iostate->arg = ExecInitExpr(iocoerce->arg, parent); + /* lookup the result type's input function */ + getTypeInputInfo(iocoerce->resulttype, &iofunc, + &iostate->intypioparam); + fmgr_info(iofunc, &iostate->infunc); + /* lookup the input type's output function */ + getTypeOutputInfo(exprType((Node *) iocoerce->arg), + &iofunc, &typisvarlena); + fmgr_info(iofunc, &iostate->outfunc); + state = (ExprState *) iostate; + } + break; + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; + ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState); + + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr; + astate->arg = ExecInitExpr(acoerce->arg, parent); + astate->resultelemtype = get_element_type(acoerce->resulttype); + if (astate->resultelemtype == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("target type is not an array"))); + /* Arrays over domains aren't supported yet */ + Assert(getBaseType(astate->resultelemtype) == + astate->resultelemtype); + astate->elemfunc.fn_oid = InvalidOid; /* not initialized */ + astate->amstate = (ArrayMapState *) palloc0(sizeof(ArrayMapState)); + state = (ExprState *) astate; + } + break; case T_ConvertRowtypeExpr: { ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; @@ -3730,7 +4533,7 @@ ExecInitExpr(Expr *node, PlanState *parent) * don't really care what type of NULL it is, so * always make an int4 NULL. */ - e = (Expr *) makeNullConst(INT4OID); + e = (Expr *) makeNullConst(INT4OID, -1); } estate = ExecInitExpr(e, parent); outlist = lappend(outlist, estate); @@ -3783,14 +4586,12 @@ ExecInitExpr(Expr *node, PlanState *parent) int strategy; Oid lefttype; Oid righttype; - bool recheck; Oid proc; get_op_opfamily_properties(opno, opfamily, &strategy, &lefttype, - &righttype, - &recheck); + &righttype); proc = get_opfamily_proc(opfamily, lefttype, righttype, @@ -3867,39 +4668,28 @@ ExecInitExpr(Expr *node, PlanState *parent) break; case T_XmlExpr: { - XmlExpr *xexpr = (XmlExpr *) node; - XmlExprState *xstate = makeNode(XmlExprState); - List *outlist; - ListCell *arg; - int i; + XmlExpr *xexpr = (XmlExpr *) node; + XmlExprState *xstate = makeNode(XmlExprState); + List *outlist; + ListCell *arg; xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml; - xstate->named_outfuncs = (FmgrInfo *) - palloc0(list_length(xexpr->named_args) * sizeof(FmgrInfo)); outlist = NIL; - i = 0; foreach(arg, xexpr->named_args) { - Expr *e = (Expr *) lfirst(arg); - ExprState *estate; - Oid typOutFunc; - bool typIsVarlena; + Expr *e = (Expr *) lfirst(arg); + ExprState *estate; estate = ExecInitExpr(e, parent); outlist = lappend(outlist, estate); - - getTypeOutputInfo(exprType((Node *) e), - &typOutFunc, &typIsVarlena); - fmgr_info(typOutFunc, &xstate->named_outfuncs[i]); - i++; } xstate->named_args = outlist; outlist = NIL; foreach(arg, xexpr->args) { - Expr *e = (Expr *) lfirst(arg); - ExprState *estate; + Expr *e = (Expr *) lfirst(arg); + ExprState *estate; estate = ExecInitExpr(e, parent); outlist = lappend(outlist, estate); @@ -3954,6 +4744,10 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) cstate; } break; + case T_CurrentOfExpr: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalCurrentOfExpr; + break; case T_TargetEntry: { TargetEntry *tle = (TargetEntry *) node; @@ -3991,41 +4785,16 @@ ExecInitExpr(Expr *node, PlanState *parent) return state; } -/* - * ExecInitExprInitPlan --- initialize a subplan expr that's being handled - * as an InitPlan. This is identical to ExecInitExpr's handling of a regular - * subplan expr, except we do NOT want to add the node to the parent's - * subplan list. - */ -SubPlanState * -ExecInitExprInitPlan(SubPlan *node, PlanState *parent) -{ - SubPlanState *sstate = makeNode(SubPlanState); - - if (!parent) - elog(ERROR, "SubPlan found with no parent plan"); - - /* The subplan's state will be initialized later */ - sstate->sub_estate = NULL; - sstate->planstate = NULL; - - sstate->testexpr = ExecInitExpr((Expr *) node->testexpr, parent); - sstate->args = (List *) ExecInitExpr((Expr *) node->args, parent); - - sstate->xprstate.expr = (Expr *) node; - - return sstate; -} - /* * ExecPrepareExpr --- initialize for expression execution outside a normal * Plan tree context. * * This differs from ExecInitExpr in that we don't assume the caller is - * already running in the EState's per-query context. Also, we apply - * fix_opfuncids() to the passed expression tree to be sure it is ready - * to run. (In ordinary Plan trees the planner will have fixed opfuncids, - * but callers outside the executor will not have done this.) + * already running in the EState's per-query context. Also, we run the + * passed expression tree through expression_planner() to prepare it for + * execution. (In ordinary Plan trees the regular planning process will have + * made the appropriate transformations on expressions, but for standalone + * expressions this won't have happened.) */ ExprState * ExecPrepareExpr(Expr *node, EState *estate) @@ -4033,10 +4802,10 @@ ExecPrepareExpr(Expr *node, EState *estate) ExprState *result; MemoryContext oldcontext; - fix_opfuncids((Node *) node); - oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + node = expression_planner(node); + result = ExecInitExpr(node, NULL); MemoryContextSwitchTo(oldcontext); @@ -4187,6 +4956,7 @@ ExecCleanTargetListLength(List *targetlist) * prepared to deal with sets of result tuples. Otherwise, a return * of *isDone = ExprMultipleResult signifies a set element, and a return * of *isDone = ExprEndResult signifies end of the set of tuple. + * We assume that *isDone has been initialized to ExprSingleResult by caller. */ static bool ExecTargetList(List *targetlist, @@ -4208,9 +4978,6 @@ ExecTargetList(List *targetlist, /* * evaluate all the expressions in the target list */ - if (isDone) - *isDone = ExprSingleResult; /* until proven otherwise */ - haveDoneSets = false; /* any exhausted set exprs in tlist? */ foreach(tl, targetlist) @@ -4325,50 +5092,6 @@ ExecTargetList(List *targetlist, return true; } -/* - * ExecVariableList - * Evaluates a simple-Variable-list projection. - * - * Results are stored into the passed values and isnull arrays. - */ -static void -ExecVariableList(ProjectionInfo *projInfo, - Datum *values, - bool *isnull) -{ - ExprContext *econtext = projInfo->pi_exprContext; - int *varSlotOffsets = projInfo->pi_varSlotOffsets; - int *varNumbers = projInfo->pi_varNumbers; - int i; - - /* - * Force extraction of all input values that we need. - */ - if (projInfo->pi_lastInnerVar > 0) - slot_getsomeattrs(econtext->ecxt_innertuple, - projInfo->pi_lastInnerVar); - if (projInfo->pi_lastOuterVar > 0) - slot_getsomeattrs(econtext->ecxt_outertuple, - projInfo->pi_lastOuterVar); - if (projInfo->pi_lastScanVar > 0) - slot_getsomeattrs(econtext->ecxt_scantuple, - projInfo->pi_lastScanVar); - - /* - * Assign to result by direct extraction of fields from source slots ... a - * mite ugly, but fast ... - */ - for (i = list_length(projInfo->pi_targetlist) - 1; i >= 0; i--) - { - char *slotptr = ((char *) econtext) + varSlotOffsets[i]; - TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); - int varNumber = varNumbers[i] - 1; - - values[i] = varSlot->tts_values[varNumber]; - isnull[i] = varSlot->tts_isnull[varNumber]; - } -} - /* * ExecProject * @@ -4386,6 +5109,8 @@ TupleTableSlot * ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) { TupleTableSlot *slot; + ExprContext *econtext; + int numSimpleVars; /* * sanity checks @@ -4396,6 +5121,11 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) * get the projection info we want */ slot = projInfo->pi_slot; + econtext = projInfo->pi_exprContext; + + /* Assume single result row until proven otherwise */ + if (isDone) + *isDone = ExprSingleResult; /* * Clear any former contents of the result slot. This makes it safe for @@ -4405,29 +5135,84 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) ExecClearTuple(slot); /* - * form a new result tuple (if possible); if successful, mark the result - * slot as containing a valid virtual tuple + * Force extraction of all input values that we'll need. The + * Var-extraction loops below depend on this, and we are also prefetching + * all attributes that will be referenced in the generic expressions. + */ + if (projInfo->pi_lastInnerVar > 0) + slot_getsomeattrs(econtext->ecxt_innertuple, + projInfo->pi_lastInnerVar); + if (projInfo->pi_lastOuterVar > 0) + slot_getsomeattrs(econtext->ecxt_outertuple, + projInfo->pi_lastOuterVar); + if (projInfo->pi_lastScanVar > 0) + slot_getsomeattrs(econtext->ecxt_scantuple, + projInfo->pi_lastScanVar); + + /* + * Assign simple Vars to result by direct extraction of fields from source + * slots ... a mite ugly, but fast ... */ - if (projInfo->pi_isVarList) + numSimpleVars = projInfo->pi_numSimpleVars; + if (numSimpleVars > 0) { - /* simple Var list: this always succeeds with one result row */ - if (isDone) - *isDone = ExprSingleResult; - ExecVariableList(projInfo, - slot->tts_values, - slot->tts_isnull); - ExecStoreVirtualTuple(slot); + Datum *values = slot->tts_values; + bool *isnull = slot->tts_isnull; + int *varSlotOffsets = projInfo->pi_varSlotOffsets; + int *varNumbers = projInfo->pi_varNumbers; + int i; + + if (projInfo->pi_directMap) + { + /* especially simple case where vars go to output in order */ + for (i = 0; i < numSimpleVars; i++) + { + char *slotptr = ((char *) econtext) + varSlotOffsets[i]; + TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); + int varNumber = varNumbers[i] - 1; + + values[i] = varSlot->tts_values[varNumber]; + isnull[i] = varSlot->tts_isnull[varNumber]; + } + } + else + { + /* we have to pay attention to varOutputCols[] */ + int *varOutputCols = projInfo->pi_varOutputCols; + + for (i = 0; i < numSimpleVars; i++) + { + char *slotptr = ((char *) econtext) + varSlotOffsets[i]; + TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); + int varNumber = varNumbers[i] - 1; + int varOutputCol = varOutputCols[i] - 1; + + values[varOutputCol] = varSlot->tts_values[varNumber]; + isnull[varOutputCol] = varSlot->tts_isnull[varNumber]; + } + } } - else + + /* + * If there are any generic expressions, evaluate them. It's possible + * that there are set-returning functions in such expressions; if so + * and we have reached the end of the set, we return the result slot, + * which we already marked empty. + */ + if (projInfo->pi_targetlist) { - if (ExecTargetList(projInfo->pi_targetlist, - projInfo->pi_exprContext, - slot->tts_values, - slot->tts_isnull, - projInfo->pi_itemIsDone, - isDone)) - ExecStoreVirtualTuple(slot); + if (!ExecTargetList(projInfo->pi_targetlist, + econtext, + slot->tts_values, + slot->tts_isnull, + projInfo->pi_itemIsDone, + isDone)) + return slot; /* no more result rows, return empty slot */ } - return slot; + /* + * Successfully formed a result row. Mark the result slot as containing a + * valid virtual tuple. + */ + return ExecStoreVirtualTuple(slot); }