X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fexecutor%2FexecQual.c;h=c7bfe7c76ca6b926f2dcbe1eee05b3a0b8b6ecc0;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=1ee41ddb8ce2b972f84a99c11f11346a6aae52e8;hpb=4a39057e590cc5ba7583516ceeef52a242867cad;p=postgresql diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 1ee41ddb8c..c7bfe7c76c 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -3,89 +3,144 @@ * execQual.c * Routines to evaluate qualification and targetlist expressions * - * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.150 2003/10/13 22:47:15 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.247 2009/06/04 18:33:07 tgl Exp $ * *------------------------------------------------------------------------- */ /* * INTERFACE ROUTINES - * ExecEvalExpr - evaluate an expression and return a datum + * ExecEvalExpr - (now a macro) evaluate an expression, return a datum * ExecEvalExprSwitchContext - same, but switch into eval memory context * ExecQual - return true/false if qualification is satisfied * ExecProject - form a new tuple by projecting the given tuple * * NOTES - * ExecEvalExpr() and ExecEvalVar() are hotspots. making these faster - * will speed up the entire system. Unfortunately they are currently - * implemented recursively. Eliminating the recursion is bound to - * improve the speed of the executor. + * The more heavily used ExecEvalExpr routines, such as ExecEvalVar(), + * are hotspots. Making these faster will speed up the entire system. * * ExecProject() is used to make tuple projections. Rather then * trying to speed it up, the execution plan should be pre-processed * to facilitate attribute sharing between nodes wherever possible, * instead of doing needless copying. -cim 5/31/91 * + * During expression evaluation, we check_stack_depth only in + * 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 "optimizer/planmain.h" -#include "parser/parse_expr.h" +#include "nodes/makefuncs.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 */ -static Datum ExecEvalAggref(AggrefExprState *aggref, - ExprContext *econtext, - bool *isNull); static Datum ExecEvalArrayRef(ArrayRefExprState *astate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull); -static Datum ExecEvalParam(Param *expression, ExprContext *econtext, - bool *isNull); +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); static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, - ExprContext *econtext, bool *isNull); -static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, - List *argList, ExprContext *econtext); + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalCaseTestExpr(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, - bool *isNull); + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRow(RowExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalRowCompare(RowCompareExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNullTest(GenericExprState *nstate, + 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(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalBooleanTest(GenericExprState *bstate, @@ -94,11 +149,85 @@ static Datum ExecEvalBooleanTest(GenericExprState *bstate, static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, - ExprContext *econtext, bool *isNull); -static Datum ExecEvalFieldSelect(GenericExprState *fstate, +static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalFieldSelect(FieldSelectState *fstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalFieldStore(FieldStoreState *fstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +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); + + +/* ---------------------------------------------------------------- + * ExecEvalExpr routines + * + * Recursively evaluate a targetlist or qualification expression. + * + * Each of the following routines having the signature + * Datum ExecEvalFoo(ExprState *expression, + * ExprContext *econtext, + * bool *isNull, + * ExprDoneCond *isDone); + * is responsible for evaluating one type or subtype of ExprState node. + * They are normally called via the ExecEvalExpr macro, which makes use of + * the function pointer set up when the ExprState node was built by + * ExecInitExpr. (In some cases, we change this pointer later to avoid + * re-executing one-time overhead.) + * + * Note: for notational simplicity we declare these functions as taking the + * specific type of ExprState that they work on. This requires casting when + * assigning the function pointer in ExecInitExpr. Be careful that the + * function signature is declared correctly, because the cast suppresses + * automatic checking! + * + * + * All these functions share this calling convention: + * + * Inputs: + * expression: the expression state tree to evaluate + * econtext: evaluation context information + * + * Outputs: + * return value: Datum value of result + * *isNull: set to TRUE if result is NULL (actual return value is + * meaningless if so); set to FALSE if non-null result + * *isDone: set to indicator of set-result status + * + * A caller that can only accept a singleton (non-set) result should pass + * NULL for isDone; if the expression computes a set result then an error + * will be reported via ereport. If the caller does pass an isDone pointer + * then *isDone is set to one of these three states: + * ExprSingleResult singleton result (not a set) + * ExprMultipleResult return value is one element of a set + * ExprEndResult there are no more elements in the set + * When ExprMultipleResult is returned, the caller should invoke + * ExecEvalExpr() repeatedly until ExprEndResult is returned. ExprEndResult + * is returned after the last real set element. For convenience isNull will + * always be set TRUE when ExprEndResult is returned, but this should not be + * taken as indicating a NULL element of the set. Note that these return + * conventions allow us to distinguish among a singleton NULL, a NULL element + * of a set, and an empty set. + * + * The caller should already have switched into the temporary memory + * context econtext->ecxt_per_tuple_memory. The convenience entry point + * ExecEvalExprSwitchContext() is provided for callers who don't prefer to + * do the switch in an outer loop. We do not do the switch in these routines + * because it'd be a waste of cycles during nested expression evaluation. + * ---------------------------------------------------------------- + */ /*---------- @@ -108,16 +237,8 @@ static Datum ExecEvalFieldSelect(GenericExprState *fstate, * if it's a simple reference, or the modified array value if it's * an array assignment (i.e., array element or slice insertion). * - * NOTE: if we get a NULL result from a subexpression, we return NULL when - * it's an array reference, or the unmodified source array when it's an - * array assignment. This may seem peculiar, but if we return NULL (as was - * done in versions up through 7.0) then an assignment like - * UPDATE table SET arrayfield[4] = NULL - * will result in setting the whole array to NULL, which is certainly not - * very desirable. By returning the source array we make the assignment - * into a no-op, instead. (Eventually we need to redesign arrays so that - * individual elements can be NULL, but for now, let's try to protect users - * from shooting themselves in the foot.) + * NOTE: if we get a NULL result from a subscript expression, we return NULL + * when it's an array reference, or raise an error when it's an assignment. * * NOTE: we deliberately refrain from applying DatumGetArrayTypeP() here, * even though that might seem natural, because this code needs to support @@ -136,89 +257,83 @@ ExecEvalArrayRef(ArrayRefExprState *astate, ArrayType *array_source; ArrayType *resultArray; bool isAssignment = (arrayRef->refassgnexpr != NULL); - List *elt; + bool eisnull; + ListCell *l; int i = 0, j = 0; IntArray upper, lower; int *lIndex; - if (arrayRef->refexpr != NULL) - { - array_source = (ArrayType *) - DatumGetPointer(ExecEvalExpr(astate->refexpr, - econtext, - isNull, - isDone)); + array_source = (ArrayType *) + DatumGetPointer(ExecEvalExpr(astate->refexpr, + econtext, + isNull, + isDone)); - /* - * If refexpr yields NULL, result is always NULL, for now anyway. - * (This means you cannot assign to an element or slice of an - * array that's NULL; it'll just stay NULL.) - */ - if (*isNull) - return (Datum) NULL; - } - else + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. In the + * assignment case, we'll cons up something below. + */ + if (*isNull) { - /* - * Empty refexpr indicates we are doing an INSERT into an array - * column. For now, we just take the refassgnexpr (which the - * parser will have ensured is an array value) and return it - * as-is, ignoring any subscripts that may have been supplied in - * the INSERT column list. This is a kluge, but it's not real - * clear what the semantics ought to be... - */ - array_source = NULL; + if (isDone && *isDone == ExprEndResult) + return (Datum) NULL; /* end of set result */ + if (!isAssignment) + return (Datum) NULL; } - foreach(elt, astate->refupperindexpr) + foreach(l, astate->refupperindexpr) { + ExprState *eltstate = (ExprState *) lfirst(l); + if (i >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", i, MAXDIM))); - upper.indx[i++] = DatumGetInt32(ExecEvalExpr((ExprState *) lfirst(elt), + upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate, econtext, - isNull, + &eisnull, NULL)); - /* If any index expr yields NULL, result is NULL or source array */ - if (*isNull) + /* If any index expr yields NULL, result is NULL or error */ + if (eisnull) { - if (!isAssignment || array_source == NULL) - return (Datum) NULL; - *isNull = false; - return PointerGetDatum(array_source); + if (isAssignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *isNull = true; + return (Datum) NULL; } } if (astate->reflowerindexpr != NIL) { - foreach(elt, astate->reflowerindexpr) + foreach(l, astate->reflowerindexpr) { + ExprState *eltstate = (ExprState *) lfirst(l); + if (j >= MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", i, MAXDIM))); - lower.indx[j++] = DatumGetInt32(ExecEvalExpr((ExprState *) lfirst(elt), + lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate, econtext, - isNull, + &eisnull, NULL)); - - /* - * If any index expr yields NULL, result is NULL or source - * array - */ - if (*isNull) + /* If any index expr yields NULL, result is NULL or error */ + if (eisnull) { - if (!isAssignment || array_source == NULL) - return (Datum) NULL; - *isNull = false; - return PointerGetDatum(array_source); + if (isAssignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *isNull = true; + return (Datum) NULL; } } /* this can't happen unless parser messed up */ @@ -231,44 +346,62 @@ ExecEvalArrayRef(ArrayRefExprState *astate, if (isAssignment) { - Datum sourceData = ExecEvalExpr(astate->refassgnexpr, - econtext, - isNull, - NULL); + Datum sourceData; + + /* + * Evaluate the value to be assigned into the array. + * + * XXX At some point we'll need to look into making the old value of + * the array element available via CaseTestExpr, as is done by + * ExecEvalFieldStore. This is not needed now but will be needed to + * support arrays of composite types; in an assignment to a field of + * an array member, the parser would generate a FieldStore that + * expects to fetch its input tuple via CaseTestExpr. + */ + sourceData = ExecEvalExpr(astate->refassgnexpr, + econtext, + &eisnull, + NULL); + + /* + * For an assignment to a fixed-length array type, both the original + * array and the value to be assigned into it must be non-NULL, else + * we punt and return the original array. + */ + if (astate->refattrlength > 0) /* fixed-length array? */ + if (eisnull || *isNull) + return PointerGetDatum(array_source); /* - * For now, can't cope with inserting NULL into an array, so make - * it a no-op per discussion above... + * For assignment to varlena arrays, we handle a NULL original array + * by substituting an empty (zero-dimensional) array; insertion of the + * new element will result in a singleton array value. It does not + * matter whether the new element is NULL. */ if (*isNull) { - if (array_source == NULL) - return (Datum) NULL; + array_source = construct_empty_array(arrayRef->refelemtype); *isNull = false; - return PointerGetDatum(array_source); } - if (array_source == NULL) - return sourceData; /* XXX do something else? */ - if (lIndex == NULL) resultArray = array_set(array_source, i, upper.indx, sourceData, + eisnull, astate->refattrlength, astate->refelemlength, astate->refelembyval, - astate->refelemalign, - isNull); + astate->refelemalign); else resultArray = array_set_slice(array_source, i, upper.indx, lower.indx, - (ArrayType *) DatumGetPointer(sourceData), + (ArrayType *) DatumGetPointer(sourceData), + eisnull, astate->refattrlength, astate->refelemlength, astate->refelembyval, - astate->refelemalign, - isNull); + astate->refelemalign); return PointerGetDatum(resultArray); } @@ -286,8 +419,7 @@ ExecEvalArrayRef(ArrayRefExprState *astate, astate->refattrlength, astate->refelemlength, astate->refelembyval, - astate->refelemalign, - isNull); + astate->refelemalign); return PointerGetDatum(resultArray); } } @@ -301,8 +433,12 @@ ExecEvalArrayRef(ArrayRefExprState *astate, * ---------------------------------------------------------------- */ static Datum -ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull) +ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { + if (isDone) + *isDone = ExprSingleResult; + if (econtext->ecxt_aggvalues == NULL) /* safety check */ elog(ERROR, "no aggregates in this expression context"); @@ -310,32 +446,70 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull) 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 -ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) +ExecEvalVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - Datum result; + Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; AttrNumber attnum; - HeapTuple heapTuple; - TupleDesc tuple_type; + + if (isDone) + *isDone = ExprSingleResult; /* - * get the slot 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 + * be treated as ordinary variables (since we no longer have access to the + * original tuple). */ + attnum = variable->varattno; + switch (variable->varno) { case INNER: /* get the tuple from the inner node */ slot = econtext->ecxt_innertuple; + Assert(attnum > 0); break; case OUTER: /* get the tuple from the outer node */ slot = econtext->ecxt_outertuple; + Assert(attnum > 0); break; default: /* get the tuple from the relation being @@ -344,259 +518,482 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) break; } - /* - * extract tuple information from the slot - */ - heapTuple = slot->val; - tuple_type = slot->ttc_tupleDescriptor; - - attnum = variable->varattno; - - /* - * Some checks that are only applied for user attribute numbers - * (bogus system attnums will be caught inside heap_getattr). - */ - if (attnum > 0) + if (attnum != InvalidAttrNumber) { /* - * This assert checks that the attnum is valid. - */ - Assert(attnum <= tuple_type->natts && - tuple_type->attrs[attnum - 1] != NULL); - - /* - * If the attribute's column has been dropped, we force a NULL result. - * This case should not happen in normal use, but it could happen if - * we are executing a plan cached before the column was dropped. + * Scalar variable case. + * + * If it's a user attribute, check validity (bogus system attnums will + * be caught inside slot_getattr). What we have to check for here is + * the possibility of an attribute having been changed in type since + * the plan tree was created. Ideally the plan would get invalidated + * and not re-used, but until that day arrives, we need defenses. + * Fortunately it's sufficient to check once on the first time + * through. + * + * Note: we allow a reference to a dropped attribute. slot_getattr + * will force a NULL result in such cases. + * + * Note: ideally we'd check typmod as well as typid, but that seems + * impractical at the moment: in many cases the tupdesc will have been + * generated by ExecTypeFromTL(), and that can't guarantee to generate + * an accurate typmod in all cases, because some expression node types + * don't carry typmod. */ - if (tuple_type->attrs[attnum - 1]->attisdropped) + if (attnum > 0) { - *isNull = true; - return (Datum) 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); - } + TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; + bool needslow = false; - /* - * If the attribute number is invalid, then we are supposed to return - * the entire tuple; we give back a whole slot so that callers know - * what the tuple looks like. - * - * XXX this is a horrid crock: since the pointer to the slot might live - * longer than the current evaluation context, we are forced to copy - * the tuple and slot into a long-lived context --- we use the - * econtext's per-query memory which should be safe enough. This - * represents a serious memory leak if many such tuples are processed - * in one command, however. We ought to redesign the representation - * of whole-tuple datums so that this is not necessary. - * - * We assume it's OK to point to the existing tupleDescriptor, rather - * than copy that too. - */ - if (attnum == InvalidAttrNumber) - { - MemoryContext oldContext; - TupleTableSlot *tempSlot; - HeapTuple tup; + 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; - oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tempSlot = MakeTupleTableSlot(); - tup = heap_copytuple(heapTuple); - ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); - ExecSetSlotDescriptor(tempSlot, tuple_type, false); - MemoryContextSwitchTo(oldContext); - return PointerGetDatum(tempSlot); - } + /* + * 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))); + } - result = heap_getattr(heapTuple, /* tuple containing attribute */ - attnum, /* attribute number of desired - * attribute */ - tuple_type, /* tuple descriptor of tuple */ - isNull); /* return: is attribute null? */ + ReleaseTupleDesc(var_tupdesc); + } - return result; + /* 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); + } } /* ---------------------------------------------------------------- - * ExecEvalParam - * - * Returns the value of a parameter. A param node contains - * 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"). + * ExecEvalScalarVar * - * 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 + * Returns a Datum for a scalar variable. * ---------------------------------------------------------------- */ static Datum -ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) +ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - int thisParamKind = expression->paramkind; - AttrNumber thisParamId = expression->paramid; + Var *variable = (Var *) exprstate->expr; + TupleTableSlot *slot; + AttrNumber attnum; - if (thisParamKind == PARAM_EXEC) - { - /* - * PARAM_EXEC params (internal executor parameters) are stored in - * the ecxt_param_exec_vals array, and can be accessed by array - * index. - */ - ParamExecData *prm; + if (isDone) + *isDone = ExprSingleResult; - prm = &(econtext->ecxt_param_exec_vals[thisParamId]); - if (prm->execPlan != NULL) - { - /* Parameter not evaluated yet, so go do it */ - ExecSetParamPlan(prm->execPlan, econtext); - /* ExecSetParamPlan should have processed this param... */ - Assert(prm->execPlan == NULL); - } - *isNull = prm->isnull; - return prm->value; - } - else + /* Get the input slot and attribute number we want */ + switch (variable->varno) { - /* - * All other parameter types must be sought in - * ecxt_param_list_info. NOTE: The last entry in the param array - * is always an entry with kind == PARAM_INVALID. - */ - ParamListInfo paramList = econtext->ecxt_param_list_info; - char *thisParamName = expression->paramname; - bool matchFound = false; - - if (paramList != NULL) - { - while (paramList->kind != PARAM_INVALID && !matchFound) - { - if (thisParamKind == paramList->kind) - { - switch (thisParamKind) - { - case PARAM_NAMED: - if (strcmp(paramList->name, thisParamName) == 0) - matchFound = true; - break; - case PARAM_NUM: - if (paramList->id == thisParamId) - matchFound = true; - break; - default: - elog(ERROR, "unrecognized paramkind: %d", - thisParamKind); - } - } - if (!matchFound) - paramList++; - } /* while */ - } /* if */ + case INNER: /* get the tuple from the inner node */ + slot = econtext->ecxt_innertuple; + break; - if (!matchFound) - { - if (thisParamKind == PARAM_NAMED) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("no value found for parameter \"%s\"", - thisParamName))); - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("no value found for parameter %d", - thisParamId))); - } + case OUTER: /* get the tuple from the outer node */ + slot = econtext->ecxt_outertuple; + break; - *isNull = paramList->isnull; - return paramList->value; + 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); +} /* ---------------------------------------------------------------- - * ExecEvalOper / ExecEvalFunc support routines - * ---------------------------------------------------------------- - */ - -/* - * GetAttributeByName - * GetAttributeByNum + * ExecEvalWholeRowVar * - * These are functions which return the value of the - * named attribute out of the tuple from the arg slot. User defined - * C functions which take a tuple as an argument are expected - * to use this. Ex: overpaid(EMP) might call GetAttributeByNum(). + * Returns a Datum for a whole-row variable. + * ---------------------------------------------------------------- */ -Datum -GetAttributeByNum(TupleTableSlot *slot, - AttrNumber attrno, - bool *isNull) +static Datum +ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - Datum retval; + Var *variable = (Var *) exprstate->expr; + TupleTableSlot *slot = econtext->ecxt_scantuple; + HeapTuple tuple; + TupleDesc tupleDesc; + HeapTupleHeader dtuple; - if (!AttributeNumberIsValid(attrno)) + if (isDone) + *isDone = ExprSingleResult; + *isNull = false; + + tuple = ExecFetchSlotTuple(slot); + tupleDesc = slot->tts_tupleDescriptor; + + /* + * 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. + */ + dtuple = (HeapTupleHeader) palloc(tuple->t_len); + memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len); + + HeapTupleHeaderSetDatumLength(dtuple, tuple->t_len); + + /* + * If the Var identifies a named composite type, label the tuple with that + * type; otherwise use what is in the tupleDesc. + */ + if (variable->vartype != RECORDOID) + { + HeapTupleHeaderSetTypeId(dtuple, variable->vartype); + HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); + } + else + { + HeapTupleHeaderSetTypeId(dtuple, tupleDesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtuple, tupleDesc->tdtypmod); + } + + 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 + * + * Returns the value of a constant. + * + * Note that for pass-by-ref datatypes, we return a pointer to the + * actual constant node. This is one of the reasons why functions + * must treat their input arguments as read-only. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalConst(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Const *con = (Const *) exprstate->expr; + + if (isDone) + *isDone = ExprSingleResult; + + *isNull = con->constisnull; + return con->constvalue; +} + +/* ---------------------------------------------------------------- + * ExecEvalParam + * + * Returns the value of a parameter. A param node contains + * 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"). + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalParam(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Param *expression = (Param *) exprstate->expr; + int thisParamId = expression->paramid; + + if (isDone) + *isDone = ExprSingleResult; + + if (expression->paramkind == PARAM_EXEC) + { + /* + * PARAM_EXEC params (internal executor parameters) are stored in the + * ecxt_param_exec_vals array, and can be accessed by array index. + */ + ParamExecData *prm; + + prm = &(econtext->ecxt_param_exec_vals[thisParamId]); + if (prm->execPlan != NULL) + { + /* Parameter not evaluated yet, so go do it */ + ExecSetParamPlan(prm->execPlan, econtext); + /* ExecSetParamPlan should have processed this param... */ + Assert(prm->execPlan == NULL); + } + *isNull = prm->isnull; + return prm->value; + } + else + { + /* + * PARAM_EXTERN parameters must be sought in ecxt_param_list_info. + */ + 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]; + + 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 */ + } +} + + +/* ---------------------------------------------------------------- + * ExecEvalOper / ExecEvalFunc support routines + * ---------------------------------------------------------------- + */ + +/* + * GetAttributeByName + * GetAttributeByNum + * + * These functions return the value of the requested attribute + * out of the given tuple Datum. + * C functions which take a tuple as an argument are expected + * to use these. Ex: overpaid(EMP) might call GetAttributeByNum(). + * Note: these are actually rather slow because they do a typcache + * lookup on each call. + */ +Datum +GetAttributeByNum(HeapTupleHeader tuple, + AttrNumber attrno, + bool *isNull) +{ + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + + if (!AttributeNumberIsValid(attrno)) elog(ERROR, "invalid attribute number %d", attrno); - if (isNull == (bool *) NULL) + if (isNull == NULL) elog(ERROR, "a NULL isNull pointer was passed"); - if (TupIsNull(slot)) + if (tuple == NULL) { + /* Kinda bogus but compatible with old behavior... */ *isNull = true; return (Datum) 0; } - retval = heap_getattr(slot->val, + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* + * 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 + * columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, attrno, - slot->ttc_tupleDescriptor, + tupDesc, isNull); - if (*isNull) - return (Datum) 0; - return retval; + ReleaseTupleDesc(tupDesc); + + return result; } Datum -GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) +GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) { AttrNumber attrno; - TupleDesc tupdesc; - Datum retval; - int natts; + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; int i; if (attname == NULL) elog(ERROR, "invalid attribute name"); - if (isNull == (bool *) NULL) + if (isNull == NULL) elog(ERROR, "a NULL isNull pointer was passed"); - if (TupIsNull(slot)) + if (tuple == NULL) { + /* Kinda bogus but compatible with old behavior... */ *isNull = true; return (Datum) 0; } - tupdesc = slot->ttc_tupleDescriptor; - natts = slot->val->t_data->t_natts; + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); attrno = InvalidAttrNumber; - for (i = 0; i < tupdesc->natts; i++) + for (i = 0; i < tupDesc->natts; i++) { - if (namestrcmp(&(tupdesc->attrs[i]->attname), attname) == 0) + if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0) { - attrno = tupdesc->attrs[i]->attnum; + attrno = tupDesc->attrs[i]->attnum; break; } } @@ -604,21 +1001,32 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) if (attrno == InvalidAttrNumber) elog(ERROR, "attribute \"%s\" does not exist", attname); - retval = heap_getattr(slot->val, + /* + * 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 + * columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, attrno, - tupdesc, + tupDesc, isNull); - if (*isNull) - return (Datum) 0; - return retval; + 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; @@ -627,16 +1035,156 @@ init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid)); - /* Safety check (should never fail, as parser should check sooner) */ - if (length(fcache->args) > FUNC_MAX_ARGS) - elog(ERROR, "too many arguments"); + /* + * Safety check on nargs. Under normal circumstances this should never + * fail, as parser should check sooner. But possibly it might fail if + * server has been compiled with FUNC_MAX_ARGS smaller than some functions + * declared in pg_proc? + */ + if (list_length(fcache->args) > FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + 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->func.fn_expr = (Node *) fcache->xprstate.expr; + fcache->shutdown_reg = false; +} + +/* + * callback function in case a FuncExpr returning a set needs to be shut down + * before it has been run to completion + */ +static void +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; + + /* execUtils will deregister the callback... */ + 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; } /* @@ -649,16 +1197,17 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, { ExprDoneCond argIsDone; int i; - List *arg; + ListCell *arg; argIsDone = ExprSingleResult; /* default assumption */ i = 0; foreach(arg, argList) { + ExprState *argstate = (ExprState *) lfirst(arg); ExprDoneCond thisArgIsDone; - fcinfo->arg[i] = ExecEvalExpr((ExprState *) lfirst(arg), + fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], &thisArgIsDone); @@ -666,10 +1215,9 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, if (thisArgIsDone != ExprSingleResult) { /* - * We allow only one argument to have a set value; we'd need - * much more complexity to keep track of multiple set - * arguments (cf. ExecTargetList) and it doesn't seem worth - * it. + * We allow only one argument to have a set value; we'd need much + * more complexity to keep track of multiple set arguments (cf. + * ExecTargetList) and it doesn't seem worth it. */ if (argIsDone != ExprSingleResult) ereport(ERROR, @@ -685,38 +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(); + /* - * 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. + * 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 */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); - 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. */ @@ -733,39 +1458,20 @@ 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) { /* - * We need to return a set result. Complain if caller not ready - * to accept one. + * We need to return a set result. Complain if caller not ready to + * accept one. */ if (isDone == NULL) ereport(ERROR, @@ -773,26 +1479,43 @@ ExecMakeFunctionResult(FuncExprState *fcache, errmsg("set-valued function called in context that cannot accept a set"))); /* - * This loop handles the situation where we have both a set - * argument and a set-valued function. Once we have exhausted the - * function's value(s) for a particular argument value, we have to - * get the next argument value and start the function over again. - * We might have to do it more than once, if the function produces - * an empty result set for a particular input value. + * 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 + * value(s) for a particular argument value, we have to get the next + * argument value and start the function over again. We might have to + * do it more than once, if the function produces an empty result set + * for a particular input value. */ for (;;) { /* - * If function is strict, and there are any NULL arguments, - * skip calling the function (at least for this set of args). + * If function is strict, and there are any NULL arguments, skip + * calling the function (at least for this set of args). */ bool callit = true; 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; @@ -802,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 { @@ -815,34 +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) + if (*isDone != ExprEndResult) + { + /* + * 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) + { + 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; + } + } + 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) { - memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo)); + /* prepare to return values from the tuplestore */ + ExecPrepareTuplestoreResult(fcache, econtext, + rsinfo.setResult, + rsinfo.setDesc); + /* remember whether we had set arguments */ fcache->setHasSetArg = hasSetArg; - fcache->setArgsValid = true; + /* loop back to top to start returning from tuplestore */ + goto restart; } - - /* - * Make sure we say we are returning a set, even if the - * function itself doesn't return sets. - */ - *isDone = ExprMultipleResult; - break; + /* 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) { @@ -854,8 +1624,8 @@ ExecMakeFunctionResult(FuncExprState *fcache, } /* - * If we reach here, loop around to run the function on the - * new argument. + * If we reach here, loop around to run the function on the new + * argument. */ } } @@ -864,25 +1634,107 @@ ExecMakeFunctionResult(FuncExprState *fcache, /* * Non-set case: much easier. * + * We change the ExprState function pointer to use the simpler + * ExecMakeFunctionResultNoSets on subsequent calls. This amounts to + * assuming that no argument can return a set if it didn't do so the + * first time. + */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; + + if (isDone) + *isDone = ExprSingleResult; + + /* * If function is strict, and there are any NULL arguments, skip * calling the function and return NULL. */ 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; +} + +/* + * ExecMakeFunctionResultNoSets + * + * Simplified version of ExecMakeFunctionResult that can only handle + * non-set cases. Hand-tuned for speed. + */ +static Datum +ExecMakeFunctionResultNoSets(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + ListCell *arg; + Datum result; + FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; + int i; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + if (isDone) + *isDone = ExprSingleResult; + + /* inlined, simplified version of ExecEvalFuncArgs */ + i = 0; + foreach(arg, fcache->args) + { + ExprState *argstate = (ExprState *) lfirst(arg); + + fcinfo.arg[i] = ExecEvalExpr(argstate, + econtext, + &fcinfo.argnull[i], + NULL); + i++; + } + + InitFunctionCallInfoData(fcinfo, &(fcache->func), i, NULL, NULL); + + /* + * If function is strict, and there are any NULL arguments, skip calling + * the function and return NULL. + */ + if (fcache->func.fn_strict) + { + while (--i >= 0) + { + if (fcinfo.argnull[i]) + { + *isNull = true; + return (Datum) 0; + } + } } + 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; } @@ -891,35 +1743,62 @@ ExecMakeFunctionResult(FuncExprState *fcache, * ExecMakeTableFunctionResult * * Evaluate a table function, producing a materialized result in a Tuplestore - * object. (If function returns an empty set, we just return NULL instead.) + * object. */ Tuplestorestate * ExecMakeTableFunctionResult(ExprState *funcexpr, ExprContext *econtext, TupleDesc expectedDesc, - TupleDesc *returnDesc) + bool randomAccess) { Tuplestorestate *tupstore = NULL; TupleDesc tupdesc = NULL; Oid funcrettype; + bool returnsTuple; + bool returnsSet = false; FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; + HeapTupleData tmptup; MemoryContext callerContext; MemoryContext oldcontext; - TupleTableSlot *slot; bool direct_function_call; bool first_time = true; - bool returnsTuple = false; + + callerContext = CurrentMemoryContext; + + funcrettype = exprType((Node *) funcexpr->expr); + + returnsTuple = type_is_rowtype(funcrettype); + + /* + * Prepare a resultinfo node for communication. We always do this even if + * not expecting a set result, so that we can pass expectedDesc. In the + * generic-expression case, the expression doesn't actually get to see the + * resultinfo, but set it up anyway because we use some of the fields as + * our own state variables. + */ + InitFunctionCallInfoData(fcinfo, NULL, 0, NULL, (Node *) &rsinfo); + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = expectedDesc; + 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; + rsinfo.setDesc = NULL; /* - * Normally the passed expression tree will be a FuncExprState, since - * the grammar only allows a function call at the top level of a table - * function reference. However, if the function doesn't return set - * then the planner might have replaced the function call via - * constant-folding or inlining. So if we see any other kind of - * expression node, execute it via the general ExecEvalExpr() code; - * the only difference is that we don't get a chance to pass a special - * ReturnSetInfo to any functions buried in the expression. + * Normally the passed expression tree will be a FuncExprState, since the + * grammar only allows a function call at the top level of a table + * function reference. However, if the function doesn't return set then + * the planner might have replaced the function call via constant-folding + * or inlining. So if we see any other kind of expression node, execute + * it via the general ExecEvalExpr() code; the only difference is that we + * don't get a chance to pass a special ReturnSetInfo to any functions + * buried in the expression. */ if (funcexpr && IsA(funcexpr, FuncExprState) && IsA(funcexpr->expr, FuncExpr)) @@ -939,18 +1818,19 @@ 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; /* * Evaluate the function's argument list. * * Note: ideally, we'd do this in the per-tuple context, but then the - * argument values would disappear when we reset the context in - * the inner loop. So do it in caller context. Perhaps we should - * make a separate context just to hold the evaluated arguments? + * argument values would disappear when we reset the context in the + * inner loop. So do it in caller context. Perhaps we should make a + * separate context just to hold the evaluated arguments? */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); fcinfo.flinfo = &(fcache->func); argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext); /* We don't allow sets in the arguments of the table function */ @@ -961,7 +1841,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, /* * If function is strict, and there are any NULL arguments, skip - * calling the function and return NULL (actually an empty set). + * calling the function and act like it returned NULL (or an empty + * set, in the returns-set case). */ if (fcache->func.fn_strict) { @@ -970,10 +1851,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, for (i = 0; i < fcinfo.nargs; i++) { if (fcinfo.argnull[i]) - { - *returnDesc = NULL; - return NULL; - } + goto no_function_result; } } } @@ -983,30 +1861,10 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, direct_function_call = false; } - funcrettype = exprType((Node *) funcexpr->expr); - - /* - * Prepare a resultinfo node for communication. We always do this - * even if not expecting a set result, so that we can pass - * expectedDesc. In the generic-expression case, the expression - * doesn't actually get to see the resultinfo, but set it up anyway - * because we use some of the fields as our own state variables. - */ - fcinfo.resultinfo = (Node *) &rsinfo; - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = expectedDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - /* - * Switch to short-lived context for calling the function or - * expression. + * Switch to short-lived context for calling the function or expression. */ - callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* * Loop to handle the ValuePerCall protocol (which is also the same @@ -1015,21 +1873,27 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, for (;;) { Datum result; - HeapTuple tuple; + + CHECK_FOR_INTERRUPTS(); /* - * reset per-tuple memory context before each call of the function - * or expression. This cleans up any local memory the function may - * leak when called. + * reset per-tuple memory context before each call of the function or + * expression. This cleans up any local memory the function may leak + * when called. */ ResetExprContext(econtext); /* 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 { @@ -1042,40 +1906,44 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, { /* * Check for end of result set. - * - * Note: if function returns an empty set, we don't build a - * tupdesc or tuplestore (since we can't get a tupdesc in the - * function-returning-tuple case) */ if (rsinfo.isDone == ExprEndResult) break; /* - * If first time through, build tupdesc and tuplestore for - * result + * Can't do anything very useful with NULL rowtype values. For a + * function returning set, we consider this a protocol violation + * (but another alternative would be to just ignore the result and + * "continue" to get another row). For a function not returning + * set, we fall out of the loop; we'll cons up an all-nulls result + * row below. + */ + if (returnsTuple && fcinfo.isnull) + { + if (!returnsSet) + break; + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("function returning set of rows cannot return null value"))); + } + + /* + * If first time through, build tupdesc and tuplestore for result */ if (first_time) { oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - if (funcrettype == RECORDOID || - get_typtype(funcrettype) == 'c') + if (returnsTuple) { /* - * Composite type, so function should have returned a - * TupleTableSlot; use its descriptor + * Use the type info embedded in the rowtype Datum to look + * up the needed tupdesc. Make a copy for the query. */ - slot = (TupleTableSlot *) DatumGetPointer(result); - if (fcinfo.isnull || !slot) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("function returning row cannot return null value"))); - if (!IsA(slot, TupleTableSlot) || - !slot->ttc_tupleDescriptor) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function returning row did not return a valid tuple slot"))); - tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor); - returnsTuple = true; + HeapTupleHeader td; + + td = DatumGetHeapTupleHeader(result); + tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), + HeapTupleHeaderGetTypMod(td)); } else { @@ -1088,10 +1956,9 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, "column", funcrettype, -1, - 0, - false); + 0); } - tupstore = tuplestore_begin_heap(true, false, SortMem); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); MemoryContextSwitchTo(oldcontext); rsinfo.setResult = tupstore; rsinfo.setDesc = tupdesc; @@ -1102,26 +1969,25 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, */ if (returnsTuple) { - slot = (TupleTableSlot *) DatumGetPointer(result); - if (fcinfo.isnull || - !slot || - !IsA(slot, TupleTableSlot) || - TupIsNull(slot)) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("function returning row cannot return null value"))); - tuple = slot->val; + HeapTupleHeader td; + + td = DatumGetHeapTupleHeader(result); + + /* + * tuplestore_puttuple needs a HeapTuple not a bare + * HeapTupleHeader, but it doesn't need all the fields. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_puttuple(tupstore, &tmptup); } else { - char nullflag; - - nullflag = fcinfo.isnull ? 'n' : ' '; - tuple = heap_formtuple(tupdesc, &result, &nullflag); + 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); /* @@ -1149,10 +2015,54 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, first_time = false; } +no_function_result: + + /* + * If we got nothing from the function (ie, an empty-set or NULL result), + * we have to create the tuplestore to return, and if it's a + * non-set-returning function then insert a single all-nulls row. + */ + if (rsinfo.setResult == NULL) + { + MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + rsinfo.setResult = tupstore; + if (!returnsSet) + { + int natts = expectedDesc->natts; + Datum *nulldatums; + bool *nullflags; + + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + nulldatums = (Datum *) palloc0(natts * sizeof(Datum)); + nullflags = (bool *) palloc(natts * sizeof(bool)); + memset(nullflags, true, natts * sizeof(bool)); + MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + 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; } @@ -1176,15 +2086,14 @@ ExecEvalFunc(FuncExprState *fcache, bool *isNull, ExprDoneCond *isDone) { - /* - * Initialize function cache if first time through - */ - if (fcache->func.fn_oid == InvalidOid) - { - FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; + /* This is called only the first time through */ + FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); - } + /* Initialize function lookup info */ + init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory, true); + + /* Go directly to ExecMakeFunctionResult on subsequent uses */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); } @@ -1199,15 +2108,14 @@ ExecEvalOper(FuncExprState *fcache, bool *isNull, ExprDoneCond *isDone) { - /* - * Initialize function cache if first time through - */ - if (fcache->func.fn_oid == InvalidOid) - { - OpExpr *op = (OpExpr *) fcache->xprstate.expr; + /* This is called only the first time through */ + OpExpr *op = (OpExpr *) fcache->xprstate.expr; - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); - } + /* Initialize function lookup info */ + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory, true); + + /* Go directly to ExecMakeFunctionResult on subsequent uses */ + fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); } @@ -1226,13 +2134,19 @@ ExecEvalOper(FuncExprState *fcache, static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull) + bool *isNull, + ExprDoneCond *isDone) { Datum result; FunctionCallInfoData fcinfo; ExprDoneCond argDone; List *argList; + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + /* * Initialize function cache if first time through */ @@ -1240,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); } @@ -1250,13 +2165,12 @@ ExecEvalDistinct(FuncExprState *fcache, argList = fcache->args; /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); + InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); if (argDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("IS DISTINCT FROM does not support set arguments"))); + errmsg("IS DISTINCT FROM does not support set arguments"))); Assert(fcinfo.nargs == 2); if (fcinfo.argnull[0] && fcinfo.argnull[1]) @@ -1291,7 +2205,8 @@ ExecEvalDistinct(FuncExprState *fcache, */ static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, - ExprContext *econtext, bool *isNull) + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr; bool useOr = opexpr->useOr; @@ -1306,6 +2221,13 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, bool typbyval; char typalign; char *s; + bits8 *bitmap; + int bitmask; + + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; /* * Initialize function cache if first time through @@ -1313,23 +2235,22 @@ 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); } /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(sstate->fxprstate.func); + InitFunctionCallInfoData(fcinfo, &(sstate->fxprstate.func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, sstate->fxprstate.args, econtext); if (argDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("op ANY/ALL (array) does not support set arguments"))); + errmsg("op ANY/ALL (array) does not support set arguments"))); Assert(fcinfo.nargs == 2); /* - * If the array is NULL then we return NULL --- it's not very - * meaningful to do anything else, even if the operator isn't strict. + * If the array is NULL then we return NULL --- it's not very meaningful + * to do anything else, even if the operator isn't strict. */ if (fcinfo.argnull[1]) { @@ -1342,18 +2263,16 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, /* * If the array is empty, we return either FALSE or TRUE per the useOr * flag. This is correct even if the scalar is NULL; since we would - * evaluate the operator zero times, it matters not whether it would - * want to return NULL. + * evaluate the operator zero times, it matters not whether it would want + * to return NULL. */ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); if (nitems <= 0) return BoolGetDatum(!useOr); /* - * If the scalar is NULL, and the function is strict, return NULL. - * This is just to avoid having to test for strictness inside the - * loop. (XXX but if arrays could have null elements, we'd need a - * test anyway.) + * If the scalar is NULL, and the function is strict, return NULL; no + * point in iterating the loop. */ if (fcinfo.argnull[0] && sstate->fxprstate.func.fn_strict) { @@ -1362,9 +2281,8 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, } /* - * We arrange to look up info about the element type only once per - * series of calls, assuming the element type doesn't change - * underneath us. + * We arrange to look up info about the element type only once per series + * of calls, assuming the element type doesn't change underneath us. */ if (sstate->element_type != ARR_ELEMTYPE(arr)) { @@ -1383,22 +2301,40 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, /* Loop over the array elements */ s = (char *) ARR_DATA_PTR(arr); + bitmap = ARR_NULLBITMAP(arr); + bitmask = 1; + for (i = 0; i < nitems; i++) { Datum elt; Datum thisresult; - /* Get array element */ - elt = fetch_att(s, typbyval, typlen); - - s = att_addlength(s, typlen, PointerGetDatum(s)); - s = (char *) att_align(s, typalign); + /* Get array element, checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + fcinfo.arg[1] = (Datum) 0; + fcinfo.argnull[1] = true; + } + else + { + elt = fetch_att(s, typbyval, typlen); + s = att_addlength_pointer(s, typlen, s); + s = (char *) att_align_nominal(s, typalign); + fcinfo.arg[1] = elt; + fcinfo.argnull[1] = false; + } /* Call comparison function */ - fcinfo.arg[1] = elt; - fcinfo.argnull[1] = false; - fcinfo.isnull = false; - thisresult = FunctionCallInvoke(&fcinfo); + if (fcinfo.argnull[1] && sstate->fxprstate.func.fn_strict) + { + fcinfo.isnull = true; + thisresult = (Datum) 0; + } + else + { + fcinfo.isnull = false; + thisresult = FunctionCallInvoke(&fcinfo); + } /* Combine results per OR or AND semantics */ if (fcinfo.isnull) @@ -1421,6 +2357,17 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, break; /* needn't look at any more elements */ } } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } } *isNull = resultnull; @@ -1443,25 +2390,27 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, * ---------------------------------------------------------------- */ static Datum -ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, bool *isNull) +ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - ExprState *clause; + ExprState *clause = linitial(notclause->args); Datum expr_value; - clause = lfirst(notclause->args); + if (isDone) + *isDone = ExprSingleResult; expr_value = ExecEvalExpr(clause, econtext, isNull, NULL); /* - * if the expression evaluates to null, then we just cascade the null - * back to whoever called us. + * if the expression evaluates to null, then we just cascade the null back + * to whoever called us. */ if (*isNull) return expr_value; /* - * evaluation of 'not' is simple.. expr is false, then return 'true' - * and vice versa. + * evaluation of 'not' is simple.. expr is false, then return 'true' and + * vice versa. */ return BoolGetDatum(!DatumGetBool(expr_value)); } @@ -1471,34 +2420,37 @@ ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, bool *isNull) +ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - List *clauses; - List *clause; + List *clauses = orExpr->args; + ListCell *clause; bool AnyNull; - Datum clause_value; - clauses = orExpr->args; + if (isDone) + *isDone = ExprSingleResult; + AnyNull = false; /* - * If any of the clauses is TRUE, the OR result is TRUE regardless of - * the states of the rest of the clauses, so we can stop evaluating - * and return TRUE immediately. If none are TRUE and one or more is - * NULL, we return NULL; otherwise we return FALSE. This makes sense - * when you interpret NULL as "don't know": if we have a TRUE then the - * OR is TRUE even if we aren't sure about some of the other inputs. - * If all the known inputs are FALSE, but we have one or more "don't - * knows", then we have to report that we "don't know" what the OR's - * result should be --- perhaps one of the "don't knows" would have - * been TRUE if we'd known its value. Only when all the inputs are - * known to be FALSE can we state confidently that the OR's result is - * FALSE. + * If any of the clauses is TRUE, the OR result is TRUE regardless of the + * states of the rest of the clauses, so we can stop evaluating and return + * TRUE immediately. If none are TRUE and one or more is NULL, we return + * NULL; otherwise we return FALSE. This makes sense when you interpret + * NULL as "don't know": if we have a TRUE then the OR is TRUE even if we + * aren't sure about some of the other inputs. If all the known inputs are + * FALSE, but we have one or more "don't knows", then we have to report + * that we "don't know" what the OR's result should be --- perhaps one of + * the "don't knows" would have been TRUE if we'd known its value. Only + * when all the inputs are known to be FALSE can we state confidently that + * the OR's result is FALSE. */ foreach(clause, clauses) { - clause_value = ExecEvalExpr((ExprState *) lfirst(clause), - econtext, isNull, NULL); + ExprState *clausestate = (ExprState *) lfirst(clause); + Datum clause_value; + + clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL); /* * if we have a non-null true result, then return it. @@ -1519,28 +2471,33 @@ ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull) +ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - List *clauses; - List *clause; + List *clauses = andExpr->args; + ListCell *clause; bool AnyNull; - Datum clause_value; - clauses = andExpr->args; + if (isDone) + *isDone = ExprSingleResult; + AnyNull = false; /* - * If any of the clauses is FALSE, the AND result is FALSE regardless - * of the states of the rest of the clauses, so we can stop evaluating - * and return FALSE immediately. If none are FALSE and one or more is - * NULL, we return NULL; otherwise we return TRUE. This makes sense - * when you interpret NULL as "don't know", using the same sort of - * reasoning as for OR, above. + * If any of the clauses is FALSE, the AND result is FALSE regardless of + * the states of the rest of the clauses, so we can stop evaluating and + * return FALSE immediately. If none are FALSE and one or more is NULL, + * we return NULL; otherwise we return TRUE. This makes sense when you + * interpret NULL as "don't know", using the same sort of reasoning as for + * OR, above. */ + foreach(clause, clauses) { - clause_value = ExecEvalExpr((ExprState *) lfirst(clause), - econtext, isNull, NULL); + ExprState *clausestate = (ExprState *) lfirst(clause); + Datum clause_value; + + clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL); /* * if we have a non-null false result, then return it. @@ -1556,6 +2513,148 @@ ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull) return BoolGetDatum(!AnyNull); } +/* ---------------------------------------------------------------- + * ExecEvalConvertRowtype + * + * Evaluate a rowtype coercion operation. This may require + * rearranging field positions. + * ---------------------------------------------------------------- + */ +static Datum +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; + Datum *invalues; + bool *inisnull; + Datum *outvalues; + bool *outisnull; + int i; + int outnatts; + + tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); + + /* this test covers the isDone exception too: */ + if (*isNull) + return tupDatum; + + 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. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + /* + * Extract all the values of the old tuple, offsetting the arrays so that + * invalues[0] is NULL and invalues[1] is the first source attribute; this + * exactly matches the numbering convention in attrMap. + */ + heap_deform_tuple(&tmptup, cstate->indesc, invalues + 1, inisnull + 1); + invalues[0] = (Datum) 0; + inisnull[0] = true; + + /* + * Transpose into proper fields of the new tuple. + */ + for (i = 0; i < outnatts; i++) + { + int j = attrMap[i]; + + outvalues[i] = invalues[j]; + outisnull[i] = inisnull[j]; + } + + /* + * Now form the new tuple. + */ + result = heap_form_tuple(cstate->outdesc, outvalues, outisnull); + + return HeapTupleGetDatum(result); +} /* ---------------------------------------------------------------- * ExecEvalCase @@ -1570,20 +2669,40 @@ static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - List *clauses; - List *clause; - Datum clause_value; + List *clauses = caseExpr->args; + ListCell *clause; + Datum save_datum; + bool save_isNull; + + if (isDone) + *isDone = ExprSingleResult; + + /* + * If there's a test expression, we have to evaluate it and save the value + * where the CaseTestExpr placeholders can find it. We must save and + * restore prior setting of econtext's caseValue fields, in case this node + * is itself within a larger CASE. + */ + save_datum = econtext->caseValue_datum; + save_isNull = econtext->caseValue_isNull; - clauses = caseExpr->args; + if (caseExpr->arg) + { + econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg, + econtext, + &econtext->caseValue_isNull, + NULL); + } /* - * we evaluate each of the WHEN clauses in turn, as soon as one is - * true we return the corresponding result. If none are true then we - * return the value of the default clause, or NULL if there is none. + * we evaluate each of the WHEN clauses in turn, as soon as one is true we + * return the corresponding result. If none are true then we return the + * value of the default clause, or NULL if there is none. */ foreach(clause, clauses) { CaseWhenState *wclause = lfirst(clause); + Datum clause_value; clause_value = ExecEvalExpr(wclause->expr, econtext, @@ -1591,12 +2710,14 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, NULL); /* - * if we have a true test, then we return the result, since the - * case statement is satisfied. A NULL result from the test is - * not considered true. + * if we have a true test, then we return the result, since the case + * statement is satisfied. A NULL result from the test is not + * considered true. */ if (DatumGetBool(clause_value) && !*isNull) { + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; return ExecEvalExpr(wclause->result, econtext, isNull, @@ -1604,6 +2725,9 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, } } + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + if (caseExpr->defresult) { return ExecEvalExpr(caseExpr->defresult, @@ -1616,64 +2740,75 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, return (Datum) 0; } +/* + * ExecEvalCaseTestExpr + * + * Return the value stored by CASE. + */ +static Datum +ExecEvalCaseTestExpr(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + if (isDone) + *isDone = ExprSingleResult; + *isNull = econtext->caseValue_isNull; + return econtext->caseValue_datum; +} + /* ---------------------------------------------------------------- * ExecEvalArray - ARRAY[] expressions - * - * NOTE: currently, if any input value is NULL then we return a NULL array, - * so the ARRAY[] construct can be considered strict. Eventually this will - * change; when it does, be sure to fix contain_nonstrict_functions(). * ---------------------------------------------------------------- */ static Datum ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, - bool *isNull) + bool *isNull, ExprDoneCond *isDone) { ArrayExpr *arrayExpr = (ArrayExpr *) astate->xprstate.expr; ArrayType *result; - List *element; + ListCell *element; Oid element_type = arrayExpr->element_typeid; int ndims = 0; int dims[MAXDIM]; int lbs[MAXDIM]; + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + if (!arrayExpr->multidims) { /* Elements are presumably of scalar type */ int nelems; Datum *dvalues; + bool *dnulls; int i = 0; ndims = 1; - nelems = length(astate->elements); + nelems = list_length(astate->elements); - /* Shouldn't happen here, but if length is 0, return NULL */ + /* Shouldn't happen here, but if length is 0, return empty array */ if (nelems == 0) - { - *isNull = true; - return (Datum) 0; - } + return PointerGetDatum(construct_empty_array(element_type)); dvalues = (Datum *) palloc(nelems * sizeof(Datum)); + dnulls = (bool *) palloc(nelems * sizeof(bool)); /* loop through and build array of datums */ foreach(element, astate->elements) { ExprState *e = (ExprState *) lfirst(element); - bool eisnull; - dvalues[i++] = ExecEvalExpr(e, econtext, &eisnull, NULL); - if (eisnull) - { - *isNull = true; - return (Datum) 0; - } + dvalues[i] = ExecEvalExpr(e, econtext, &dnulls[i], NULL); + i++; } /* setup for 1-D array of the given length */ dims[0] = nelems; lbs[0] = 1; - result = construct_md_array(dvalues, ndims, dims, lbs, + result = construct_md_array(dvalues, dnulls, ndims, dims, lbs, element_type, astate->elemlength, astate->elembyval, @@ -1682,15 +2817,29 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, else { /* Must be nested array expressions */ - char *dat = NULL; - Size ndatabytes = 0; - int nbytes; - int outer_nelems = length(astate->elements); + int nbytes = 0; + int nitems = 0; + int outer_nelems = 0; int elem_ndims = 0; int *elem_dims = NULL; int *elem_lbs = NULL; bool firstone = true; + bool havenulls = false; + bool haveempty = false; + char **subdata; + bits8 **subbitmaps; + int *subbytes; + int *subnitems; int i; + int32 dataoffset; + char *dat; + int iitem; + + i = list_length(astate->elements); + subdata = (char **) palloc(i * sizeof(char *)); + subbitmaps = (bits8 **) palloc(i * sizeof(bits8 *)); + subbytes = (int *) palloc(i * sizeof(int)); + subnitems = (int *) palloc(i * sizeof(int)); /* loop through and get data area from each element */ foreach(element, astate->elements) @@ -1699,13 +2848,14 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, bool eisnull; Datum arraydatum; ArrayType *array; - int elem_ndatabytes; + int this_ndims; arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL); + /* temporarily ignore null subarrays */ if (eisnull) { - *isNull = true; - return (Datum) 0; + haveempty = true; + continue; } array = DatumGetArrayTypeP(arraydatum); @@ -1716,20 +2866,28 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot merge incompatible arrays"), errdetail("Array with element type %s cannot be " - "included in ARRAY construct with element type %s.", + "included in ARRAY construct with element type %s.", 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, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds " \ - "the maximum allowed (%d)", ndims, MAXDIM))); + errmsg("number of array dimensions (%d) exceeds " \ + "the maximum allowed (%d)", ndims, MAXDIM))); elem_dims = (int *) palloc(elem_ndims * sizeof(int)); memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)); @@ -1741,80 +2899,508 @@ 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), elem_ndims * sizeof(int)) != 0) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), - errmsg("multidimensional arrays must have array " - "expressions with matching dimensions"))); + errmsg("multidimensional arrays must have array " + "expressions with matching dimensions"))); + } + + subdata[outer_nelems] = ARR_DATA_PTR(array); + subbitmaps[outer_nelems] = ARR_NULLBITMAP(array); + subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array); + nbytes += subbytes[outer_nelems]; + 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; + for (i = 1; i < ndims; i++) + { + dims[i] = elem_dims[i - 1]; + lbs[i] = elem_lbs[i - 1]; + } + + if (havenulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndims); + } + + result = (ArrayType *) palloc(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = element_type; + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + + dat = ARR_DATA_PTR(result); + iitem = 0; + for (i = 0; i < outer_nelems; i++) + { + memcpy(dat, subdata[i], subbytes[i]); + dat += subbytes[i]; + if (havenulls) + array_bitmap_copy(ARR_NULLBITMAP(result), iitem, + subbitmaps[i], 0, + subnitems[i]); + iitem += subnitems[i]; + } + } + + return PointerGetDatum(result); +} + +/* ---------------------------------------------------------------- + * ExecEvalRow - ROW() expressions + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRow(RowExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + HeapTuple tuple; + Datum *values; + bool *isnull; + int natts; + ListCell *arg; + int i; + + /* Set default values for result flags: non-null, not a set result */ + *isNull = false; + if (isDone) + *isDone = ExprSingleResult; + + /* Allocate workspace */ + natts = rstate->tupdesc->natts; + values = (Datum *) palloc0(natts * sizeof(Datum)); + isnull = (bool *) palloc(natts * sizeof(bool)); + + /* preset to nulls in case rowtype has some later-added columns */ + memset(isnull, true, natts * sizeof(bool)); + + /* Evaluate field values */ + i = 0; + foreach(arg, rstate->args) + { + ExprState *e = (ExprState *) lfirst(arg); + + values[i] = ExecEvalExpr(e, econtext, &isnull[i], NULL); + i++; + } + + tuple = heap_form_tuple(rstate->tupdesc, values, isnull); + + pfree(values); + pfree(isnull); + + return HeapTupleGetDatum(tuple); +} + +/* ---------------------------------------------------------------- + * ExecEvalRowCompare - ROW() comparison-op ROW() + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRowCompare(RowCompareExprState *rstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + bool result; + RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype; + int32 cmpresult = 0; + ListCell *l; + ListCell *r; + int i; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + i = 0; + forboth(l, rstate->largs, r, rstate->rargs) + { + ExprState *le = (ExprState *) lfirst(l); + ExprState *re = (ExprState *) lfirst(r); + FunctionCallInfoData locfcinfo; + + InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2, + NULL, NULL); + locfcinfo.arg[0] = ExecEvalExpr(le, econtext, + &locfcinfo.argnull[0], NULL); + locfcinfo.arg[1] = ExecEvalExpr(re, econtext, + &locfcinfo.argnull[1], NULL); + if (rstate->funcs[i].fn_strict && + (locfcinfo.argnull[0] || locfcinfo.argnull[1])) + return (Datum) 0; /* force NULL result */ + locfcinfo.isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); + if (locfcinfo.isnull) + return (Datum) 0; /* force NULL result */ + if (cmpresult != 0) + break; /* no need to compare remaining columns */ + i++; + } + + switch (rctype) + { + /* EQ and NE cases aren't allowed here */ + case ROWCOMPARE_LT: + result = (cmpresult < 0); + break; + case ROWCOMPARE_LE: + result = (cmpresult <= 0); + break; + case ROWCOMPARE_GE: + result = (cmpresult >= 0); + break; + case ROWCOMPARE_GT: + result = (cmpresult > 0); + break; + default: + elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype); + result = 0; /* keep compiler quiet */ + break; + } + + *isNull = false; + return BoolGetDatum(result); +} + +/* ---------------------------------------------------------------- + * ExecEvalCoalesce + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + ListCell *arg; + + if (isDone) + *isDone = ExprSingleResult; + + /* Simply loop through until something NOT NULL is found */ + foreach(arg, coalesceExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + Datum value; + + value = ExecEvalExpr(e, econtext, isNull, NULL); + if (!*isNull) + return value; + } + + /* Else return NULL */ + *isNull = true; + return (Datum) 0; +} + +/* ---------------------------------------------------------------- + * ExecEvalMinMax + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Datum result = (Datum) 0; + MinMaxOp op = ((MinMaxExpr *) minmaxExpr->xprstate.expr)->op; + FunctionCallInfoData locfcinfo; + ListCell *arg; + + if (isDone) + *isDone = ExprSingleResult; + *isNull = true; /* until we get a result */ + + InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2, NULL, NULL); + locfcinfo.argnull[0] = false; + locfcinfo.argnull[1] = false; + + foreach(arg, minmaxExpr->args) + { + ExprState *e = (ExprState *) lfirst(arg); + Datum value; + bool valueIsNull; + int32 cmpresult; + + value = ExecEvalExpr(e, econtext, &valueIsNull, NULL); + if (valueIsNull) + continue; /* ignore NULL inputs */ + + if (*isNull) + { + /* first nonnull input, adopt value */ + result = value; + *isNull = false; + } + else + { + /* apply comparison function */ + locfcinfo.arg[0] = result; + locfcinfo.arg[1] = value; + locfcinfo.isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); + if (locfcinfo.isnull) /* probably should not happen */ + continue; + if (cmpresult > 0 && op == IS_LEAST) + result = value; + else if (cmpresult < 0 && op == IS_GREATEST) + result = value; + } + } + + 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); - elem_ndatabytes = ARR_SIZE(array) - ARR_OVERHEAD(elem_ndims); - ndatabytes += elem_ndatabytes; - if (dat == NULL) - dat = (char *) palloc(ndatabytes); - else - dat = (char *) repalloc(dat, ndatabytes); + e = (ExprState *) lsecond(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + version = NULL; + else + version = DatumGetTextP(value); - memcpy(dat + (ndatabytes - elem_ndatabytes), - ARR_DATA_PTR(array), - elem_ndatabytes); - } + e = (ExprState *) lthird(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + standalone = DatumGetInt32(value); - /* setup for multi-D array */ - dims[0] = outer_nelems; - lbs[0] = 1; - for (i = 1; i < ndims; i++) - { - dims[i] = elem_dims[i - 1]; - lbs[i] = elem_lbs[i - 1]; - } + *isNull = false; - nbytes = ndatabytes + ARR_OVERHEAD(ndims); - result = (ArrayType *) palloc(nbytes); + return PointerGetDatum(xmlroot(data, + version, + standalone)); + } + break; - result->size = nbytes; - result->ndim = ndims; - result->flags = 0; - result->elemtype = element_type; - memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); - memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); - if (ndatabytes > 0) - memcpy(ARR_DATA_PTR(result), dat, ndatabytes); + case IS_XMLSERIALIZE: + { + ExprState *e; - if (dat != NULL) - pfree(dat); - } + /* argument type is known to be xml */ + Assert(list_length(xmlExpr->args) == 1); - return PointerGetDatum(result); -} + e = (ExprState *) linitial(xmlExpr->args); + value = ExecEvalExpr(e, econtext, &isnull, NULL); + if (isnull) + return (Datum) 0; -/* ---------------------------------------------------------------- - * ExecEvalCoalesce - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, - bool *isNull) -{ - List *arg; + *isNull = false; - /* Simply loop through until something NOT NULL is found */ - foreach(arg, coalesceExpr->args) - { - ExprState *e = (ExprState *) lfirst(arg); - Datum value; + return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption)); + } + break; - value = ExecEvalExpr(e, econtext, isNull, NULL); - if (!*isNull) - return value; + 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; } - /* Else return NULL */ - *isNull = true; + elog(ERROR, "unrecognized XML operation"); return (Datum) 0; } @@ -1827,33 +3413,37 @@ ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, * ---------------------------------------------------------------- */ static Datum -ExecEvalNullIf(FuncExprState *fcache, ExprContext *econtext, - bool *isNull) +ExecEvalNullIf(FuncExprState *nullIfExpr, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { Datum result; FunctionCallInfoData fcinfo; ExprDoneCond argDone; List *argList; + if (isDone) + *isDone = ExprSingleResult; + /* * Initialize function cache if first time through */ - if (fcache->func.fn_oid == InvalidOid) + if (nullIfExpr->func.fn_oid == InvalidOid) { - NullIfExpr *op = (NullIfExpr *) fcache->xprstate.expr; + NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr; - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); - Assert(!fcache->func.fn_retset); + init_fcache(op->opfuncid, nullIfExpr, + econtext->ecxt_per_query_memory, true); + Assert(!nullIfExpr->func.fn_retset); } /* - * extract info from fcache + * extract info from nullIfExpr */ - argList = fcache->args; + argList = nullIfExpr->args; /* Need to prep callinfo structure */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); + InitFunctionCallInfoData(fcinfo, &(nullIfExpr->func), 0, NULL, NULL); argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); if (argDone != ExprSingleResult) ereport(ERROR, @@ -1886,7 +3476,7 @@ ExecEvalNullIf(FuncExprState *fcache, ExprContext *econtext, * ---------------------------------------------------------------- */ static Datum -ExecEvalNullTest(GenericExprState *nstate, +ExecEvalNullTest(NullTestState *nstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) @@ -1899,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 */ + } } } @@ -2022,7 +3661,7 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, { CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr; Datum result; - List *l; + ListCell *l; result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); @@ -2039,8 +3678,8 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, if (*isNull) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), - errmsg("domain %s does not allow null values", - format_type_be(ctest->resulttype)))); + errmsg("domain %s does not allow null values", + format_type_be(ctest->resulttype)))); break; case DOM_CONSTRAINT_CHECK: { @@ -2053,8 +3692,7 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, * Set up value to be returned by CoerceToDomainValue * nodes. We must save and restore prior setting of * econtext's domainValue fields, in case this node is - * itself within a check expression for another - * domain. + * itself within a check expression for another domain. */ save_datum = econtext->domainValue_datum; save_isNull = econtext->domainValue_isNull; @@ -2094,9 +3732,12 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, * Return the value stored by CoerceToDomain. */ static Datum -ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, - ExprContext *econtext, bool *isNull) +ExecEvalCoerceToDomainValue(ExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { + if (isDone) + *isDone = ExprSingleResult; *isNull = econtext->domainValue_isNull; return econtext->domainValue_datum; } @@ -2108,235 +3749,320 @@ ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, * ---------------------------------------------------------------- */ static Datum -ExecEvalFieldSelect(GenericExprState *fstate, +ExecEvalFieldSelect(FieldSelectState *fstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr; + AttrNumber fieldnum = fselect->fieldnum; Datum result; - TupleTableSlot *resSlot; + Datum tupDatum; + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + Form_pg_attribute attr; + HeapTupleData tmptup; - result = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); + tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); /* this test covers the isDone exception too: */ if (*isNull) - return result; + return tupDatum; + + tuple = DatumGetHeapTupleHeader(tupDatum); + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + + /* Lookup tupdesc if first time through or if type changes */ + 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; + } - resSlot = (TupleTableSlot *) DatumGetPointer(result); - Assert(resSlot != NULL && IsA(resSlot, TupleTableSlot)); - result = heap_getattr(resSlot->val, - fselect->fieldnum, - resSlot->ttc_tupleDescriptor, + /* 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 + * columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, + fieldnum, + tupDesc, isNull); return result; } /* ---------------------------------------------------------------- - * ExecEvalExpr - * - * Recursively evaluate a targetlist or qualification expression. - * - * Inputs: - * expression: the expression state tree to evaluate - * econtext: evaluation context information - * - * Outputs: - * return value: Datum value of result - * *isNull: set to TRUE if result is NULL (actual return value is - * meaningless if so); set to FALSE if non-null result - * *isDone: set to indicator of set-result status - * - * A caller that can only accept a singleton (non-set) result should pass - * NULL for isDone; if the expression computes a set result then an error - * will be reported via ereport. If the caller does pass an isDone pointer - * then *isDone is set to one of these three states: - * ExprSingleResult singleton result (not a set) - * ExprMultipleResult return value is one element of a set - * ExprEndResult there are no more elements in the set - * When ExprMultipleResult is returned, the caller should invoke - * ExecEvalExpr() repeatedly until ExprEndResult is returned. ExprEndResult - * is returned after the last real set element. For convenience isNull will - * always be set TRUE when ExprEndResult is returned, but this should not be - * taken as indicating a NULL element of the set. Note that these return - * conventions allow us to distinguish among a singleton NULL, a NULL element - * of a set, and an empty set. - * - * The caller should already have switched into the temporary memory - * context econtext->ecxt_per_tuple_memory. The convenience entry point - * ExecEvalExprSwitchContext() is provided for callers who don't prefer to - * do the switch in an outer loop. We do not do the switch here because - * it'd be a waste of cycles during recursive entries to ExecEvalExpr(). + * ExecEvalFieldStore * - * This routine is an inner loop routine and must be as fast as possible. + * Evaluate a FieldStore node. * ---------------------------------------------------------------- */ -Datum -ExecEvalExpr(ExprState *expression, - ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone) +static Datum +ExecEvalFieldStore(FieldStoreState *fstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) { - Datum retDatum; - Expr *expr; + FieldStore *fstore = (FieldStore *) fstate->xprstate.expr; + HeapTuple tuple; + Datum tupDatum; + TupleDesc tupDesc; + Datum *values; + bool *isnull; + Datum save_datum; + bool save_isNull; + ListCell *l1, + *l2; + + tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); - /* Set default values for result flags: non-null, not a set result */ - *isNull = false; - if (isDone) - *isDone = ExprSingleResult; + if (isDone && *isDone == ExprEndResult) + return tupDatum; + + /* 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)); + isnull = (bool *) palloc(tupDesc->natts * sizeof(bool)); - /* Is this still necessary? Doubtful... */ - if (expression == NULL) + if (!*isNull) { - *isNull = true; - return (Datum) 0; + /* + * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We + * set all the fields in the struct just in case. + */ + HeapTupleHeader tuphdr; + HeapTupleData tmptup; + + tuphdr = DatumGetHeapTupleHeader(tupDatum); + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuphdr; + + heap_deform_tuple(&tmptup, tupDesc, values, isnull); + } + else + { + /* Convert null input tuple into an all-nulls row */ + memset(isnull, true, tupDesc->natts * sizeof(bool)); } - /* - * here we dispatch the work to the appropriate type of function given - * the type of our expression. - */ - expr = expression->expr; - switch (nodeTag(expr)) + /* Result is never null */ + *isNull = false; + + save_datum = econtext->caseValue_datum; + save_isNull = econtext->caseValue_isNull; + + forboth(l1, fstate->newvals, l2, fstore->fieldnums) { - case T_Var: - retDatum = ExecEvalVar((Var *) expr, econtext, isNull); - break; - case T_Const: - { - Const *con = (Const *) expr; + ExprState *newval = (ExprState *) lfirst(l1); + AttrNumber fieldnum = lfirst_int(l2); - retDatum = con->constvalue; - *isNull = con->constisnull; - break; - } - case T_Param: - retDatum = ExecEvalParam((Param *) expr, econtext, isNull); - break; - case T_Aggref: - retDatum = ExecEvalAggref((AggrefExprState *) expression, - econtext, - isNull); - break; - case T_ArrayRef: - retDatum = ExecEvalArrayRef((ArrayRefExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_FuncExpr: - retDatum = ExecEvalFunc((FuncExprState *) expression, econtext, - isNull, isDone); - break; - case T_OpExpr: - retDatum = ExecEvalOper((FuncExprState *) expression, econtext, - isNull, isDone); - break; - case T_DistinctExpr: - retDatum = ExecEvalDistinct((FuncExprState *) expression, econtext, - isNull); - break; - case T_ScalarArrayOpExpr: - retDatum = ExecEvalScalarArrayOp((ScalarArrayOpExprState *) expression, - econtext, isNull); - break; - case T_BoolExpr: - { - BoolExprState *state = (BoolExprState *) expression; + Assert(fieldnum > 0 && fieldnum <= tupDesc->natts); - switch (((BoolExpr *) expr)->boolop) - { - case AND_EXPR: - retDatum = ExecEvalAnd(state, econtext, isNull); - break; - case OR_EXPR: - retDatum = ExecEvalOr(state, econtext, isNull); - break; - case NOT_EXPR: - retDatum = ExecEvalNot(state, econtext, isNull); - break; - default: - elog(ERROR, "unrecognized boolop: %d", - (int) ((BoolExpr *) expr)->boolop); - retDatum = 0; /* keep compiler quiet */ - break; - } - break; - } - case T_SubPlan: - retDatum = ExecSubPlan((SubPlanState *) expression, - econtext, - isNull); - break; - case T_FieldSelect: - retDatum = ExecEvalFieldSelect((GenericExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_RelabelType: - retDatum = ExecEvalExpr(((GenericExprState *) expression)->arg, - econtext, - isNull, - isDone); - break; - case T_CaseExpr: - retDatum = ExecEvalCase((CaseExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_ArrayExpr: - retDatum = ExecEvalArray((ArrayExprState *) expression, - econtext, - isNull); - break; - case T_CoalesceExpr: - retDatum = ExecEvalCoalesce((CoalesceExprState *) expression, - econtext, - isNull); - break; - case T_NullIfExpr: - retDatum = ExecEvalNullIf((FuncExprState *) expression, - econtext, - isNull); - break; - case T_NullTest: - retDatum = ExecEvalNullTest((GenericExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_BooleanTest: - retDatum = ExecEvalBooleanTest((GenericExprState *) expression, - econtext, - isNull, - isDone); - break; - case T_CoerceToDomain: - retDatum = ExecEvalCoerceToDomain((CoerceToDomainState *) expression, - econtext, - isNull, - isDone); - break; - case T_CoerceToDomainValue: - retDatum = ExecEvalCoerceToDomainValue((CoerceToDomainValue *) expr, - econtext, - isNull); - break; - default: - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(expression)); - retDatum = 0; /* keep compiler quiet */ - break; + /* + * Use the CaseTestExpr mechanism to pass down the old value of the + * field being replaced; this is useful in case we have a nested field + * update situation. It's safe to reuse the CASE mechanism because + * there cannot be a CASE between here and where the value would be + * needed. + */ + econtext->caseValue_datum = values[fieldnum - 1]; + econtext->caseValue_isNull = isnull[fieldnum - 1]; + + values[fieldnum - 1] = ExecEvalExpr(newval, + econtext, + &isnull[fieldnum - 1], + NULL); + } + + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + + tuple = heap_form_tuple(tupDesc, values, isnull); + + pfree(values); + pfree(isnull); + + return HeapTupleGetDatum(tuple); +} + +/* ---------------------------------------------------------------- + * ExecEvalRelabelType + * + * Evaluate a RelabelType node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalRelabelType(GenericExprState *exprstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + 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); } - return retDatum; -} /* ExecEvalExpr() */ + /* 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 */ +} /* - * Same as above, but get into the right allocation context explicitly. + * ExecEvalExprSwitchContext + * + * Same as ExecEvalExpr, but get into the right allocation context explicitly. */ Datum ExecEvalExprSwitchContext(ExprState *expression, @@ -2367,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 @@ -2393,20 +4119,38 @@ ExecInitExpr(Expr *node, PlanState *parent) if (node == NULL) return NULL; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + switch (nodeTag(node)) { case T_Var: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalVar; + break; case T_Const: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalConst; + break; case T_Param: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalParam; + break; case T_CoerceToDomainValue: - /* No special setup needed for these node types */ state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalCoerceToDomainValue; + break; + case T_CaseTestExpr: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalCaseTestExpr; break; case T_Aggref: { Aggref *aggref = (Aggref *) node; AggrefExprState *astate = makeNode(AggrefExprState); + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref; if (parent && IsA(parent, AggState)) { AggState *aggstate = (AggState *) parent; @@ -2415,32 +4159,72 @@ 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.) + * nonsensical. (This should have been caught earlier, + * but we defend against it here anyway.) */ 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; ArrayRefExprState *astate = makeNode(ArrayRefExprState); + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef; astate->refupperindexpr = (List *) ExecInitExpr((Expr *) aref->refupperindexpr, parent); astate->reflowerindexpr = (List *) @@ -2462,6 +4246,7 @@ ExecInitExpr(Expr *node, PlanState *parent) FuncExpr *funcexpr = (FuncExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc; fstate->args = (List *) ExecInitExpr((Expr *) funcexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2473,6 +4258,7 @@ ExecInitExpr(Expr *node, PlanState *parent) OpExpr *opexpr = (OpExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper; fstate->args = (List *) ExecInitExpr((Expr *) opexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2484,6 +4270,7 @@ ExecInitExpr(Expr *node, PlanState *parent) DistinctExpr *distinctexpr = (DistinctExpr *) node; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct; fstate->args = (List *) ExecInitExpr((Expr *) distinctexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2495,6 +4282,7 @@ ExecInitExpr(Expr *node, PlanState *parent) ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState); + sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp; sstate->fxprstate.args = (List *) ExecInitExpr((Expr *) opexpr->args, parent); sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */ @@ -2507,6 +4295,22 @@ ExecInitExpr(Expr *node, PlanState *parent) BoolExpr *boolexpr = (BoolExpr *) node; BoolExprState *bstate = makeNode(BoolExprState); + switch (boolexpr->boolop) + { + case AND_EXPR: + bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAnd; + break; + case OR_EXPR: + bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOr; + break; + case NOT_EXPR: + bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNot; + break; + default: + elog(ERROR, "unrecognized boolop: %d", + (int) boolexpr->boolop); + break; + } bstate->args = (List *) ExecInitExpr((Expr *) boolexpr->args, parent); state = (ExprState *) bstate; @@ -2514,37 +4318,54 @@ ExecInitExpr(Expr *node, PlanState *parent) break; case T_SubPlan: { - /* Keep this in sync with ExecInitExprInitPlan, below */ SubPlan *subplan = (SubPlan *) node; - SubPlanState *sstate = makeNode(SubPlanState); + 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->exprs = (List *) - ExecInitExpr((Expr *) subplan->exprs, 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; - GenericExprState *gstate = makeNode(GenericExprState); + FieldSelectState *fstate = makeNode(FieldSelectState); - gstate->arg = ExecInitExpr(fselect->arg, parent); - state = (ExprState *) gstate; + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect; + fstate->arg = ExecInitExpr(fselect->arg, parent); + fstate->argdesc = NULL; + state = (ExprState *) fstate; + } + break; + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + FieldStoreState *fstate = makeNode(FieldStoreState); + + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore; + fstate->arg = ExecInitExpr(fstore->arg, parent); + fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent); + fstate->argdesc = NULL; + state = (ExprState *) fstate; } break; case T_RelabelType: @@ -2552,32 +4373,83 @@ ExecInitExpr(Expr *node, PlanState *parent) RelabelType *relabel = (RelabelType *) node; GenericExprState *gstate = makeNode(GenericExprState); + gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType; gstate->arg = ExecInitExpr(relabel->arg, 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); + + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype; + cstate->arg = ExecInitExpr(convert->arg, parent); + state = (ExprState *) cstate; + } + break; case T_CaseExpr: { CaseExpr *caseexpr = (CaseExpr *) node; CaseExprState *cstate = makeNode(CaseExprState); - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, caseexpr->args) + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase; + cstate->arg = ExecInitExpr(caseexpr->arg, parent); + foreach(l, caseexpr->args) { - CaseWhen *when = (CaseWhen *) lfirst(inlist); + CaseWhen *when = (CaseWhen *) lfirst(l); CaseWhenState *wstate = makeNode(CaseWhenState); Assert(IsA(when, CaseWhen)); + wstate->xprstate.evalfunc = NULL; /* not used */ wstate->xprstate.expr = (Expr *) when; wstate->expr = ExecInitExpr(when->expr, parent); wstate->result = ExecInitExpr(when->result, parent); - FastAppend(&outlist, wstate); + outlist = lappend(outlist, wstate); } - cstate->args = FastListValue(&outlist); - /* caseexpr->arg should be null by now */ - Assert(caseexpr->arg == NULL); + cstate->args = outlist; cstate->defresult = ExecInitExpr(caseexpr->defresult, parent); state = (ExprState *) cstate; } @@ -2586,19 +4458,19 @@ ExecInitExpr(Expr *node, PlanState *parent) { ArrayExpr *arrayexpr = (ArrayExpr *) node; ArrayExprState *astate = makeNode(ArrayExprState); - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, arrayexpr->elements) + astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArray; + foreach(l, arrayexpr->elements) { - Expr *e = (Expr *) lfirst(inlist); + Expr *e = (Expr *) lfirst(l); ExprState *estate; estate = ExecInitExpr(e, parent); - FastAppend(&outlist, estate); + outlist = lappend(outlist, estate); } - astate->elements = FastListValue(&outlist); + astate->elements = outlist; /* do one-time catalog lookup for type info */ get_typlenbyvalalign(arrayexpr->element_typeid, &astate->elemlength, @@ -2607,31 +4479,232 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) astate; } break; + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + RowExprState *rstate = makeNode(RowExprState); + Form_pg_attribute *attrs; + List *outlist = NIL; + ListCell *l; + int i; + + rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow; + /* Build tupdesc to describe result tuples */ + if (rowexpr->row_typeid == RECORDOID) + { + /* 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_copy(rowexpr->row_typeid, -1); + } + /* Set up evaluation, skipping any deleted columns */ + Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts); + attrs = rstate->tupdesc->attrs; + i = 0; + foreach(l, rowexpr->args) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + if (!attrs[i]->attisdropped) + { + /* + * Guard against ALTER COLUMN TYPE on rowtype since + * the RowExpr was created. XXX should we check + * typmod too? Not sure we can be sure it'll be the + * same. + */ + if (exprType((Node *) e) != attrs[i]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("ROW() column has type %s instead of type %s", + format_type_be(exprType((Node *) e)), + format_type_be(attrs[i]->atttypid)))); + } + else + { + /* + * Ignore original expression and insert a NULL. We + * don't really care what type of NULL it is, so + * always make an int4 NULL. + */ + e = (Expr *) makeNullConst(INT4OID, -1); + } + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + i++; + } + rstate->args = outlist; + state = (ExprState *) rstate; + } + break; + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + RowCompareExprState *rstate = makeNode(RowCompareExprState); + int nopers = list_length(rcexpr->opnos); + List *outlist; + ListCell *l; + ListCell *l2; + int i; + + rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare; + Assert(list_length(rcexpr->largs) == nopers); + outlist = NIL; + foreach(l, rcexpr->largs) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->largs = outlist; + Assert(list_length(rcexpr->rargs) == nopers); + outlist = NIL; + foreach(l, rcexpr->rargs) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + rstate->rargs = outlist; + Assert(list_length(rcexpr->opfamilies) == nopers); + rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo)); + i = 0; + forboth(l, rcexpr->opnos, l2, rcexpr->opfamilies) + { + 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 + * index support machinery doesn't do that, and neither + * does this code. + */ + fmgr_info(proc, &(rstate->funcs[i])); + i++; + } + state = (ExprState *) rstate; + } + break; case T_CoalesceExpr: { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; CoalesceExprState *cstate = makeNode(CoalesceExprState); - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, coalesceexpr->args) + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoalesce; + foreach(l, coalesceexpr->args) { - Expr *e = (Expr *) lfirst(inlist); + Expr *e = (Expr *) lfirst(l); ExprState *estate; estate = ExecInitExpr(e, parent); - FastAppend(&outlist, estate); + outlist = lappend(outlist, estate); } - cstate->args = FastListValue(&outlist); + cstate->args = outlist; state = (ExprState *) cstate; } break; + case T_MinMaxExpr: + { + MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; + MinMaxExprState *mstate = makeNode(MinMaxExprState); + List *outlist = NIL; + ListCell *l; + TypeCacheEntry *typentry; + + mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax; + foreach(l, minmaxexpr->args) + { + Expr *e = (Expr *) lfirst(l); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + outlist = lappend(outlist, estate); + } + mstate->args = outlist; + /* Look up the btree comparison function for the datatype */ + typentry = lookup_type_cache(minmaxexpr->minmaxtype, + TYPECACHE_CMP_PROC); + if (!OidIsValid(typentry->cmp_proc)) + ereport(ERROR, + (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. + */ + 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; FuncExprState *fstate = makeNode(FuncExprState); + fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf; fstate->args = (List *) ExecInitExpr((Expr *) nullifexpr->args, parent); fstate->func.fn_oid = InvalidOid; /* not initialized */ @@ -2641,10 +4714,13 @@ ExecInitExpr(Expr *node, PlanState *parent) case T_NullTest: { NullTest *ntest = (NullTest *) node; - GenericExprState *gstate = makeNode(GenericExprState); + NullTestState *nstate = makeNode(NullTestState); - 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: @@ -2652,6 +4728,7 @@ ExecInitExpr(Expr *node, PlanState *parent) BooleanTest *btest = (BooleanTest *) node; GenericExprState *gstate = makeNode(GenericExprState); + gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest; gstate->arg = ExecInitExpr(btest->arg, parent); state = (ExprState *) gstate; } @@ -2661,34 +4738,39 @@ ExecInitExpr(Expr *node, PlanState *parent) CoerceToDomain *ctest = (CoerceToDomain *) node; CoerceToDomainState *cstate = makeNode(CoerceToDomainState); + cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain; cstate->arg = ExecInitExpr(ctest->arg, parent); cstate->constraints = GetDomainConstraints(ctest->resulttype); state = (ExprState *) cstate; } break; + case T_CurrentOfExpr: + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalCurrentOfExpr; + break; case T_TargetEntry: { TargetEntry *tle = (TargetEntry *) node; GenericExprState *gstate = makeNode(GenericExprState); + gstate->xprstate.evalfunc = NULL; /* not used */ gstate->arg = ExecInitExpr(tle->expr, parent); state = (ExprState *) gstate; } break; case T_List: { - FastList outlist; - List *inlist; + List *outlist = NIL; + ListCell *l; - FastListInit(&outlist); - foreach(inlist, (List *) node) + foreach(l, (List *) node) { - FastAppend(&outlist, - ExecInitExpr((Expr *) lfirst(inlist), - parent)); + outlist = lappend(outlist, + ExecInitExpr((Expr *) lfirst(l), + parent)); } /* Don't fall through to the "common" code below */ - return (ExprState *) FastListValue(&outlist); + return (ExprState *) outlist; } default: elog(ERROR, "unrecognized node type: %d", @@ -2703,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->exprs = (List *) ExecInitExpr((Expr *) node->exprs, 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) @@ -2745,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); @@ -2795,7 +4852,7 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull) { bool result; MemoryContext oldContext; - List *qlist; + ListCell *l; /* * debugging stuff @@ -2812,22 +4869,22 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull) oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* - * Evaluate the qual conditions one at a time. If we find a FALSE - * result, we can stop evaluating and return FALSE --- the AND result - * must be FALSE. Also, if we find a NULL result when resultForNull - * is FALSE, we can stop and return FALSE --- the AND result must be - * FALSE or NULL in that case, and the caller doesn't care which. + * Evaluate the qual conditions one at a time. If we find a FALSE result, + * we can stop evaluating and return FALSE --- the AND result must be + * FALSE. Also, if we find a NULL result when resultForNull is FALSE, we + * can stop and return FALSE --- the AND result must be FALSE or NULL in + * that case, and the caller doesn't care which. * - * If we get to the end of the list, we can return TRUE. This will - * happen when the AND result is indeed TRUE, or when the AND result - * is NULL (one or more NULL subresult, with all the rest TRUE) and - * the caller has specified resultForNull = TRUE. + * If we get to the end of the list, we can return TRUE. This will happen + * when the AND result is indeed TRUE, or when the AND result is NULL (one + * or more NULL subresult, with all the rest TRUE) and the caller has + * specified resultForNull = TRUE. */ result = true; - foreach(qlist, qual) + foreach(l, qual) { - ExprState *clause = (ExprState *) lfirst(qlist); + ExprState *clause = (ExprState *) lfirst(l); Datum expr_value; bool isNull; @@ -2863,7 +4920,7 @@ int ExecTargetListLength(List *targetlist) { /* This used to be more complex, but fjoins are dead */ - return length(targetlist); + return list_length(targetlist); } /* @@ -2873,94 +4930,66 @@ int ExecCleanTargetListLength(List *targetlist) { int len = 0; - List *tl; + ListCell *tl; foreach(tl, targetlist) { TargetEntry *curTle = (TargetEntry *) lfirst(tl); Assert(IsA(curTle, TargetEntry)); - if (!curTle->resdom->resjunk) + if (!curTle->resjunk) len++; } return len; } -/* ---------------------------------------------------------------- - * ExecTargetList - * +/* + * ExecTargetList * Evaluates a targetlist with respect to the given - * expression context and returns a tuple. + * expression context. Returns TRUE if we were able to create + * a result, FALSE if we have exhausted a set-valued expression. * - * The caller must pass workspace for the values and nulls arrays - * as well as the itemIsDone array. This convention saves palloc'ing - * workspace on each call, and some callers may find it useful to examine - * the values array directly. + * Results are stored into the passed values and isnull arrays. + * The caller must provide an itemIsDone array that persists across calls. * * As with ExecEvalExpr, the caller should pass isDone = NULL if not * 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 HeapTuple +static bool ExecTargetList(List *targetlist, - TupleDesc targettype, ExprContext *econtext, Datum *values, - char *nulls, + bool *isnull, ExprDoneCond *itemIsDone, ExprDoneCond *isDone) { MemoryContext oldContext; - List *tl; - bool isNull; + ListCell *tl; bool haveDoneSets; - static struct tupleDesc NullTupleDesc; /* we assume this inits to - * zeroes */ - - /* - * debugging stuff - */ - EV_printf("ExecTargetList: tl is "); - EV_nodeDisplay(targetlist); - EV_printf("\n"); /* * Run in short-lived per-tuple context while computing expressions. */ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - /* - * There used to be some klugy and demonstrably broken code here that - * special-cased the situation where targetlist == NIL. Now we just - * fall through and return an empty-but-valid tuple. We do, however, - * have to cope with the possibility that targettype is NULL --- - * heap_formtuple won't like that, so pass a dummy descriptor with - * natts = 0 to deal with it. - */ - if (targettype == NULL) - targettype = &NullTupleDesc; - /* * 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) { GenericExprState *gstate = (GenericExprState *) lfirst(tl); TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resdom->resno - 1; + AttrNumber resind = tle->resno - 1; values[resind] = ExecEvalExpr(gstate->arg, econtext, - &isNull, + &isnull[resind], &itemIsDone[resind]); - nulls[resind] = isNull ? 'n' : ' '; if (itemIsDone[resind] != ExprSingleResult) { @@ -2990,38 +5019,36 @@ ExecTargetList(List *targetlist, if (*isDone == ExprSingleResult) { /* - * all sets are done, so report that tlist expansion is - * complete. + * all sets are done, so report that tlist expansion is complete. */ *isDone = ExprEndResult; MemoryContextSwitchTo(oldContext); - return NULL; + return false; } else { /* - * We have some done and some undone sets. Restart the done - * ones so that we can deliver a tuple (if possible). + * We have some done and some undone sets. Restart the done ones + * so that we can deliver a tuple (if possible). */ foreach(tl, targetlist) { GenericExprState *gstate = (GenericExprState *) lfirst(tl); TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resdom->resno - 1; + AttrNumber resind = tle->resno - 1; if (itemIsDone[resind] == ExprEndResult) { values[resind] = ExecEvalExpr(gstate->arg, econtext, - &isNull, + &isnull[resind], &itemIsDone[resind]); - nulls[resind] = isNull ? 'n' : ' '; if (itemIsDone[resind] == ExprEndResult) { /* - * Oh dear, this item is returning an empty set. - * Guess we can't make a tuple after all. + * Oh dear, this item is returning an empty set. Guess + * we can't make a tuple after all. */ *isDone = ExprEndResult; break; @@ -3030,9 +5057,9 @@ ExecTargetList(List *targetlist, } /* - * If we cannot make a tuple because some sets are empty, we - * still have to cycle the nonempty sets to completion, else - * resources will not be released from subplans etc. + * If we cannot make a tuple because some sets are empty, we still + * have to cycle the nonempty sets to completion, else resources + * will not be released from subplans etc. * * XXX is that still necessary? */ @@ -3042,79 +5069,150 @@ ExecTargetList(List *targetlist, { GenericExprState *gstate = (GenericExprState *) lfirst(tl); TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resdom->resno - 1; + AttrNumber resind = tle->resno - 1; while (itemIsDone[resind] == ExprMultipleResult) { - (void) ExecEvalExpr(gstate->arg, - econtext, - &isNull, - &itemIsDone[resind]); + values[resind] = ExecEvalExpr(gstate->arg, + econtext, + &isnull[resind], + &itemIsDone[resind]); } } MemoryContextSwitchTo(oldContext); - return NULL; + return false; } } } - /* - * form the new result tuple (in the caller's memory context!) - */ + /* Report success */ MemoryContextSwitchTo(oldContext); - return heap_formtuple(targettype, values, nulls); + return true; } -/* ---------------------------------------------------------------- - * ExecProject +/* + * ExecProject * * projects a tuple based on projection info and stores - * it in the specified tuple table slot. + * it in the previously specified tuple table slot. * - * Note: someday soon the executor can be extended to eliminate - * redundant projections by storing pointers to datums - * in the tuple table and then passing these around when - * possible. this should make things much quicker. - * -cim 6/3/91 - * ---------------------------------------------------------------- + * Note: the result is always a virtual tuple; therefore it + * may reference the contents of the exprContext's scan tuples + * and/or temporary results constructed in the exprContext. + * If the caller wishes the result to be valid longer than that + * data will be valid, he must call ExecMaterializeSlot on the + * result slot. */ TupleTableSlot * ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) { TupleTableSlot *slot; - TupleDesc tupType; - HeapTuple newTuple; + ExprContext *econtext; + int numSimpleVars; /* * sanity checks */ - if (projInfo == NULL) - return (TupleTableSlot *) NULL; + Assert(projInfo != NULL); /* * get the projection info we want */ slot = projInfo->pi_slot; - tupType = slot->ttc_tupleDescriptor; + 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 + * us to use the slot's Datum/isnull arrays as workspace. (Also, we can + * return the slot as-is if we decide no rows can be projected.) + */ + ExecClearTuple(slot); + + /* + * 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 ... + */ + numSimpleVars = projInfo->pi_numSimpleVars; + if (numSimpleVars > 0) + { + 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]; + } + } + } /* - * form a new result tuple (if possible --- result can be NULL) + * 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. */ - newTuple = ExecTargetList(projInfo->pi_targetlist, - tupType, - projInfo->pi_exprContext, - projInfo->pi_tupValues, - projInfo->pi_tupNulls, - projInfo->pi_itemIsDone, - isDone); + if (projInfo->pi_targetlist) + { + 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 */ + } /* - * store the tuple in the projection slot and return the slot. + * Successfully formed a result row. Mark the result slot as containing a + * valid virtual tuple. */ - return ExecStoreTuple(newTuple, /* tuple to store */ - slot, /* slot to store in */ - InvalidBuffer, /* tuple has no buffer */ - true); + return ExecStoreVirtualTuple(slot); }