X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fexecutor%2FexecQual.c;h=c7bfe7c76ca6b926f2dcbe1eee05b3a0b8b6ecc0;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=fe78a0fa0841daffdf8bf988888f2c0f8937e038;hpb=6e07709760a29d8dbfb93b9846c905bd40689082;p=postgresql diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index fe78a0fa08..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-2005, 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.187 2005/12/28 01:29:59 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.247 2009/06/04 18:33:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,31 +29,30 @@ * 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" #include "executor/execdebug.h" -#include "executor/functions.h" #include "executor/nodeSubplan.h" #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/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/typcache.h" +#include "utils/xml.h" /* static function decls */ @@ -63,16 +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); @@ -106,18 +127,20 @@ static Datum ExecEvalRow(RowExprState *rstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalRowCompare(RowCompareExprState *rstate, - ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone); + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalNullTest(GenericExprState *nstate, +static Datum ExecEvalNullTest(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalBooleanTest(GenericExprState *bstate, @@ -138,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); /* ---------------------------------------------------------------- @@ -272,7 +303,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate, if (isAssignment) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("array subscript in assignment must not be NULL"))); + errmsg("array subscript in assignment must not be null"))); *isNull = true; return (Datum) NULL; } @@ -300,7 +331,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate, if (isAssignment) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("array subscript in assignment must not be NULL"))); + errmsg("array subscript in assignment must not be null"))); *isNull = true; return (Datum) NULL; } @@ -415,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 @@ -434,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 @@ -461,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); } -#endif /* USE_ASSERT_CHECKING */ +} + +/* ---------------------------------------------------------------- + * 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; + } + + attnum = variable->varattno; + + /* Fetch the value from the slot */ return slot_getattr(slot, attnum, isNull); } @@ -497,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 @@ -508,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; @@ -517,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; @@ -542,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) { @@ -553,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); } @@ -563,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 * @@ -593,11 +837,6 @@ ExecEvalConst(ExprState *exprstate, ExprContext *econtext, * something like ($.name) and the expression context contains * the current parameter bindings (name = "sam") (age = 34)... * so our job is to find and return the appropriate datum ("sam"). - * - * Q: if we have a parameter ($.foo) without a binding, i.e. - * there is no (foo = xxx) in the parameter list info, - * is this a fatal error or should this be a "not available" - * (in which case we could return NULL)? -cim 10/13/89 * ---------------------------------------------------------------- */ static Datum @@ -605,13 +844,12 @@ ExecEvalParam(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { Param *expression = (Param *) exprstate->expr; - int thisParamKind = expression->paramkind; - AttrNumber thisParamId = expression->paramid; + int thisParamId = expression->paramid; if (isDone) *isDone = ExprSingleResult; - if (thisParamKind == PARAM_EXEC) + if (expression->paramkind == PARAM_EXEC) { /* * PARAM_EXEC params (internal executor parameters) are stored in the @@ -633,18 +871,27 @@ ExecEvalParam(ExprState *exprstate, ExprContext *econtext, else { /* - * All other parameter types must be sought in ecxt_param_list_info. + * PARAM_EXTERN parameters must be sought in ecxt_param_list_info. */ - ParamListInfo paramInfo; + ParamListInfo paramInfo = econtext->ecxt_param_list_info; + + Assert(expression->paramkind == PARAM_EXTERN); + if (paramInfo && + thisParamId > 0 && thisParamId <= paramInfo->numParams) + { + ParamExternData *prm = ¶mInfo->params[thisParamId - 1]; - paramInfo = lookupParam(econtext->ecxt_param_list_info, - thisParamKind, - expression->paramname, - thisParamId, - false); - Assert(paramInfo->ptype == expression->paramtype); - *isNull = paramInfo->isnull; - return paramInfo->value; + if (OidIsValid(prm->ptype)) + { + Assert(prm->ptype == expression->paramtype); + *isNull = prm->isnull; + return prm->value; + } + } + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no value found for parameter %d", thisParamId))); + return (Datum) 0; /* keep compiler quiet */ } } @@ -707,6 +954,9 @@ GetAttributeByNum(HeapTupleHeader tuple, attrno, tupDesc, isNull); + + ReleaseTupleDesc(tupDesc); + return result; } @@ -765,14 +1015,18 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) attrno, tupDesc, isNull); + + ReleaseTupleDesc(tupDesc); + return result; } /* * 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; @@ -790,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; - /* Initialize additional info */ + /* 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; + } + + 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; } /* @@ -811,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; @@ -818,6 +1132,61 @@ ShutdownFuncExpr(Datum arg) fcache->shutdown_reg = false; } +/* + * get_cached_rowtype: utility function to lookup a rowtype tupdesc + * + * type_id, typmod: identity of the rowtype + * cache_field: where to cache the TupleDesc pointer in expression state node + * (field must be initialized to NULL) + * econtext: expression context we are executing in + * + * NOTE: because the shutdown callback will be called during plan rescan, + * must be prepared to re-do this during any node execution; cannot call + * just once during expression initialization + */ +static TupleDesc +get_cached_rowtype(Oid type_id, int32 typmod, + TupleDesc *cache_field, ExprContext *econtext) +{ + TupleDesc tupDesc = *cache_field; + + /* Do lookup if no cached value or if requested type changed */ + if (tupDesc == NULL || + type_id != tupDesc->tdtypeid || + typmod != tupDesc->tdtypmod) + { + tupDesc = lookup_rowtype_tupdesc(type_id, typmod); + + if (*cache_field) + { + /* Release old tupdesc; but callback is already registered */ + ReleaseTupleDesc(*cache_field); + } + else + { + /* Need to register shutdown callback to release tupdesc */ + RegisterExprContextCallback(econtext, + ShutdownTupleDescRef, + PointerGetDatum(cache_field)); + } + *cache_field = tupDesc; + } + return tupDesc; +} + +/* + * Callback function to release a tupdesc refcount at expression tree shutdown + */ +static void +ShutdownTupleDescRef(Datum arg) +{ + TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg); + + if (*cache_field) + ReleaseTupleDesc(*cache_field); + *cache_field = NULL; +} + /* * Evaluate arguments for a function. */ @@ -864,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. */ @@ -913,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) { @@ -951,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 @@ -969,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; @@ -981,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 { @@ -994,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) { @@ -1068,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; @@ -1100,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 */ @@ -1138,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; } @@ -1150,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; @@ -1165,6 +1757,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, bool returnsTuple; bool returnsSet = false; FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; HeapTupleData tmptup; MemoryContext callerContext; @@ -1176,8 +1769,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, funcrettype = exprType((Node *) funcexpr->expr); - returnsTuple = (funcrettype == RECORDOID || - get_typtype(funcrettype) == 'c'); + returnsTuple = type_is_rowtype(funcrettype); /* * Prepare a resultinfo node for communication. We always do this even if @@ -1190,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; @@ -1224,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; @@ -1278,7 +1873,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, for (;;) { Datum result; - HeapTuple tuple; + + CHECK_FOR_INTERRUPTS(); /* * reset per-tuple memory context before each call of the function or @@ -1290,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 { @@ -1341,9 +1942,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, HeapTupleHeader td; td = DatumGetHeapTupleHeader(result); - tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(td), + tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), HeapTupleHeaderGetTypMod(td)); - tupdesc = CreateTupleDescCopy(tupdesc); } else { @@ -1358,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; @@ -1379,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); /* @@ -1425,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; } @@ -1475,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; @@ -1497,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; @@ -1539,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); } @@ -1619,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); } @@ -1702,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; } @@ -1909,17 +2525,18 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { + ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) cstate->xprstate.expr; HeapTuple result; Datum tupDatum; HeapTupleHeader tuple; HeapTupleData tmptup; - AttrNumber *attrMap = cstate->attrMap; - Datum *invalues = cstate->invalues; - bool *inisnull = cstate->inisnull; - Datum *outvalues = cstate->outvalues; - bool *outisnull = cstate->outisnull; + AttrNumber *attrMap; + Datum *invalues; + bool *inisnull; + Datum *outvalues; + bool *outisnull; int i; - int outnatts = cstate->outdesc->natts; + int outnatts; tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); @@ -1929,9 +2546,82 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, tuple = DatumGetHeapTupleHeader(tupDatum); + /* Lookup tupdescs if first time through or after rescan */ + if (cstate->indesc == NULL) + get_cached_rowtype(exprType((Node *) convert->arg), -1, + &cstate->indesc, econtext); + if (cstate->outdesc == NULL) + get_cached_rowtype(convert->resulttype, -1, + &cstate->outdesc, econtext); + Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid); Assert(HeapTupleHeaderGetTypMod(tuple) == cstate->indesc->tdtypmod); + /* if first time through, initialize */ + if (cstate->attrMap == NULL) + { + MemoryContext old_cxt; + int n; + + /* allocate state in long-lived memory context */ + old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + + /* prepare map from old to new attribute numbers */ + n = cstate->outdesc->natts; + cstate->attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); + for (i = 0; i < n; i++) + { + Form_pg_attribute att = cstate->outdesc->attrs[i]; + char *attname; + Oid atttypid; + int32 atttypmod; + int j; + + if (att->attisdropped) + continue; /* attrMap[i] is already 0 */ + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + for (j = 0; j < cstate->indesc->natts; j++) + { + att = cstate->indesc->attrs[j]; + if (att->attisdropped) + continue; + if (strcmp(attname, NameStr(att->attname)) == 0) + { + /* Found it, check type */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of type %s does not match corresponding attribute of type %s", + attname, + format_type_be(cstate->indesc->tdtypeid), + format_type_be(cstate->outdesc->tdtypeid)); + cstate->attrMap[i] = (AttrNumber) (j + 1); + break; + } + } + if (cstate->attrMap[i] == 0) + elog(ERROR, "attribute \"%s\" of type %s does not exist", + attname, + format_type_be(cstate->indesc->tdtypeid)); + } + /* preallocate workspace for Datum arrays */ + n = cstate->indesc->natts + 1; /* +1 for NULL */ + cstate->invalues = (Datum *) palloc(n * sizeof(Datum)); + cstate->inisnull = (bool *) palloc(n * sizeof(bool)); + n = cstate->outdesc->natts; + cstate->outvalues = (Datum *) palloc(n * sizeof(Datum)); + cstate->outisnull = (bool *) palloc(n * sizeof(bool)); + + MemoryContextSwitchTo(old_cxt); + } + + attrMap = cstate->attrMap; + invalues = cstate->invalues; + inisnull = cstate->inisnull; + outvalues = cstate->outvalues; + outisnull = cstate->outisnull; + outnatts = cstate->outdesc->natts; + /* * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. */ @@ -2135,6 +2825,7 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, int *elem_lbs = NULL; bool firstone = true; bool havenulls = false; + bool haveempty = false; char **subdata; bits8 **subbitmaps; int *subbytes; @@ -2157,11 +2848,15 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, bool eisnull; Datum arraydatum; ArrayType *array; + int this_ndims; arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL); - /* ignore null subarrays */ + /* temporarily ignore null subarrays */ if (eisnull) + { + haveempty = true; continue; + } array = DatumGetArrayTypeP(arraydatum); @@ -2175,10 +2870,18 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, format_type_be(ARR_ELEMTYPE(array)), format_type_be(element_type)))); + this_ndims = ARR_NDIM(array); + /* temporarily ignore zero-dimensional subarrays */ + if (this_ndims <= 0) + { + haveempty = true; + continue; + } + if (firstone) { /* Get sub-array details from first member */ - elem_ndims = ARR_NDIM(array); + elem_ndims = this_ndims; ndims = elem_ndims + 1; if (ndims <= 0 || ndims > MAXDIM) ereport(ERROR, @@ -2196,7 +2899,7 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, else { /* Check other sub-arrays are compatible */ - if (elem_ndims != ARR_NDIM(array) || + if (elem_ndims != this_ndims || memcmp(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)) != 0 || memcmp(elem_lbs, ARR_LBOUND(array), @@ -2211,13 +2914,29 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, subbitmaps[outer_nelems] = ARR_NULLBITMAP(array); subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array); nbytes += subbytes[outer_nelems]; - subnitems[outer_nelems] = ArrayGetNItems(ARR_NDIM(array), + subnitems[outer_nelems] = ArrayGetNItems(this_ndims, ARR_DIMS(array)); nitems += subnitems[outer_nelems]; havenulls |= ARR_HASNULL(array); outer_nelems++; } + /* + * 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...) + */ + if (haveempty) + { + if (ndims == 0) /* didn't find any nonempty array */ + return PointerGetDatum(construct_empty_array(element_type)); + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("multidimensional arrays must have array " + "expressions with matching dimensions"))); + } + /* setup for multi-D array */ dims[0] = outer_nelems; lbs[0] = 1; @@ -2239,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; @@ -2357,7 +3076,7 @@ ExecEvalRowCompare(RowCompareExprState *rstate, switch (rctype) { - /* EQ and NE cases aren't allowed here */ + /* EQ and NE cases aren't allowed here */ case ROWCOMPARE_LT: result = (cmpresult < 0); break; @@ -2466,6 +3185,225 @@ ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext, return result; } +/* ---------------------------------------------------------------- + * ExecEvalXml + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; + Datum value; + bool isnull; + ListCell *arg; + ListCell *narg; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + switch (xexpr->op) + { + case IS_XMLCONCAT: + { + List *values = NIL; + + foreach(arg, xmlExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (!isnull) + values = lappend(values, DatumGetPointer(value)); + } + + if (list_length(values) > 0) + { + *isNull = false; + return PointerGetDatum(xmlconcat(values)); + } + else + return (Datum) 0; + } + break; + + case IS_XMLFOREST: + { + StringInfoData buf; + + initStringInfo(&buf); + forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names) + { + ExprState *e = (ExprState *) lfirst(arg); + char *argname = strVal(lfirst(narg)); + + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (!isnull) + { + appendStringInfo(&buf, "<%s>%s", + argname, + map_sql_value_to_xml_value(value, exprType((Node *) e->expr)), + argname); + *isNull = false; + } + } + + 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; + + case IS_XMLELEMENT: + *isNull = false; + return PointerGetDatum(xmlelement(xmlExpr, econtext)); + break; + + case IS_XMLPARSE: + { + ExprState *e; + text *data; + bool preserve_whitespace; + + /* arguments are known to be text, bool */ + Assert(list_length(xmlExpr->args) == 2); + + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + data = DatumGetTextP(value); + + e = (ExprState *) lsecond(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) /* probably can't happen */ + return (Datum) 0; + preserve_whitespace = DatumGetBool(value); + + *isNull = false; + + return PointerGetDatum(xmlparse(data, + xexpr->xmloption, + preserve_whitespace)); + } + break; + + case IS_XMLPI: + { + ExprState *e; + text *arg; + + /* optional argument is known to be text */ + Assert(list_length(xmlExpr->args) <= 1); + + if (xmlExpr->args) + { + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + arg = NULL; + else + arg = DatumGetTextP(value); + } + else + { + arg = NULL; + isnull = false; + } + + return PointerGetDatum(xmlpi(xexpr->name, arg, isnull, isNull)); + } + break; + + case IS_XMLROOT: + { + ExprState *e; + xmltype *data; + text *version; + int standalone; + + /* arguments are known to be xml, text, int */ + Assert(list_length(xmlExpr->args) == 3); + + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; + data = DatumGetXmlP(value); + + e = (ExprState *) lsecond(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + version = NULL; + else + version = DatumGetTextP(value); + + e = (ExprState *) lthird(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + standalone = DatumGetInt32(value); + + *isNull = false; + + return PointerGetDatum(xmlroot(data, + version, + standalone)); + } + break; + + 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; + + 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; + } + + elog(ERROR, "unrecognized XML operation"); + return (Datum) 0; +} + /* ---------------------------------------------------------------- * ExecEvalNullIf * @@ -2494,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); } @@ -2537,7 +3476,7 @@ ExecEvalNullIf(FuncExprState *nullIfExpr, * ---------------------------------------------------------------- */ static Datum -ExecEvalNullTest(GenericExprState *nstate, +ExecEvalNullTest(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) @@ -2550,28 +3489,77 @@ ExecEvalNullTest(GenericExprState *nstate, if (isDone && *isDone == ExprEndResult) return result; /* nothing to check */ - switch (ntest->nulltesttype) + if (nstate->argisrow && !(*isNull)) { - case IS_NULL: - if (*isNull) + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + int att; + + tuple = DatumGetHeapTupleHeader(result); + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + + /* Lookup tupdesc if first time through or if type changes */ + tupDesc = get_cached_rowtype(tupType, tupTypmod, + &nstate->argdesc, econtext); + + /* + * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + for (att = 1; att <= tupDesc->natts; att++) + { + /* ignore dropped columns */ + if (tupDesc->attrs[att - 1]->attisdropped) + continue; + if (heap_attisnull(&tmptup, att)) { - *isNull = false; - return BoolGetDatum(true); + /* null field disproves IS NOT NULL */ + if (ntest->nulltesttype == IS_NOT_NULL) + return BoolGetDatum(false); } else - return BoolGetDatum(false); - case IS_NOT_NULL: - if (*isNull) { - *isNull = false; - return BoolGetDatum(false); + /* non-null field disproves IS NULL */ + if (ntest->nulltesttype == IS_NULL) + return BoolGetDatum(false); } - else - return BoolGetDatum(true); - default: - elog(ERROR, "unrecognized nulltesttype: %d", - (int) ntest->nulltesttype); - return (Datum) 0; /* keep compiler quiet */ + } + + return BoolGetDatum(true); + } + else + { + /* Simple scalar-argument case, or a null rowtype datum */ + switch (ntest->nulltesttype) + { + case IS_NULL: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else + return BoolGetDatum(false); + case IS_NOT_NULL: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else + return BoolGetDatum(true); + default: + elog(ERROR, "unrecognized nulltesttype: %d", + (int) ntest->nulltesttype); + return (Datum) 0; /* keep compiler quiet */ + } } } @@ -2767,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); @@ -2787,23 +3777,30 @@ ExecEvalFieldSelect(FieldSelectState *fstate, tupTypmod = HeapTupleHeaderGetTypMod(tuple); /* Lookup tupdesc if first time through or if type changes */ - tupDesc = fstate->argdesc; - if (tupDesc == NULL || - tupType != tupDesc->tdtypeid || - tupTypmod != tupDesc->tdtypmod) + 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) { - MemoryContext oldcontext; - - tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - /* Copy the tupdesc into query storage for safety */ - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupDesc = CreateTupleDescCopy(tupDesc); - if (fstate->argdesc) - FreeTupleDesc(fstate->argdesc); - fstate->argdesc = tupDesc; - MemoryContextSwitchTo(oldcontext); + *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 @@ -2815,7 +3812,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate, tmptup.t_data = tuple; result = heap_getattr(&tmptup, - fselect->fieldnum, + fieldnum, tupDesc, isNull); return result; @@ -2849,22 +3846,9 @@ ExecEvalFieldStore(FieldStoreState *fstate, if (isDone && *isDone == ExprEndResult) return tupDatum; - /* Lookup tupdesc if first time through or if type changes */ - tupDesc = fstate->argdesc; - if (tupDesc == NULL || - fstore->resulttype != tupDesc->tdtypeid) - { - MemoryContext oldcontext; - - tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1); - /* Copy the tupdesc into query storage for safety */ - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupDesc = CreateTupleDescCopy(tupDesc); - if (fstate->argdesc) - FreeTupleDesc(fstate->argdesc); - fstate->argdesc = tupDesc; - MemoryContextSwitchTo(oldcontext); - } + /* Lookup tupdesc if first time through or after rescan */ + tupDesc = get_cached_rowtype(fstore->resulttype, -1, + &fstate->argdesc, econtext); /* Allocate workspace */ values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); @@ -2947,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 @@ -2982,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 @@ -3015,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); @@ -3055,10 +4159,11 @@ ExecInitExpr(Expr *node, PlanState *parent) aggstate->aggs = lcons(astate, aggstate->aggs); naggs = ++aggstate->numaggs; - astate->target = ExecInitExpr(aggref->target, parent); + astate->args = (List *) ExecInitExpr((Expr *) aggref->args, + parent); /* - * Complain if the aggregate's argument contains any + * Complain if the aggregate's arguments contain any * aggregates; nested agg functions are semantically * nonsensical. (This should have been caught earlier, * but we defend against it here anyway.) @@ -3066,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; @@ -3175,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; @@ -3233,65 +4378,53 @@ 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; ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState); - int i; - int n; cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype; cstate->arg = ExecInitExpr(convert->arg, parent); - /* save copies of needed tuple descriptors */ - cstate->indesc = lookup_rowtype_tupdesc(exprType((Node *) convert->arg), -1); - cstate->indesc = CreateTupleDescCopy(cstate->indesc); - cstate->outdesc = lookup_rowtype_tupdesc(convert->resulttype, -1); - cstate->outdesc = CreateTupleDescCopy(cstate->outdesc); - /* prepare map from old to new attribute numbers */ - n = cstate->outdesc->natts; - cstate->attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); - for (i = 0; i < n; i++) - { - Form_pg_attribute att = cstate->outdesc->attrs[i]; - char *attname; - Oid atttypid; - int32 atttypmod; - int j; - - if (att->attisdropped) - continue; /* attrMap[i] is already 0 */ - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - for (j = 0; j < cstate->indesc->natts; j++) - { - att = cstate->indesc->attrs[j]; - if (att->attisdropped) - continue; - if (strcmp(attname, NameStr(att->attname)) == 0) - { - /* Found it, check type */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of type %s does not match corresponding attribute of type %s", - attname, - format_type_be(cstate->indesc->tdtypeid), - format_type_be(cstate->outdesc->tdtypeid)); - cstate->attrMap[i] = (AttrNumber) (j + 1); - break; - } - } - if (cstate->attrMap[i] == 0) - elog(ERROR, "attribute \"%s\" of type %s does not exist", - attname, - format_type_be(cstate->indesc->tdtypeid)); - } - /* preallocate workspace for Datum arrays */ - n = cstate->indesc->natts + 1; /* +1 for NULL */ - cstate->invalues = (Datum *) palloc(n * sizeof(Datum)); - cstate->inisnull = (bool *) palloc(n * sizeof(bool)); - n = cstate->outdesc->natts; - cstate->outvalues = (Datum *) palloc(n * sizeof(Datum)); - cstate->outisnull = (bool *) palloc(n * sizeof(bool)); state = (ExprState *) cstate; } break; @@ -3362,12 +4495,12 @@ ExecInitExpr(Expr *node, PlanState *parent) /* generic record, use runtime type assignment */ rstate->tupdesc = ExecTypeFromExprList(rowexpr->args); BlessTupleDesc(rstate->tupdesc); + /* we won't need to redo this at runtime */ } else { /* it's been cast to a named type, use that */ - rstate->tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1); - rstate->tupdesc = CreateTupleDescCopy(rstate->tupdesc); + rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1); } /* Set up evaluation, skipping any deleted columns */ Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts); @@ -3400,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); @@ -3443,21 +4576,27 @@ ExecInitExpr(Expr *node, PlanState *parent) outlist = lappend(outlist, estate); } rstate->rargs = outlist; - Assert(list_length(rcexpr->opclasses) == nopers); + Assert(list_length(rcexpr->opfamilies) == nopers); rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo)); i = 0; - forboth(l, rcexpr->opnos, l2, rcexpr->opclasses) + forboth(l, rcexpr->opnos, l2, rcexpr->opfamilies) { - Oid opno = lfirst_oid(l); - Oid opclass = lfirst_oid(l2); - int strategy; - Oid subtype; - bool recheck; - Oid proc; - - get_op_opclass_properties(opno, opclass, - &strategy, &subtype, &recheck); - proc = get_opclass_proc(opclass, subtype, BTORDER_PROC); + Oid opno = lfirst_oid(l); + Oid opfamily = lfirst_oid(l2); + int strategy; + Oid lefttype; + Oid righttype; + Oid proc; + + get_op_opfamily_properties(opno, opfamily, + &strategy, + &lefttype, + &righttype); + proc = get_opfamily_proc(opfamily, + lefttype, + righttype, + BTORDER_PROC); + /* * If we enforced permissions checks on index support * functions, we'd need to make a check here. But the @@ -3516,16 +4655,50 @@ ExecInitExpr(Expr *node, PlanState *parent) (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("could not identify a comparison function for type %s", format_type_be(minmaxexpr->minmaxtype)))); + /* * If we enforced permissions checks on index support - * functions, we'd need to make a check here. But the - * index support machinery doesn't do that, and neither - * does this code. + * functions, we'd need to make a check here. But the index + * support machinery doesn't do that, and neither does this + * code. */ fmgr_info(typentry->cmp_proc, &(mstate->cfunc)); state = (ExprState *) mstate; } break; + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + XmlExprState *xstate = makeNode(XmlExprState); + List *outlist; + ListCell *arg; + + xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml; + outlist = NIL; + foreach(arg, xexpr->named_args) + { + Expr *e = (Expr *) lfirst(arg); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + xstate->named_args = outlist; + + outlist = NIL; + foreach(arg, xexpr->args) + { + Expr *e = (Expr *) lfirst(arg); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + xstate->args = outlist; + + state = (ExprState *) xstate; + } + break; case T_NullIfExpr: { NullIfExpr *nullifexpr = (NullIfExpr *) node; @@ -3541,11 +4714,13 @@ ExecInitExpr(Expr *node, PlanState *parent) case T_NullTest: { NullTest *ntest = (NullTest *) node; - GenericExprState *gstate = makeNode(GenericExprState); + NullTestState *nstate = makeNode(NullTestState); - gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest; - gstate->arg = ExecInitExpr(ntest->arg, parent); - state = (ExprState *) gstate; + nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest; + nstate->arg = ExecInitExpr(ntest->arg, parent); + nstate->argisrow = type_is_rowtype(exprType((Node *) ntest->arg)); + nstate->argdesc = NULL; + state = (ExprState *) nstate; } break; case T_BooleanTest: @@ -3569,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; @@ -3606,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) @@ -3648,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); @@ -3802,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, @@ -3823,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) @@ -3940,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 * @@ -4001,6 +5109,8 @@ TupleTableSlot * ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) { TupleTableSlot *slot; + ExprContext *econtext; + int numSimpleVars; /* * sanity checks @@ -4011,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 @@ -4020,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); }