X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fexecutor%2FexecQual.c;h=d2efab0e36fa459b5991387cf0d2fda832604d3a;hb=9ca5c754fbbe099097e1c4a1def2b58225aca363;hp=1aeb07a7a9ffda1f4620cf408e11dbce69526f6c;hpb=d0e17e211230d37f26843711115864a87d0eae18;p=postgresql diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 1aeb07a7a9..d2efab0e36 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -3,12 +3,12 @@ * execQual.c * Routines to evaluate qualification and targetlist expressions * - * Portions Copyright (c) 1996-2000, PostgreSQL, Inc + * Portions Copyright (c) 1996-2002, 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.75 2000/07/22 03:34:27 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.137 2003/07/30 19:02:18 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,82 +35,134 @@ #include "postgres.h" #include "access/heapam.h" -#include "catalog/pg_language.h" -#include "executor/execFlatten.h" +#include "catalog/pg_type.h" +#include "commands/typecmds.h" #include "executor/execdebug.h" #include "executor/functions.h" #include "executor/nodeSubplan.h" +#include "miscadmin.h" +#include "optimizer/planmain.h" +#include "parser/parse_expr.h" +#include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/fcache2.h" +#include "utils/lsyscache.h" /* static function decls */ -static Datum ExecEvalAggref(Aggref *aggref, ExprContext *econtext, bool *isNull); -static Datum ExecEvalArrayRef(ArrayRef *arrayRef, ExprContext *econtext, - bool *isNull, bool *isDone); -static Datum ExecEvalOper(Expr *opClause, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext, - bool *isNull, bool *isDone); -static void ExecEvalFuncArgs(FunctionCachePtr fcache, ExprContext *econtext, - List *argList, FunctionCallInfo fcinfo, - bool *argIsDone); -static Datum ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull); -static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull); -static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull); +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 ExecMakeFunctionResult(Node *node, List *arguments, - ExprContext *econtext, bool *isNull, bool *isDone); +static Datum ExecEvalParam(Param *expression, ExprContext *econtext, + bool *isNull); +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); +static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, + ExprContext *econtext, bool *isNull); +static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, + List *argList, ExprContext *econtext); +static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, + bool *isNull); +static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, + bool *isNull); +static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, + bool *isNull); +static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalArray(ArrayExprState *astate, + ExprContext *econtext, + bool *isNull); +static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, + ExprContext *econtext, + bool *isNull); +static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, ExprContext *econtext, + bool *isNull); +static Datum ExecEvalNullTest(GenericExprState *nstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalBooleanTest(GenericExprState *bstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +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, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); -/* + +/*---------- * ExecEvalArrayRef * * This function takes an ArrayRef and returns the extracted Datum * if it's a simple reference, or the modified array value if it's - * an array assignment (read array element insertion). + * 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: we deliberately refrain from applying DatumGetArrayTypeP() here, * even though that might seem natural, because this code needs to support * both varlena arrays and fixed-length array types. DatumGetArrayTypeP() * only works for the varlena kind. The routines we call in arrayfuncs.c * have to know the difference (that's what they need refattrlength for). + *---------- */ static Datum -ExecEvalArrayRef(ArrayRef *arrayRef, +ExecEvalArrayRef(ArrayRefExprState *astate, ExprContext *econtext, bool *isNull, - bool *isDone) + ExprDoneCond *isDone) { + ArrayRef *arrayRef = (ArrayRef *) astate->xprstate.expr; ArrayType *array_source; ArrayType *resultArray; + bool isAssignment = (arrayRef->refassgnexpr != NULL); List *elt; int i = 0, j = 0; IntArray upper, lower; int *lIndex; - bool dummy; - - *isNull = false; if (arrayRef->refexpr != NULL) { array_source = (ArrayType *) - DatumGetPointer(ExecEvalExpr(arrayRef->refexpr, + DatumGetPointer(ExecEvalExpr(astate->refexpr, econtext, isNull, isDone)); - /* If refexpr yields NULL, result is always NULL, for now anyway */ + + /* + * 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 { - /* - * Null refexpr indicates we are doing an INSERT into an array + * 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 @@ -120,54 +172,81 @@ ExecEvalArrayRef(ArrayRef *arrayRef, array_source = NULL; } - foreach(elt, arrayRef->refupperindexpr) + foreach(elt, astate->refupperindexpr) { if (i >= MAXDIM) - elog(ERROR, "ExecEvalArrayRef: can only handle %d dimensions", - MAXDIM); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions exceeds the maximum allowed, %d", + MAXDIM))); - upper.indx[i++] = DatumGetInt32(ExecEvalExpr((Node *) lfirst(elt), + upper.indx[i++] = DatumGetInt32(ExecEvalExpr((ExprState *) lfirst(elt), econtext, isNull, - &dummy)); - /* If any index expr yields NULL, result is NULL */ + NULL)); + /* If any index expr yields NULL, result is NULL or source array */ if (*isNull) - return (Datum) NULL; + { + if (!isAssignment || array_source == NULL) + return (Datum) NULL; + *isNull = false; + return PointerGetDatum(array_source); + } } - if (arrayRef->reflowerindexpr != NIL) + if (astate->reflowerindexpr != NIL) { - foreach(elt, arrayRef->reflowerindexpr) + foreach(elt, astate->reflowerindexpr) { if (j >= MAXDIM) - elog(ERROR, "ExecEvalArrayRef: can only handle %d dimensions", - MAXDIM); + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions exceeds the maximum allowed, %d", + MAXDIM))); - lower.indx[j++] = DatumGetInt32(ExecEvalExpr((Node *) lfirst(elt), + lower.indx[j++] = DatumGetInt32(ExecEvalExpr((ExprState *) lfirst(elt), econtext, isNull, - &dummy)); - /* If any index expr yields NULL, result is NULL */ + NULL)); + + /* + * If any index expr yields NULL, result is NULL or source + * array + */ if (*isNull) - return (Datum) NULL; + { + if (!isAssignment || array_source == NULL) + return (Datum) NULL; + *isNull = false; + return PointerGetDatum(array_source); + } } + /* this can't happen unless parser messed up */ if (i != j) - elog(ERROR, - "ExecEvalArrayRef: upper and lower indices mismatch"); + elog(ERROR, "upper and lower index lists are not same length"); lIndex = lower.indx; } else lIndex = NULL; - if (arrayRef->refassgnexpr != NULL) + if (isAssignment) { - Datum sourceData = ExecEvalExpr(arrayRef->refassgnexpr, + Datum sourceData = ExecEvalExpr(astate->refassgnexpr, econtext, isNull, - &dummy); - /* For now, can't cope with inserting NULL into an array */ + NULL); + + /* + * For now, can't cope with inserting NULL into an array, so make + * it a no-op per discussion above... + */ if (*isNull) - return (Datum) NULL; + { + if (array_source == NULL) + return (Datum) NULL; + *isNull = false; + return PointerGetDatum(array_source); + } if (array_source == NULL) return sourceData; /* XXX do something else? */ @@ -176,35 +255,38 @@ ExecEvalArrayRef(ArrayRef *arrayRef, resultArray = array_set(array_source, i, upper.indx, sourceData, - arrayRef->refelembyval, - arrayRef->refelemlength, - arrayRef->refattrlength, + astate->refattrlength, + astate->refelemlength, + astate->refelembyval, + astate->refelemalign, isNull); else resultArray = array_set_slice(array_source, i, upper.indx, lower.indx, - (ArrayType *) DatumGetPointer(sourceData), - arrayRef->refelembyval, - arrayRef->refelemlength, - arrayRef->refattrlength, + (ArrayType *) DatumGetPointer(sourceData), + astate->refattrlength, + astate->refelemlength, + astate->refelembyval, + astate->refelemalign, isNull); return PointerGetDatum(resultArray); } if (lIndex == NULL) - return array_ref(array_source, i, - upper.indx, - arrayRef->refelembyval, - arrayRef->refelemlength, - arrayRef->refattrlength, + return array_ref(array_source, i, upper.indx, + astate->refattrlength, + astate->refelemlength, + astate->refelembyval, + astate->refelemalign, isNull); else { resultArray = array_get_slice(array_source, i, upper.indx, lower.indx, - arrayRef->refelembyval, - arrayRef->refelemlength, - arrayRef->refattrlength, + astate->refattrlength, + astate->refelemlength, + astate->refelembyval, + astate->refelemalign, isNull); return PointerGetDatum(resultArray); } @@ -219,10 +301,10 @@ ExecEvalArrayRef(ArrayRef *arrayRef, * ---------------------------------------------------------------- */ static Datum -ExecEvalAggref(Aggref *aggref, ExprContext *econtext, bool *isNull) +ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull) { if (econtext->ecxt_aggvalues == NULL) /* safety check */ - elog(ERROR, "ExecEvalAggref: no aggregates in this expression context"); + elog(ERROR, "no aggregates in this expression context"); *isNull = econtext->ecxt_aggnulls[aggref->aggno]; return econtext->ecxt_aggvalues[aggref->aggno]; @@ -259,8 +341,6 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) AttrNumber attnum; HeapTuple heapTuple; TupleDesc tuple_type; - bool byval; - int16 len; /* * get the slot we want @@ -297,28 +377,32 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) /* * 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 + * 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; - TupleDesc td; HeapTuple tup; - tempSlot = makeNode(TupleTableSlot); - tempSlot->ttc_shouldFree = false; - tempSlot->ttc_descIsNew = true; - tempSlot->ttc_tupleDescriptor = (TupleDesc) NULL; - tempSlot->ttc_buffer = InvalidBuffer; - tempSlot->ttc_whichplan = -1; - + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tempSlot = MakeTupleTableSlot(); tup = heap_copytuple(heapTuple); - td = CreateTupleDescCopy(tuple_type); - - ExecSetSlotDescriptor(tempSlot, td); - ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); + ExecSetSlotDescriptor(tempSlot, tuple_type, false); + MemoryContextSwitchTo(oldContext); return PointerGetDatum(tempSlot); } @@ -328,36 +412,6 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) tuple_type, /* tuple descriptor of tuple */ isNull); /* return: is attribute null? */ - /* - * return null if att is null - */ - if (*isNull) - return (Datum) 0; - - /* - * get length and type information.. ??? what should we do about - * variable length attributes - variable length attributes have their - * length stored in the first 4 bytes of the memory pointed to by the - * returned value.. If we can determine that the type is a variable - * length type, we can do the right thing. -cim 9/15/89 - */ - if (attnum < 0) - { - - /* - * If this is a pseudo-att, we get the type and fake the length. - * There ought to be a routine to return the real lengths, so - * we'll mark this one ... XXX -mao - */ - len = heap_sysattrlen(attnum); /* XXX see -mao above */ - byval = heap_sysattrbyval(attnum); /* XXX see -mao above */ - } - else - { - len = tuple_type->attrs[attnum - 1]->attlen; - byval = tuple_type->attrs[attnum - 1]->attbyval ? true : false; - } - return result; } @@ -367,40 +421,32 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) * 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 replace the param node with the datum - * containing the appropriate information ("sam"). + * so our job is to find and return the appropriate datum ("sam"). * * Q: if we have a parameter ($.foo) without a binding, i.e. * there is no (foo = xxx) in the parameter list info, * is this a fatal error or should this be a "not available" - * (in which case we shoud return a Const node with the - * isnull flag) ? -cim 10/13/89 - * - * Minor modification: Param nodes now have an extra field, - * `paramkind' which specifies the type of parameter - * (see params.h). So while searching the paramList for - * a paramname/value pair, we have also to check for `kind'. - * - * NOTE: The last entry in `paramList' is always an - * entry with kind == PARAM_INVALID. + * (in which case we could return NULL)? -cim 10/13/89 * ---------------------------------------------------------------- */ -Datum +static Datum ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) { - char *thisParameterName; - int thisParameterKind = expression->paramkind; - AttrNumber thisParameterId = expression->paramid; - int matchFound; - ParamListInfo paramList; + int thisParamKind = expression->paramkind; + AttrNumber thisParamId = expression->paramid; - if (thisParameterKind == PARAM_EXEC) + 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; - prm = &(econtext->ecxt_param_exec_vals[thisParameterId]); + 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); @@ -408,102 +454,60 @@ ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) *isNull = prm->isnull; return prm->value; } - - thisParameterName = expression->paramname; - paramList = econtext->ecxt_param_list_info; - - *isNull = false; - - /* - * search the list with the parameter info to find a matching name. An - * entry with an InvalidName denotes the last element in the array. - */ - matchFound = 0; - if (paramList != NULL) + else { - /* - * search for an entry in 'paramList' that matches the - * `expression'. + * 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. */ - while (paramList->kind != PARAM_INVALID && !matchFound) + ParamListInfo paramList = econtext->ecxt_param_list_info; + char *thisParamName = expression->paramname; + bool matchFound = false; + + if (paramList != NULL) { - switch (thisParameterKind) + while (paramList->kind != PARAM_INVALID && !matchFound) { - case PARAM_NAMED: - if (thisParameterKind == paramList->kind && - strcmp(paramList->name, thisParameterName) == 0) - matchFound = 1; - break; - case PARAM_NUM: - if (thisParameterKind == paramList->kind && - paramList->id == thisParameterId) - matchFound = 1; - break; - case PARAM_OLD: - case PARAM_NEW: - if (thisParameterKind == paramList->kind && - paramList->id == thisParameterId) + if (thisParamKind == paramList->kind) + { + switch (thisParamKind) { - matchFound = 1; - - /* - * sanity check - */ - if (strcmp(paramList->name, thisParameterName) != 0) - { - elog(ERROR, - "ExecEvalParam: new/old params with same id & diff names"); - } + 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); } - break; - default: - - /* - * oops! this is not supposed to happen! - */ - elog(ERROR, "ExecEvalParam: invalid paramkind %d", - thisParameterKind); - } - if (!matchFound) - paramList++; - } /* while */ - } /* if */ - - if (!matchFound) - { - - /* - * ooops! we couldn't find this parameter in the parameter list. - * Signal an error - */ - elog(ERROR, "ExecEvalParam: Unknown value for parameter %s", - thisParameterName); - } + } + if (!matchFound) + paramList++; + } /* while */ + } /* if */ - /* - * return the value. - */ - if (paramList->isnull) - { - *isNull = true; - return (Datum) 0; - } + 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))); + } - if (expression->param_tlist != NIL) - { - HeapTuple tup; - Datum value; - List *tlist = expression->param_tlist; - TargetEntry *tle = (TargetEntry *) lfirst(tlist); - TupleTableSlot *slot = (TupleTableSlot *) paramList->value; - - tup = slot->val; - value = ProjectAttribute(slot->ttc_tupleDescriptor, - tle, tup, isNull); - return value; + *isNull = paramList->isnull; + return paramList->value; } - return paramList->value; } @@ -520,13 +524,8 @@ ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull) * 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(). - * - * XXX these two functions are misdeclared: they should be declared to - * return Datum. They are not used anywhere in the backend proper, and - * exist only for use by user-defined functions. Should we change their - * definitions, at risk of breaking user code? */ -char * +Datum GetAttributeByNum(TupleTableSlot *slot, AttrNumber attrno, bool *isNull) @@ -534,18 +533,15 @@ GetAttributeByNum(TupleTableSlot *slot, Datum retval; if (!AttributeNumberIsValid(attrno)) - elog(ERROR, "GetAttributeByNum: Invalid attribute number"); - - if (!AttrNumberIsForUserDefinedAttr(attrno)) - elog(ERROR, "GetAttributeByNum: cannot access system attributes here"); + elog(ERROR, "invalid attribute number %d", attrno); if (isNull == (bool *) NULL) - elog(ERROR, "GetAttributeByNum: a NULL isNull flag was passed"); + elog(ERROR, "a NULL isNull pointer was passed"); if (TupIsNull(slot)) { *isNull = true; - return (char *) NULL; + return (Datum) 0; } retval = heap_getattr(slot->val, @@ -553,11 +549,12 @@ GetAttributeByNum(TupleTableSlot *slot, slot->ttc_tupleDescriptor, isNull); if (*isNull) - return (char *) NULL; - return (char *) retval; + return (Datum) 0; + + return retval; } -char * +Datum GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) { AttrNumber attrno; @@ -567,15 +564,15 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) int i; if (attname == NULL) - elog(ERROR, "GetAttributeByName: Invalid attribute name"); + elog(ERROR, "invalid attribute name"); if (isNull == (bool *) NULL) - elog(ERROR, "GetAttributeByName: a NULL isNull flag was passed"); + elog(ERROR, "a NULL isNull pointer was passed"); if (TupIsNull(slot)) { *isNull = true; - return (char *) NULL; + return (Datum) 0; } tupdesc = slot->ttc_tupleDescriptor; @@ -592,176 +589,183 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull) } if (attrno == InvalidAttrNumber) - elog(ERROR, "GetAttributeByName: attribute %s not found", attname); + elog(ERROR, "attribute \"%s\" does not exist", attname); retval = heap_getattr(slot->val, attrno, tupdesc, isNull); if (*isNull) - return (char *) NULL; - return (char *) retval; + return (Datum) 0; + + return retval; } +/* + * init_fcache - initialize a FuncExprState node during first use + */ +void +init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) +{ + AclResult aclresult; + + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_func_name(foid)); -static void -ExecEvalFuncArgs(FunctionCachePtr fcache, - ExprContext *econtext, + /* Safety check (should never fail, as parser should check sooner) */ + if (length(fcache->args) > FUNC_MAX_ARGS) + elog(ERROR, "too many arguments"); + + /* Set up the primary fmgr lookup information */ + fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); + + /* Initialize additional info */ + fcache->setArgsValid = false; + fcache->func.fn_expr = (Node *) fcache->xprstate.expr; +} + +/* + * Evaluate arguments for a function. + */ +static ExprDoneCond +ExecEvalFuncArgs(FunctionCallInfo fcinfo, List *argList, - FunctionCallInfo fcinfo, - bool *argIsDone) + ExprContext *econtext) { + ExprDoneCond argIsDone; int i; List *arg; + argIsDone = ExprSingleResult; /* default assumption */ + i = 0; foreach(arg, argList) { + ExprDoneCond thisArgIsDone; - /* - * evaluate the expression, in general functions cannot take sets - * as arguments but we make an exception in the case of nested dot - * expressions. We have to watch out for this case here. - */ - fcinfo->arg[i] = ExecEvalExpr((Node *) lfirst(arg), + fcinfo->arg[i] = ExecEvalExpr((ExprState *) lfirst(arg), econtext, &fcinfo->argnull[i], - argIsDone); + &thisArgIsDone); - if (!(*argIsDone)) + if (thisArgIsDone != ExprSingleResult) { - if (i != 0) - elog(ERROR, "functions can only take sets in their first argument"); - fcache->setArg = fcinfo->arg[0]; - fcache->hasSetArg = true; + /* + * 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, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("functions and operators can take at most one set argument"))); + argIsDone = thisArgIsDone; } i++; } + + fcinfo->nargs = i; + + return argIsDone; } /* * ExecMakeFunctionResult + * + * Evaluate the arguments to a function and then the function itself. */ -static Datum -ExecMakeFunctionResult(Node *node, - List *arguments, +Datum +ExecMakeFunctionResult(FuncExprState *fcache, ExprContext *econtext, bool *isNull, - bool *isDone) + ExprDoneCond *isDone) { - FunctionCallInfoData fcinfo; - FunctionCachePtr fcache; - List *ftlist; - bool funcisset; - Datum result; - bool argDone; - - MemSet(&fcinfo, 0, sizeof(fcinfo)); - - /* - * This is kind of ugly, Func nodes now have targetlists so that we - * know when and what to project out from postquel function results. - * ExecMakeFunctionResult becomes a little bit more of a dual personality - * as a result. - */ - if (IsA(node, Func)) - { - fcache = ((Func *) node)->func_fcache; - ftlist = ((Func *) node)->func_tlist; - funcisset = (((Func *) node)->funcid == F_SETEVAL); - } - else - { - fcache = ((Oper *) node)->op_fcache; - ftlist = NIL; - funcisset = false; - } - - fcinfo.flinfo = &fcache->func; - fcinfo.nargs = fcache->nargs; + List *arguments = fcache->args; + Datum result; + FunctionCallInfoData fcinfo; + ReturnSetInfo rsinfo; /* for functions returning sets */ + ExprDoneCond argDone; + bool hasSetArg; + int i; /* * arguments is a list of expressions to evaluate before passing to - * the function manager. We collect the results of evaluating the - * expressions into the FunctionCallInfo struct. Note we assume that - * fcache->nargs is the correct length of the arguments list! + * 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 (fcache->nargs > 0) + if (!fcache->setArgsValid) { - if (fcache->nargs > FUNC_MAX_ARGS) - elog(ERROR, "ExecMakeFunctionResult: too many arguments"); - - /* - * If the setArg in the fcache is set we have an argument - * returning a set of tuples (i.e. a nested dot expression). We - * don't want to evaluate the arguments again until the function - * is done. hasSetArg will always be false until we eval the args - * for the first time. - */ - if (fcache->hasSetArg && fcache->setArg != (Datum) 0) + /* Need to prep callinfo structure */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(fcache->func); + argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext); + if (argDone == ExprEndResult) { - fcinfo.arg[0] = fcache->setArg; - argDone = false; - } - else - ExecEvalFuncArgs(fcache, econtext, arguments, &fcinfo, &argDone); - - if (fcache->hasSetArg && argDone) - { - /* can only get here if input is an empty set. */ + /* input is an empty set, so return an empty set. */ *isNull = true; - *isDone = true; + if (isDone) + *isDone = ExprEndResult; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); return (Datum) 0; } + hasSetArg = (argDone != ExprSingleResult); + } + else + { + /* Copy callinfo from previous evaluation */ + memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo)); + hasSetArg = fcache->setHasSetArg; + /* Reset flag (we may set it again below) */ + fcache->setArgsValid = false; } /* - * If this function is really a set, we have to diddle with things. If - * the function has already been called at least once, then the setArg - * field of the fcache holds the OID of this set in pg_proc. (This is - * not quite legit, since the setArg field is really for functions - * which take sets of tuples as input - set functions take no inputs - * at all. But it's a nice place to stash this value, for now.) - * - * If this is the first call of the set's function, then the call to - * ExecEvalFuncArgs above just returned the OID of the pg_proc tuple - * which defines this set. So replace the existing funcid in the - * funcnode with the set's OID. Also, we want a new fcache which - * points to the right function, so get that, now that we have the - * right OID. Also zero out fcinfo.arg, since the real set doesn't take - * any arguments. + * If function returns set, prepare a resultinfo node for + * communication */ - if (funcisset) + if (fcache->func.fn_retset) { - if (fcache->setArg) - { - ((Func *) node)->funcid = DatumGetObjectId(fcache->setArg); - } - else - { - ((Func *) node)->funcid = DatumGetObjectId(fcinfo.arg[0]); - setFcache(node, DatumGetObjectId(fcinfo.arg[0]), NIL, econtext); - fcache = ((Func *) node)->func_fcache; - fcache->setArg = fcinfo.arg[0]; - } - fcinfo.arg[0] = (Datum) 0; + 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. */ - if (fcache->language == SQLlanguageId) + if (fcache->func.fn_retset || hasSetArg) { - /*-------------------- - * This loop handles the situation where we are iterating through - * all results in a nested dot function (whose argument function - * returns a set of tuples) and the current function finally - * finishes. We need to get the next argument in the set and start - * the function all over again. We might have to do it more than - * once, if the function produces no results for a particular argument. - * This is getting unclean. - *-------------------- + /* + * We need to return a set result. Complain if caller not ready + * to accept one. + */ + if (isDone == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + 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. */ for (;;) { @@ -769,12 +773,10 @@ ExecMakeFunctionResult(Node *node, * If function is strict, and there are any NULL arguments, * skip calling the function (at least for this set of args). */ - bool callit = true; + bool callit = true; - if (fcinfo.flinfo->fn_strict) + if (fcache->func.fn_strict) { - int i; - for (i = 0; i < fcinfo.nargs; i++) { if (fcinfo.argnull[i]) @@ -787,35 +789,53 @@ ExecMakeFunctionResult(Node *node, if (callit) { - result = postquel_function(&fcinfo, fcache, ftlist, isDone); + fcinfo.isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(&fcinfo); *isNull = fcinfo.isnull; + *isDone = rsinfo.isDone; } else { result = (Datum) 0; *isNull = true; - *isDone = true; + *isDone = ExprEndResult; } - if (!*isDone) - break; /* got a result from current argument */ - if (!fcache->hasSetArg) - break; /* input not a set, so done */ - - /* OK, get the next argument... */ - ExecEvalFuncArgs(fcache, econtext, arguments, &fcinfo, &argDone); - - if (argDone) + 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) + { + memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo)); + fcache->setHasSetArg = hasSetArg; + fcache->setArgsValid = true; + } /* - * End of arguments, so reset the setArg flag and say - * "Done" + * Make sure we say we are returning a set, even if the + * function itself doesn't return sets. */ - fcache->setArg = (Datum) 0; - fcache->hasSetArg = false; - *isDone = true; + *isDone = ExprMultipleResult; + break; + } + + /* 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); + + if (argDone != ExprMultipleResult) + { + /* End of argument set, so we're done. */ *isNull = true; + *isDone = ExprEndResult; result = (Datum) 0; break; } @@ -825,39 +845,17 @@ ExecMakeFunctionResult(Node *node, * new argument. */ } - - if (funcisset) - { - - /* - * reset the funcid so that next call to this routine will - * still recognize this func as a set. Note that for now we - * assume that the set function in pg_proc must be a Postquel - * function - the funcid is not reset below for C functions. - */ - ((Func *) node)->funcid = F_SETEVAL; - - /* - * If we're done with the results of this function, get rid of - * its func cache. - */ - if (*isDone) - ((Func *) node)->func_fcache = NULL; - } } else { - /* A non-SQL function cannot return a set, at present. */ - *isDone = true; - /* - * If function is strict, and there are any NULL arguments, - * skip calling the function and return NULL. + * Non-set case: much easier. + * + * If function is strict, and there are any NULL arguments, skip + * calling the function and return NULL. */ - if (fcinfo.flinfo->fn_strict) + if (fcache->func.fn_strict) { - int i; - for (i = 0; i < fcinfo.nargs; i++) { if (fcinfo.argnull[i]) @@ -867,6 +865,7 @@ ExecMakeFunctionResult(Node *node, } } } + fcinfo.isnull = false; result = FunctionCallInvoke(&fcinfo); *isNull = fcinfo.isnull; } @@ -875,126 +874,568 @@ ExecMakeFunctionResult(Node *node, } -/* ---------------------------------------------------------------- - * ExecEvalOper - * ExecEvalFunc +/* + * ExecMakeTableFunctionResult * - * Evaluate the functional result of a list of arguments by calling the - * function manager. - * ---------------------------------------------------------------- - */ - -/* ---------------------------------------------------------------- - * ExecEvalOper - * ---------------------------------------------------------------- + * Evaluate a table function, producing a materialized result in a Tuplestore + * object. (If function returns an empty set, we just return NULL instead.) */ -static Datum -ExecEvalOper(Expr *opClause, ExprContext *econtext, bool *isNull) +Tuplestorestate * +ExecMakeTableFunctionResult(ExprState *funcexpr, + ExprContext *econtext, + TupleDesc expectedDesc, + TupleDesc *returnDesc) { - Oper *op; - List *argList; - FunctionCachePtr fcache; - bool isDone; + Tuplestorestate *tupstore = NULL; + TupleDesc tupdesc = NULL; + Oid funcrettype; + FunctionCallInfoData fcinfo; + ReturnSetInfo rsinfo; + MemoryContext callerContext; + MemoryContext oldcontext; + TupleTableSlot *slot; + bool direct_function_call; + bool first_time = true; + bool returnsTuple = false; /* - * we extract the oid of the function associated with the op and then - * pass the work onto ExecMakeFunctionResult which evaluates the - * arguments and returns the result of calling the function on the - * evaluated arguments. + * 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. */ - op = (Oper *) opClause->oper; - argList = opClause->args; + if (funcexpr && IsA(funcexpr, FuncExprState) && + IsA(funcexpr->expr, FuncExpr)) + { + FuncExprState *fcache = (FuncExprState *) funcexpr; + ExprDoneCond argDone; - /* - * get the fcache from the Oper node. If it is NULL, then initialize - * it - */ - fcache = op->op_fcache; - if (fcache == NULL) + /* + * This path is similar to ExecMakeFunctionResult. + */ + direct_function_call = true; + + /* + * Initialize function cache if first time through + */ + if (fcache->func.fn_oid == InvalidOid) + { + FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; + + init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + } + + /* + * 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? + */ + 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 */ + if (argDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + /* + * If function is strict, and there are any NULL arguments, skip + * calling the function and return NULL (actually an empty set). + */ + if (fcache->func.fn_strict) + { + int i; + + for (i = 0; i < fcinfo.nargs; i++) + { + if (fcinfo.argnull[i]) + { + *returnDesc = NULL; + return NULL; + } + } + } + } + else { - setFcache((Node *) op, op->opid, argList, econtext); - fcache = op->op_fcache; + /* Treat funcexpr as a generic expression */ + direct_function_call = false; } + funcrettype = exprType((Node *) funcexpr->expr); + /* - * call ExecMakeFunctionResult() with a dummy isDone that we ignore. - * We don't have operator whose arguments are sets. + * 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. */ - return ExecMakeFunctionResult((Node *) op, argList, econtext, - isNull, &isDone); -} - -/* ---------------------------------------------------------------- - * ExecEvalFunc - * ---------------------------------------------------------------- - */ - -static Datum -ExecEvalFunc(Expr *funcClause, - ExprContext *econtext, - bool *isNull, - bool *isDone) -{ - Func *func; - List *argList; - FunctionCachePtr fcache; + 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; /* - * we extract the oid of the function associated with the func node and - * then pass the work onto ExecMakeFunctionResult which evaluates the - * arguments and returns the result of calling the function on the - * evaluated arguments. - * - * this is nearly identical to the ExecEvalOper code. + * Switch to short-lived context for calling the function or expression. */ - func = (Func *) funcClause->oper; - argList = funcClause->args; + callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* - * get the fcache from the Func node. If it is NULL, then initialize - * it + * Loop to handle the ValuePerCall protocol (which is also the same + * behavior needed in the generic ExecEvalExpr path). */ - fcache = func->func_fcache; - if (fcache == NULL) + for (;;) { - setFcache((Node *) func, func->funcid, argList, econtext); - fcache = func->func_fcache; - } + Datum result; + HeapTuple tuple; - return ExecMakeFunctionResult((Node *) func, argList, econtext, - isNull, isDone); -} + /* + * 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); -/* ---------------------------------------------------------------- - * ExecEvalNot - * ExecEvalOr - * ExecEvalAnd - * - * Evaluate boolean expressions. Evaluation of 'or' is - * short-circuited when the first true (or null) value is found. - * - * The query planner reformulates clause expressions in the - * qualification to conjunctive normal form. If we ever get - * an AND to evaluate, we can be sure that it's not a top-level - * clause in the qualification, but appears lower (as a function - * argument, for example), or in the target list. Not that you - * need to know this, mind you... - * ---------------------------------------------------------------- + /* Call the function or expression one time */ + if (direct_function_call) + { + fcinfo.isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(&fcinfo); + } + else + { + result = ExecEvalExpr(funcexpr, econtext, + &fcinfo.isnull, &rsinfo.isDone); + } + + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) + { + /* + * 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 + */ + if (first_time) + { + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + if (funcrettype == RECORDOID || + get_typtype(funcrettype) == 'c') + { + /* + * Composite type, so function should have returned a + * TupleTableSlot; use its descriptor + */ + slot = (TupleTableSlot *) DatumGetPointer(result); + if (fcinfo.isnull || !slot) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("function returning tuple cannot return NULL"))); + if (!IsA(slot, TupleTableSlot) || + !slot->ttc_tupleDescriptor) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function returning tuple did not return a valid tuple slot"))); + tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor); + returnsTuple = true; + } + else + { + /* + * Scalar type, so make a single-column descriptor + */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + "column", + funcrettype, + -1, + 0, + false); + } + tupstore = tuplestore_begin_heap(true, false, SortMem); + MemoryContextSwitchTo(oldcontext); + rsinfo.setResult = tupstore; + rsinfo.setDesc = tupdesc; + } + + /* + * Store current resultset item. + */ + 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 tuple cannot return NULL"))); + tuple = slot->val; + } + else + { + char nullflag; + + nullflag = fcinfo.isnull ? 'n' : ' '; + tuple = heap_formtuple(tupdesc, &result, &nullflag); + } + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tuplestore_puttuple(tupstore, tuple); + MemoryContextSwitchTo(oldcontext); + + /* + * Are we done? + */ + if (rsinfo.isDone != ExprMultipleResult) + break; + } + else if (rsinfo.returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (!first_time || rsinfo.isDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("table-function protocol for materialize mode was not followed"))); + /* Done evaluating the set result */ + break; + } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo.returnMode))); + + first_time = false; + } + + MemoryContextSwitchTo(callerContext); + + /* The returned pointers are those in rsinfo */ + *returnDesc = rsinfo.setDesc; + return rsinfo.setResult; +} + + +/* ---------------------------------------------------------------- + * ExecEvalFunc + * ExecEvalOper + * + * Evaluate the functional result of a list of arguments by calling the + * function manager. + * ---------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------- + * ExecEvalFunc + * ---------------------------------------------------------------- */ static Datum -ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull) +ExecEvalFunc(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) { - Node *clause; - Datum expr_value; - bool isDone; + /* + * Initialize function cache if first time through + */ + if (fcache->func.fn_oid == InvalidOid) + { + FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - clause = lfirst(notclause->args); + init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + } + + return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalOper + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalOper(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + /* + * Initialize function cache if first time through + */ + if (fcache->func.fn_oid == InvalidOid) + { + OpExpr *op = (OpExpr *) fcache->xprstate.expr; + + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + } + + return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalDistinct + * + * IS DISTINCT FROM must evaluate arguments to determine whether + * they are NULL; if either is NULL then the result is already + * known. If neither is NULL, then proceed to evaluate the + * function. Note that this is *always* derived from the equals + * operator, but since we need special processing of the arguments + * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalDistinct(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull) +{ + Datum result; + FunctionCallInfoData fcinfo; + ExprDoneCond argDone; + List *argList; + + /* + * Initialize function cache if first time through + */ + if (fcache->func.fn_oid == InvalidOid) + { + DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr; + + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + Assert(!fcache->func.fn_retset); + } + + /* + * extract info from fcache + */ + argList = fcache->args; + + /* Need to prep callinfo structure */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(fcache->func); + argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); + if (argDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("IS DISTINCT FROM does not support set arguments"))); + Assert(fcinfo.nargs == 2); + + if (fcinfo.argnull[0] && fcinfo.argnull[1]) + { + /* Both NULL? Then is not distinct... */ + result = BoolGetDatum(FALSE); + } + else if (fcinfo.argnull[0] || fcinfo.argnull[1]) + { + /* Only one is NULL? Then is distinct... */ + result = BoolGetDatum(TRUE); + } + else + { + fcinfo.isnull = false; + result = FunctionCallInvoke(&fcinfo); + *isNull = fcinfo.isnull; + /* Must invert result of "=" */ + result = BoolGetDatum(!DatumGetBool(result)); + } + + return result; +} + +/* + * ExecEvalScalarArrayOp + * + * Evaluate "scalar op ANY/ALL (array)". The operator always yields boolean, + * and we combine the results across all array elements using OR and AND + * (for ANY and ALL respectively). Of course we short-circuit as soon as + * the result is known. + */ +static Datum +ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, + ExprContext *econtext, bool *isNull) +{ + ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr; + bool useOr = opexpr->useOr; + ArrayType *arr; + int nitems; + Datum result; + bool resultnull; + FunctionCallInfoData fcinfo; + ExprDoneCond argDone; + int i; + int16 typlen; + bool typbyval; + char typalign; + char *s; /* - * We don't iterate over sets in the quals, so pass in an isDone flag, - * but ignore it. + * Initialize function cache if first time through */ - expr_value = ExecEvalExpr(clause, econtext, isNull, &isDone); + if (sstate->fxprstate.func.fn_oid == InvalidOid) + { + init_fcache(opexpr->opfuncid, &sstate->fxprstate, + econtext->ecxt_per_query_memory); + Assert(!sstate->fxprstate.func.fn_retset); + } + + /* Need to prep callinfo structure */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(sstate->fxprstate.func); + 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"))); + 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 (fcinfo.argnull[1]) + { + *isNull = true; + return (Datum) 0; + } + /* Else okay to fetch and detoast the array */ + arr = DatumGetArrayTypeP(fcinfo.arg[1]); + + /* + * 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. + */ + 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 (fcinfo.argnull[0] && sstate->fxprstate.func.fn_strict) + { + *isNull = true; + return (Datum) 0; + } + + /* + * 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)) + { + get_typlenbyvalalign(ARR_ELEMTYPE(arr), + &sstate->typlen, + &sstate->typbyval, + &sstate->typalign); + sstate->element_type = ARR_ELEMTYPE(arr); + } + typlen = sstate->typlen; + typbyval = sstate->typbyval; + typalign = sstate->typalign; + + result = BoolGetDatum(!useOr); + resultnull = false; + + /* Loop over the array elements */ + s = (char *) ARR_DATA_PTR(arr); + 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); + + /* Call comparison function */ + fcinfo.arg[1] = elt; + fcinfo.argnull[1] = false; + fcinfo.isnull = false; + thisresult = FunctionCallInvoke(&fcinfo); + + /* Combine results per OR or AND semantics */ + if (fcinfo.isnull) + resultnull = true; + else if (useOr) + { + if (DatumGetBool(thisresult)) + { + result = BoolGetDatum(true); + resultnull = false; + break; /* needn't look at any more elements */ + } + } + else + { + if (!DatumGetBool(thisresult)) + { + result = BoolGetDatum(false); + resultnull = false; + break; /* needn't look at any more elements */ + } + } + } + + *isNull = resultnull; + return result; +} + +/* ---------------------------------------------------------------- + * ExecEvalNot + * ExecEvalOr + * ExecEvalAnd + * + * Evaluate boolean expressions, with appropriate short-circuiting. + * + * The query planner reformulates clause expressions in the + * qualification to conjunctive normal form. If we ever get + * an AND to evaluate, we can be sure that it's not a top-level + * clause in the qualification, but appears lower (as a function + * argument, for example), or in the target list. Not that you + * need to know this, mind you... + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, bool *isNull) +{ + ExprState *clause; + Datum expr_value; + + clause = lfirst(notclause->args); + + expr_value = ExecEvalExpr(clause, econtext, isNull, NULL); /* * if the expression evaluates to null, then we just cascade the null @@ -1007,7 +1448,7 @@ ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull) * evaluation of 'not' is simple.. expr is false, then return 'true' * and vice versa. */ - return BoolGetDatum(! DatumGetBool(expr_value)); + return BoolGetDatum(!DatumGetBool(expr_value)); } /* ---------------------------------------------------------------- @@ -1015,11 +1456,10 @@ ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull) +ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, bool *isNull) { List *clauses; List *clause; - bool isDone; bool AnyNull; Datum clause_value; @@ -1042,15 +1482,8 @@ ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull) */ foreach(clause, clauses) { - - /* - * We don't iterate over sets in the quals, so pass in an isDone - * flag, but ignore it. - */ - clause_value = ExecEvalExpr((Node *) lfirst(clause), - econtext, - isNull, - &isDone); + clause_value = ExecEvalExpr((ExprState *) lfirst(clause), + econtext, isNull, NULL); /* * if we have a non-null true result, then return it. @@ -1071,11 +1504,10 @@ ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull) +ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, bool *isNull) { List *clauses; List *clause; - bool isDone; bool AnyNull; Datum clause_value; @@ -1092,22 +1524,15 @@ ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull) */ foreach(clause, clauses) { - - /* - * We don't iterate over sets in the quals, so pass in an isDone - * flag, but ignore it. - */ - clause_value = ExecEvalExpr((Node *) lfirst(clause), - econtext, - isNull, - &isDone); + clause_value = ExecEvalExpr((ExprState *) lfirst(clause), + econtext, isNull, NULL); /* * if we have a non-null false result, then return it. */ if (*isNull) AnyNull = true; /* remember we got a null */ - else if (! DatumGetBool(clause_value)) + else if (!DatumGetBool(clause_value)) return clause_value; } @@ -1116,6 +1541,7 @@ ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull) return BoolGetDatum(!AnyNull); } + /* ---------------------------------------------------------------- * ExecEvalCase * @@ -1126,12 +1552,12 @@ ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull) * ---------------------------------------------------------------- */ static Datum -ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull) +ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { List *clauses; List *clause; Datum clause_value; - bool isDone; clauses = caseExpr->args; @@ -1142,16 +1568,12 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull) */ foreach(clause, clauses) { - CaseWhen *wclause = lfirst(clause); + CaseWhenState *wclause = lfirst(clause); - /* - * We don't iterate over sets in the quals, so pass in an isDone - * flag, but ignore it. - */ clause_value = ExecEvalExpr(wclause->expr, econtext, isNull, - &isDone); + NULL); /* * if we have a true test, then we return the result, since the @@ -1163,7 +1585,7 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull) return ExecEvalExpr(wclause->result, econtext, isNull, - &isDone); + isDone); } } @@ -1172,40 +1594,565 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull) return ExecEvalExpr(caseExpr->defresult, econtext, isNull, - &isDone); + isDone); } *isNull = true; return (Datum) 0; } +/* ---------------------------------------------------------------- + * 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) +{ + ArrayExpr *arrayExpr = (ArrayExpr *) astate->xprstate.expr; + ArrayType *result; + List *element; + Oid element_type = arrayExpr->element_typeid; + int ndims = arrayExpr->ndims; + int dims[MAXDIM]; + int lbs[MAXDIM]; + + if (ndims == 1) + { + int nelems; + Datum *dvalues; + int i = 0; + + nelems = length(astate->elements); + + /* Shouldn't happen here, but if length is 0, return NULL */ + if (nelems == 0) + { + *isNull = true; + return (Datum) 0; + } + + dvalues = (Datum *) palloc(nelems * sizeof(Datum)); + + /* 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; + } + } + + /* setup for 1-D array of the given length */ + dims[0] = nelems; + lbs[0] = 1; + + result = construct_md_array(dvalues, ndims, dims, lbs, + element_type, + astate->elemlength, + astate->elembyval, + astate->elemalign); + } + else + { + char *dat = NULL; + Size ndatabytes = 0; + int nbytes; + int outer_nelems = length(astate->elements); + int elem_ndims = 0; + int *elem_dims = NULL; + int *elem_lbs = NULL; + bool firstone = true; + int i; + + if (ndims <= 0 || ndims > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions exceeds the maximum allowed, %d", + MAXDIM))); + + /* loop through and get data area from each element */ + foreach(element, astate->elements) + { + ExprState *e = (ExprState *) lfirst(element); + bool eisnull; + Datum arraydatum; + ArrayType *array; + int elem_ndatabytes; + + arraydatum = ExecEvalExpr(e, econtext, &eisnull, NULL); + if (eisnull) + { + *isNull = true; + return (Datum) 0; + } + + array = DatumGetArrayTypeP(arraydatum); + + if (firstone) + { + /* Get sub-array details from first member */ + elem_ndims = ARR_NDIM(array); + elem_dims = (int *) palloc(elem_ndims * sizeof(int)); + memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)); + elem_lbs = (int *) palloc(elem_ndims * sizeof(int)); + memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int)); + firstone = false; + } + else + { + /* Check other sub-arrays are compatible */ + if (elem_ndims != ARR_NDIM(array) || + 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"))); + } + + 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); + + memcpy(dat + (ndatabytes - elem_ndatabytes), + ARR_DATA_PTR(array), + elem_ndatabytes); + } + + /* 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]; + } + + nbytes = ndatabytes + ARR_OVERHEAD(ndims); + result = (ArrayType *) palloc(nbytes); + + 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); + + if (dat != NULL) + pfree(dat); + } + + return PointerGetDatum(result); +} + +/* ---------------------------------------------------------------- + * ExecEvalCoalesce + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, + bool *isNull) +{ + List *arg; + + /* 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; +} + +/* ---------------------------------------------------------------- + * ExecEvalNullIf + * + * Note that this is *always* derived from the equals operator, + * but since we need special processing of the arguments + * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalNullIf(FuncExprState *fcache, ExprContext *econtext, + bool *isNull) +{ + Datum result; + FunctionCallInfoData fcinfo; + ExprDoneCond argDone; + List *argList; + + /* + * Initialize function cache if first time through + */ + if (fcache->func.fn_oid == InvalidOid) + { + NullIfExpr *op = (NullIfExpr *) fcache->xprstate.expr; + + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + Assert(!fcache->func.fn_retset); + } + + /* + * extract info from fcache + */ + argList = fcache->args; + + /* Need to prep callinfo structure */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(fcache->func); + argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); + if (argDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("NULLIF does not support set arguments"))); + Assert(fcinfo.nargs == 2); + + /* if either argument is NULL they can't be equal */ + if (!fcinfo.argnull[0] && !fcinfo.argnull[1]) + { + fcinfo.isnull = false; + result = FunctionCallInvoke(&fcinfo); + /* if the arguments are equal return null */ + if (!fcinfo.isnull && DatumGetBool(result)) + { + *isNull = true; + return (Datum) 0; + } + } + + /* else return first argument */ + *isNull = fcinfo.argnull[0]; + return fcinfo.arg[0]; +} + +/* ---------------------------------------------------------------- + * ExecEvalNullTest + * + * Evaluate a NullTest node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalNullTest(GenericExprState *nstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + NullTest *ntest = (NullTest *) nstate->xprstate.expr; + Datum result; + + result = ExecEvalExpr(nstate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return result; /* nothing to check */ + + 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 */ + } +} + +/* ---------------------------------------------------------------- + * ExecEvalBooleanTest + * + * Evaluate a BooleanTest node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalBooleanTest(GenericExprState *bstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + BooleanTest *btest = (BooleanTest *) bstate->xprstate.expr; + Datum result; + + result = ExecEvalExpr(bstate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return result; /* nothing to check */ + + switch (btest->booltesttype) + { + case IS_TRUE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else if (DatumGetBool(result)) + return BoolGetDatum(true); + else + return BoolGetDatum(false); + case IS_NOT_TRUE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else if (DatumGetBool(result)) + return BoolGetDatum(false); + else + return BoolGetDatum(true); + case IS_FALSE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else if (DatumGetBool(result)) + return BoolGetDatum(false); + else + return BoolGetDatum(true); + case IS_NOT_FALSE: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else if (DatumGetBool(result)) + return BoolGetDatum(true); + else + return BoolGetDatum(false); + case IS_UNKNOWN: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(true); + } + else + return BoolGetDatum(false); + case IS_NOT_UNKNOWN: + if (*isNull) + { + *isNull = false; + return BoolGetDatum(false); + } + else + return BoolGetDatum(true); + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) btest->booltesttype); + return (Datum) 0; /* keep compiler quiet */ + } +} + +/* + * ExecEvalCoerceToDomain + * + * Test the provided data against the domain constraint(s). If the data + * passes the constraint specifications, pass it through (return the + * datum) otherwise throw an error. + */ +static Datum +ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr; + Datum result; + List *l; + + result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); + + if (isDone && *isDone == ExprEndResult) + return result; /* nothing to check */ + + foreach(l, cstate->constraints) + { + DomainConstraintState *con = (DomainConstraintState *) lfirst(l); + + switch (con->constrainttype) + { + case DOM_CONSTRAINT_NOTNULL: + if (*isNull) + ereport(ERROR, + (errcode(ERRCODE_NOT_NULL_VIOLATION), + errmsg("domain %s does not allow NULL values", + format_type_be(ctest->resulttype)))); + break; + case DOM_CONSTRAINT_CHECK: + { + Datum conResult; + bool conIsNull; + Datum save_datum; + bool save_isNull; + + /* + * 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. + */ + save_datum = econtext->domainValue_datum; + save_isNull = econtext->domainValue_isNull; + + econtext->domainValue_datum = result; + econtext->domainValue_isNull = *isNull; + + conResult = ExecEvalExpr(con->check_expr, + econtext, &conIsNull, NULL); + + if (!conIsNull && + !DatumGetBool(conResult)) + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("value for domain %s violates CHECK constraint \"%s\"", + format_type_be(ctest->resulttype), + con->name))); + econtext->domainValue_datum = save_datum; + econtext->domainValue_isNull = save_isNull; + + break; + } + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->constrainttype); + break; + } + } + + /* If all has gone well (constraints did not fail) return the datum */ + return result; +} + +/* + * ExecEvalCoerceToDomainValue + * + * Return the value stored by CoerceToDomain. + */ +static Datum +ExecEvalCoerceToDomainValue(CoerceToDomainValue *conVal, + ExprContext *econtext, bool *isNull) +{ + *isNull = econtext->domainValue_isNull; + return econtext->domainValue_datum; +} + +/* ---------------------------------------------------------------- + * ExecEvalFieldSelect + * + * Evaluate a FieldSelect node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalFieldSelect(GenericExprState *fstate, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr; + Datum result; + TupleTableSlot *resSlot; + + result = ExecEvalExpr(fstate->arg, econtext, isNull, isDone); + + /* this test covers the isDone exception too: */ + if (*isNull) + return result; + + resSlot = (TupleTableSlot *) DatumGetPointer(result); + Assert(resSlot != NULL && IsA(resSlot, TupleTableSlot)); + result = heap_getattr(resSlot->val, + fselect->fieldnum, + resSlot->ttc_tupleDescriptor, + isNull); + return result; +} + /* ---------------------------------------------------------------- * ExecEvalExpr * * Recursively evaluate a targetlist or qualification expression. * - * 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(). + * 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 * - * This routine is an inner loop routine and must be as fast - * as possible. + * 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(). + * + * This routine is an inner loop routine and must be as fast as possible. * ---------------------------------------------------------------- */ Datum -ExecEvalExpr(Node *expression, +ExecEvalExpr(ExprState *expression, ExprContext *econtext, bool *isNull, - bool *isDone) + ExprDoneCond *isDone) { Datum retDatum; + Expr *expr; /* Set default values for result flags: non-null, not a set result */ *isNull = false; - *isDone = true; + if (isDone) + *isDone = ExprSingleResult; /* Is this still necessary? Doubtful... */ if (expression == NULL) @@ -1218,85 +2165,137 @@ ExecEvalExpr(Node *expression, * here we dispatch the work to the appropriate type of function given * the type of our expression. */ - switch (nodeTag(expression)) + expr = expression->expr; + switch (nodeTag(expr)) { case T_Var: - retDatum = ExecEvalVar((Var *) expression, econtext, isNull); + retDatum = ExecEvalVar((Var *) expr, econtext, isNull); break; case T_Const: { - Const *con = (Const *) expression; + Const *con = (Const *) expr; retDatum = con->constvalue; *isNull = con->constisnull; break; } case T_Param: - retDatum = ExecEvalParam((Param *) expression, econtext, isNull); - break; - case T_Iter: - retDatum = ExecEvalIter((Iter *) expression, - econtext, - isNull, - isDone); + retDatum = ExecEvalParam((Param *) expr, econtext, isNull); break; case T_Aggref: - retDatum = ExecEvalAggref((Aggref *) expression, econtext, isNull); + retDatum = ExecEvalAggref((AggrefExprState *) expression, + econtext, + isNull); break; case T_ArrayRef: - retDatum = ExecEvalArrayRef((ArrayRef *) expression, + retDatum = ExecEvalArrayRef((ArrayRefExprState *) expression, econtext, isNull, isDone); break; - case T_Expr: + 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: { - Expr *expr = (Expr *) expression; + BoolExprState *state = (BoolExprState *) expression; - switch (expr->opType) + switch (((BoolExpr *) expr)->boolop) { - case OP_EXPR: - retDatum = ExecEvalOper(expr, econtext, isNull); - break; - case FUNC_EXPR: - retDatum = ExecEvalFunc(expr, econtext, - isNull, isDone); + case AND_EXPR: + retDatum = ExecEvalAnd(state, econtext, isNull); break; case OR_EXPR: - retDatum = ExecEvalOr(expr, econtext, isNull); - break; - case AND_EXPR: - retDatum = ExecEvalAnd(expr, econtext, isNull); + retDatum = ExecEvalOr(state, econtext, isNull); break; case NOT_EXPR: - retDatum = ExecEvalNot(expr, econtext, isNull); - break; - case SUBPLAN_EXPR: - retDatum = ExecSubPlan((SubPlan *) expr->oper, - expr->args, econtext, - isNull); + retDatum = ExecEvalNot(state, econtext, isNull); break; default: - elog(ERROR, "ExecEvalExpr: unknown expression type %d", - expr->opType); + 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(((RelabelType *) expression)->arg, + retDatum = ExecEvalExpr(((GenericExprState *) expression)->arg, econtext, isNull, isDone); break; case T_CaseExpr: - retDatum = ExecEvalCase((CaseExpr *) expression, econtext, isNull); + 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, "ExecEvalExpr: unknown expression type %d", - nodeTag(expression)); + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(expression)); retDatum = 0; /* keep compiler quiet */ break; } @@ -1309,10 +2308,10 @@ ExecEvalExpr(Node *expression, * Same as above, but get into the right allocation context explicitly. */ Datum -ExecEvalExprSwitchContext(Node *expression, +ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext, bool *isNull, - bool *isDone) + ExprDoneCond *isDone) { Datum retDatum; MemoryContext oldContext; @@ -1324,6 +2323,408 @@ ExecEvalExprSwitchContext(Node *expression, } +/* + * ExecInitExpr: prepare an expression tree for execution + * + * This function builds and returns an ExprState tree paralleling the given + * Expr node tree. The ExprState tree can then be handed to ExecEvalExpr + * for execution. Because the Expr tree itself is read-only as far as + * ExecInitExpr and ExecEvalExpr are concerned, several different executions + * of the same plan tree can occur concurrently. + * + * This must be called in a memory context that will last as long as repeated + * 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.) + * + * Note: there is no ExecEndExpr function; we assume that any resource + * cleanup needed will be handled by just releasing the memory context + * in which the state tree is built. Functions that require additional + * cleanup work can register a shutdown callback in the ExprContext. + * + * 'node' is the root of the expression tree to examine + * 'parent' is the PlanState node that owns the expression. + * + * 'parent' may be NULL if we are preparing an expression that is not + * associated with a plan tree. (If so, it can't have aggs or subplans.) + * This case should usually come through ExecPrepareExpr, not directly here. + */ +ExprState * +ExecInitExpr(Expr *node, PlanState *parent) +{ + ExprState *state; + + if (node == NULL) + return NULL; + switch (nodeTag(node)) + { + case T_Var: + case T_Const: + case T_Param: + case T_CoerceToDomainValue: + /* No special setup needed for these node types */ + state = (ExprState *) makeNode(ExprState); + break; + case T_Aggref: + { + Aggref *aggref = (Aggref *) node; + AggrefExprState *astate = makeNode(AggrefExprState); + + if (parent && IsA(parent, AggState)) + { + AggState *aggstate = (AggState *) parent; + int naggs; + + aggstate->aggs = lcons(astate, aggstate->aggs); + naggs = ++aggstate->numaggs; + + astate->target = ExecInitExpr(aggref->target, parent); + + /* + * Complain if the aggregate's argument contains any + * aggregates; nested agg functions are semantically + * 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"))); + } + else + { + /* planner messed up */ + elog(ERROR, "aggref found in non-Agg plan node"); + } + state = (ExprState *) astate; + } + break; + case T_ArrayRef: + { + ArrayRef *aref = (ArrayRef *) node; + ArrayRefExprState *astate = makeNode(ArrayRefExprState); + + astate->refupperindexpr = (List *) + ExecInitExpr((Expr *) aref->refupperindexpr, parent); + astate->reflowerindexpr = (List *) + ExecInitExpr((Expr *) aref->reflowerindexpr, parent); + astate->refexpr = ExecInitExpr(aref->refexpr, parent); + astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr, + parent); + /* do one-time catalog lookups for type info */ + astate->refattrlength = get_typlen(aref->refarraytype); + get_typlenbyvalalign(aref->refelemtype, + &astate->refelemlength, + &astate->refelembyval, + &astate->refelemalign); + state = (ExprState *) astate; + } + break; + case T_FuncExpr: + { + FuncExpr *funcexpr = (FuncExpr *) node; + FuncExprState *fstate = makeNode(FuncExprState); + + fstate->args = (List *) + ExecInitExpr((Expr *) funcexpr->args, parent); + fstate->func.fn_oid = InvalidOid; /* not initialized */ + state = (ExprState *) fstate; + } + break; + case T_OpExpr: + { + OpExpr *opexpr = (OpExpr *) node; + FuncExprState *fstate = makeNode(FuncExprState); + + fstate->args = (List *) + ExecInitExpr((Expr *) opexpr->args, parent); + fstate->func.fn_oid = InvalidOid; /* not initialized */ + state = (ExprState *) fstate; + } + break; + case T_DistinctExpr: + { + DistinctExpr *distinctexpr = (DistinctExpr *) node; + FuncExprState *fstate = makeNode(FuncExprState); + + fstate->args = (List *) + ExecInitExpr((Expr *) distinctexpr->args, parent); + fstate->func.fn_oid = InvalidOid; /* not initialized */ + state = (ExprState *) fstate; + } + break; + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; + ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState); + + sstate->fxprstate.args = (List *) + ExecInitExpr((Expr *) opexpr->args, parent); + sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */ + sstate->element_type = InvalidOid; /* ditto */ + state = (ExprState *) sstate; + } + break; + case T_BoolExpr: + { + BoolExpr *boolexpr = (BoolExpr *) node; + BoolExprState *bstate = makeNode(BoolExprState); + + bstate->args = (List *) + ExecInitExpr((Expr *) boolexpr->args, parent); + state = (ExprState *) bstate; + } + break; + case T_SubPlan: + { + /* Keep this in sync with ExecInitExprInitPlan, below */ + SubPlan *subplan = (SubPlan *) node; + SubPlanState *sstate = makeNode(SubPlanState); + + 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->exprs = (List *) + ExecInitExpr((Expr *) subplan->exprs, parent); + sstate->args = (List *) + ExecInitExpr((Expr *) subplan->args, parent); + + state = (ExprState *) sstate; + } + break; + case T_FieldSelect: + { + FieldSelect *fselect = (FieldSelect *) node; + GenericExprState *gstate = makeNode(GenericExprState); + + gstate->arg = ExecInitExpr(fselect->arg, parent); + state = (ExprState *) gstate; + } + break; + case T_RelabelType: + { + RelabelType *relabel = (RelabelType *) node; + GenericExprState *gstate = makeNode(GenericExprState); + + gstate->arg = ExecInitExpr(relabel->arg, parent); + state = (ExprState *) gstate; + } + break; + case T_CaseExpr: + { + CaseExpr *caseexpr = (CaseExpr *) node; + CaseExprState *cstate = makeNode(CaseExprState); + FastList outlist; + List *inlist; + + FastListInit(&outlist); + foreach(inlist, caseexpr->args) + { + CaseWhen *when = (CaseWhen *) lfirst(inlist); + CaseWhenState *wstate = makeNode(CaseWhenState); + + Assert(IsA(when, CaseWhen)); + wstate->xprstate.expr = (Expr *) when; + wstate->expr = ExecInitExpr(when->expr, parent); + wstate->result = ExecInitExpr(when->result, parent); + FastAppend(&outlist, wstate); + } + cstate->args = FastListValue(&outlist); + /* caseexpr->arg should be null by now */ + Assert(caseexpr->arg == NULL); + cstate->defresult = ExecInitExpr(caseexpr->defresult, parent); + state = (ExprState *) cstate; + } + break; + case T_ArrayExpr: + { + ArrayExpr *arrayexpr = (ArrayExpr *) node; + ArrayExprState *astate = makeNode(ArrayExprState); + FastList outlist; + List *inlist; + + FastListInit(&outlist); + foreach(inlist, arrayexpr->elements) + { + Expr *e = (Expr *) lfirst(inlist); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + FastAppend(&outlist, estate); + } + astate->elements = FastListValue(&outlist); + /* do one-time catalog lookup for type info */ + get_typlenbyvalalign(arrayexpr->element_typeid, + &astate->elemlength, + &astate->elembyval, + &astate->elemalign); + state = (ExprState *) astate; + } + break; + case T_CoalesceExpr: + { + CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; + CoalesceExprState *cstate = makeNode(CoalesceExprState); + FastList outlist; + List *inlist; + + FastListInit(&outlist); + foreach(inlist, coalesceexpr->args) + { + Expr *e = (Expr *) lfirst(inlist); + ExprState *estate; + + estate = ExecInitExpr(e, parent); + FastAppend(&outlist, estate); + } + cstate->args = FastListValue(&outlist); + state = (ExprState *) cstate; + } + break; + case T_NullIfExpr: + { + NullIfExpr *nullifexpr = (NullIfExpr *) node; + FuncExprState *fstate = makeNode(FuncExprState); + + fstate->args = (List *) + ExecInitExpr((Expr *) nullifexpr->args, parent); + fstate->func.fn_oid = InvalidOid; /* not initialized */ + state = (ExprState *) fstate; + } + break; + case T_NullTest: + { + NullTest *ntest = (NullTest *) node; + GenericExprState *gstate = makeNode(GenericExprState); + + gstate->arg = ExecInitExpr(ntest->arg, parent); + state = (ExprState *) gstate; + } + break; + case T_BooleanTest: + { + BooleanTest *btest = (BooleanTest *) node; + GenericExprState *gstate = makeNode(GenericExprState); + + gstate->arg = ExecInitExpr(btest->arg, parent); + state = (ExprState *) gstate; + } + break; + case T_CoerceToDomain: + { + CoerceToDomain *ctest = (CoerceToDomain *) node; + CoerceToDomainState *cstate = makeNode(CoerceToDomainState); + + cstate->arg = ExecInitExpr(ctest->arg, parent); + cstate->constraints = GetDomainConstraints(ctest->resulttype); + state = (ExprState *) cstate; + } + break; + case T_TargetEntry: + { + TargetEntry *tle = (TargetEntry *) node; + GenericExprState *gstate = makeNode(GenericExprState); + + gstate->arg = ExecInitExpr(tle->expr, parent); + state = (ExprState *) gstate; + } + break; + case T_List: + { + FastList outlist; + List *inlist; + + FastListInit(&outlist); + foreach(inlist, (List *) node) + { + FastAppend(&outlist, + ExecInitExpr((Expr *) lfirst(inlist), + parent)); + } + /* Don't fall through to the "common" code below */ + return (ExprState *) FastListValue(&outlist); + } + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(node)); + state = NULL; /* keep compiler quiet */ + break; + } + + /* Common code for all state-node types */ + state->expr = node; + + 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.) + */ +ExprState * +ExecPrepareExpr(Expr *node, EState *estate) +{ + ExprState *result; + MemoryContext oldcontext; + + fix_opfuncids((Node *) node); + + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + result = ExecInitExpr(node, NULL); + + MemoryContextSwitchTo(oldcontext); + + return result; +} + + /* ---------------------------------------------------------------- * ExecQual / ExecTargetList / ExecProject * ---------------------------------------------------------------- @@ -1394,30 +2795,25 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull) foreach(qlist, qual) { - Node *clause = (Node *) lfirst(qlist); + ExprState *clause = (ExprState *) lfirst(qlist); Datum expr_value; bool isNull; - bool isDone; - /* - * pass isDone, but ignore it. We don't iterate over multiple - * returns in the qualifications. - */ - expr_value = ExecEvalExpr(clause, econtext, &isNull, &isDone); + expr_value = ExecEvalExpr(clause, econtext, &isNull, NULL); if (isNull) { if (resultForNull == false) { - result = false; /* treat NULL as FALSE */ + result = false; /* treat NULL as FALSE */ break; } } else { - if (! DatumGetBool(expr_value)) + if (!DatumGetBool(expr_value)) { - result = false; /* definitely FALSE */ + result = false; /* definitely FALSE */ break; } } @@ -1428,22 +2824,32 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull) return result; } +/* + * Number of items in a tlist (including any resjunk items!) + */ int ExecTargetListLength(List *targetlist) { - int len; + /* This used to be more complex, but fjoins are dead */ + return length(targetlist); +} + +/* + * Number of items in a tlist, not including any resjunk items + */ +int +ExecCleanTargetListLength(List *targetlist) +{ + int len = 0; List *tl; - TargetEntry *curTle; - len = 0; foreach(tl, targetlist) { - curTle = lfirst(tl); + TargetEntry *curTle = (TargetEntry *) lfirst(tl); - if (curTle->resdom != NULL) + Assert(IsA(curTle, TargetEntry)); + if (!curTle->resdom->resjunk) len++; - else - len += curTle->fjoin->fj_nNodes; } return len; } @@ -1451,34 +2857,33 @@ ExecTargetListLength(List *targetlist) /* ---------------------------------------------------------------- * ExecTargetList * - * Evaluates a targetlist with respect to the current - * expression context and return a tuple. + * Evaluates a targetlist with respect to the given + * expression context and returns a tuple. + * + * 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. + * + * 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. * ---------------------------------------------------------------- */ static HeapTuple ExecTargetList(List *targetlist, - int nodomains, TupleDesc targettype, - Datum *values, ExprContext *econtext, - bool *isDone) + Datum *values, + char *nulls, + ExprDoneCond *itemIsDone, + ExprDoneCond *isDone) { MemoryContext oldContext; - char nulls_array[64]; - bool fjNullArray[64]; - bool itemIsDoneArray[64]; - char *null_head; - bool *fjIsNull; - bool *itemIsDone; List *tl; - TargetEntry *tle; - Node *expr; - Resdom *resdom; - AttrNumber resind; - Datum constvalue; - HeapTuple newTuple; bool isNull; - bool haveDoneIters; + bool haveDoneSets; static struct tupleDesc NullTupleDesc; /* we assume this inits to * zeroes */ @@ -1505,230 +2910,135 @@ ExecTargetList(List *targetlist, if (targettype == NULL) targettype = &NullTupleDesc; - /* - * allocate an array of char's to hold the "null" information only if - * we have a really large targetlist. otherwise we use the stack. - * - * We also allocate a bool array that is used to hold fjoin result state, - * and another that holds the isDone status for each targetlist item. - */ - if (nodomains > 64) - { - null_head = (char *) palloc(nodomains + 1); - fjIsNull = (bool *) palloc(nodomains + 1); - itemIsDone = (bool *) palloc(nodomains + 1); - } - else - { - null_head = &nulls_array[0]; - fjIsNull = &fjNullArray[0]; - itemIsDone = &itemIsDoneArray[0]; - } - /* * evaluate all the expressions in the target list */ + if (isDone) + *isDone = ExprSingleResult; /* until proven otherwise */ - *isDone = true; /* until proven otherwise */ - haveDoneIters = false; /* any isDone Iter exprs in tlist? */ + 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; - /* - * remember, a target list is a list of lists: - * - * (( expr) ( expr) ...) - * - * tl is a pointer to successive cdr's of the targetlist tle is a - * pointer to the target list entry in tl - */ - tle = lfirst(tl); - - if (tle->resdom != NULL) - { - expr = tle->expr; - resdom = tle->resdom; - resind = resdom->resno - 1; - - constvalue = ExecEvalExpr(expr, + values[resind] = ExecEvalExpr(gstate->arg, econtext, &isNull, &itemIsDone[resind]); + nulls[resind] = isNull ? 'n' : ' '; - values[resind] = constvalue; - - if (!isNull) - null_head[resind] = ' '; - else - null_head[resind] = 'n'; - - if (IsA(expr, Iter)) - { - if (itemIsDone[resind]) - haveDoneIters = true; - else - *isDone = false; /* we have undone Iters in the - * list */ - } - } - else + if (itemIsDone[resind] != ExprSingleResult) { - int curNode; - Resdom *fjRes; - List *fjTlist = (List *) tle->expr; - Fjoin *fjNode = tle->fjoin; - int nNodes = fjNode->fj_nNodes; - DatumPtr results = fjNode->fj_results; - - ExecEvalFjoin(tle, econtext, fjIsNull, isDone); - - /* this is probably wrong: */ - if (*isDone) + /* We have a set-valued expression in the tlist */ + if (isDone == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (itemIsDone[resind] == ExprMultipleResult) { - newTuple = NULL; - goto exit; + /* we have undone sets in the tlist, set flag */ + *isDone = ExprMultipleResult; } - - /* - * get the result from the inner node - */ - fjRes = (Resdom *) fjNode->fj_innerNode; - resind = fjRes->resno - 1; - if (fjIsNull[0]) - null_head[resind] = 'n'; else { - null_head[resind] = ' '; - values[resind] = results[0]; - } - - /* - * Get results from all of the outer nodes - */ - for (curNode = 1; - curNode < nNodes; - curNode++, fjTlist = lnext(fjTlist)) - { -#ifdef NOT_USED /* what is this?? */ - Node *outernode = lfirst(fjTlist); - - fjRes = (Resdom *) outernode->iterexpr; -#endif - resind = fjRes->resno - 1; - if (fjIsNull[curNode]) - null_head[resind] = 'n'; - else - { - null_head[resind] = ' '; - values[resind] = results[curNode]; - } + /* we have done sets in the tlist, set flag for that */ + haveDoneSets = true; } } } - if (haveDoneIters) + if (haveDoneSets) { - if (*isDone) + /* + * note: can't get here unless we verified isDone != NULL + */ + if (*isDone == ExprSingleResult) { - /* - * all Iters are done, so return a null indicating tlist set - * expansion is complete. + * all sets are done, so report that tlist expansion is + * complete. */ - newTuple = NULL; - goto exit; + *isDone = ExprEndResult; + MemoryContextSwitchTo(oldContext); + return NULL; } else { - /* - * We have some done and some undone Iters. Restart the done + * We have some done and some undone sets. Restart the done * ones so that we can deliver a tuple (if possible). - * - * XXX this code is a crock, because it only works for Iters at - * the top level of tlist expressions, and doesn't even work - * right for them: you should get all possible combinations of - * Iter results, but you won't unless the numbers of values - * returned by each are relatively prime. Should have a - * mechanism more like aggregate functions, where we make a - * list of all Iters contained in the tlist and cycle through - * their values in a methodical fashion. To do someday; can't - * get excited about fixing a Berkeley feature that's not in - * SQL92. (The only reason we're doing this much is that we - * have to be sure all the Iters are run to completion, or - * their subplan executors will have unreleased resources, - * e.g. pinned buffers...) */ foreach(tl, targetlist) { - tle = lfirst(tl); + GenericExprState *gstate = (GenericExprState *) lfirst(tl); + TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; + AttrNumber resind = tle->resdom->resno - 1; - if (tle->resdom != NULL) + if (itemIsDone[resind] == ExprEndResult) { - expr = tle->expr; - resdom = tle->resdom; - resind = resdom->resno - 1; - - if (IsA(expr, Iter) &&itemIsDone[resind]) - { - constvalue = ExecEvalExpr(expr, + values[resind] = ExecEvalExpr(gstate->arg, econtext, &isNull, &itemIsDone[resind]); - if (itemIsDone[resind]) - { - - /* - * Oh dear, this Iter is returning an empty - * set. Guess we can't make a tuple after all. - */ - *isDone = true; - newTuple = NULL; - goto exit; - } - - values[resind] = constvalue; - - if (!isNull) - null_head[resind] = ' '; - else - null_head[resind] = 'n'; + 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. + */ + *isDone = ExprEndResult; + break; } } } - } - } - /* - * form the new result tuple (in the caller's memory context!) - */ - MemoryContextSwitchTo(oldContext); + /* + * 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? + */ + if (*isDone == ExprEndResult) + { + foreach(tl, targetlist) + { + GenericExprState *gstate = (GenericExprState *) lfirst(tl); + TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; + AttrNumber resind = tle->resdom->resno - 1; - newTuple = (HeapTuple) heap_formtuple(targettype, values, null_head); + while (itemIsDone[resind] == ExprMultipleResult) + { + (void) ExecEvalExpr(gstate->arg, + econtext, + &isNull, + &itemIsDone[resind]); + } + } -exit: + MemoryContextSwitchTo(oldContext); + return NULL; + } + } + } /* - * free the status arrays if we palloc'd them + * form the new result tuple (in the caller's memory context!) */ - if (nodomains > 64) - { - pfree(null_head); - pfree(fjIsNull); - pfree(itemIsDone); - } - - /* make sure we are in the right context if we did "goto exit" */ MemoryContextSwitchTo(oldContext); - return newTuple; + return heap_formtuple(targettype, values, nulls); } /* ---------------------------------------------------------------- * ExecProject * - * projects a tuple based in projection info and stores + * projects a tuple based on projection info and stores * it in the specified tuple table slot. * * Note: someday soon the executor can be extended to eliminate @@ -1739,14 +3049,10 @@ exit: * ---------------------------------------------------------------- */ TupleTableSlot * -ExecProject(ProjectionInfo *projInfo, bool *isDone) +ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) { TupleTableSlot *slot; - List *targetlist; - int len; TupleDesc tupType; - Datum *tupValue; - ExprContext *econtext; HeapTuple newTuple; /* @@ -1759,29 +3065,24 @@ ExecProject(ProjectionInfo *projInfo, bool *isDone) * get the projection info we want */ slot = projInfo->pi_slot; - targetlist = projInfo->pi_targetlist; - len = projInfo->pi_len; tupType = slot->ttc_tupleDescriptor; - tupValue = projInfo->pi_tupValue; - econtext = projInfo->pi_exprContext; - /* - * form a new (result) tuple + * form a new result tuple (if possible --- result can be NULL) */ - newTuple = ExecTargetList(targetlist, - len, + newTuple = ExecTargetList(projInfo->pi_targetlist, tupType, - tupValue, - econtext, + projInfo->pi_exprContext, + projInfo->pi_tupValues, + projInfo->pi_tupNulls, + projInfo->pi_itemIsDone, isDone); /* * store the tuple in the projection slot and return the slot. */ - return (TupleTableSlot *) - ExecStoreTuple(newTuple,/* tuple to store */ - slot, /* slot to store in */ - InvalidBuffer, /* tuple has no buffer */ - true); + return ExecStoreTuple(newTuple, /* tuple to store */ + slot, /* slot to store in */ + InvalidBuffer, /* tuple has no buffer */ + true); }