X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fexecutor%2FexecQual.c;h=c7bfe7c76ca6b926f2dcbe1eee05b3a0b8b6ecc0;hb=76d4abf2d974ffa578ddc7ff40984cc05c1d48b1;hp=c873134708f09e4eb90d057e9e24c639de25bd80;hpb=226837e57eb6092b160e7272e7d09a3748c0eb47;p=postgresql diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index c873134708..c7bfe7c76c 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -3,12 +3,12 @@ * execQual.c * Routines to evaluate qualification and targetlist expressions * - * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.229 2008/04/13 20:51:20 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.247 2009/06/04 18:33:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -29,14 +29,13 @@ * instead of doing needless copying. -cim 5/31/91 * * During expression evaluation, we check_stack_depth only in - * ExecMakeFunctionResult rather than at every single node. This - * is a compromise that trades off precision of the stack limit setting - * to gain speed. + * ExecMakeFunctionResult (and substitute routines) rather than at every + * single node. This is a compromise that trades off precision of the + * stack limit setting to gain speed. */ #include "postgres.h" -#include "access/heapam.h" #include "access/nbtree.h" #include "catalog/pg_type.h" #include "commands/typecmds.h" @@ -45,8 +44,9 @@ #include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" -#include "optimizer/planmain.h" -#include "parser/parse_expr.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/planner.h" +#include "pgstat.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -62,6 +62,9 @@ static Datum ExecEvalArrayRef(ArrayRefExprState *astate, static Datum ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, @@ -74,12 +77,23 @@ static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalParam(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); +static void init_fcache(Oid foid, FuncExprState *fcache, + MemoryContext fcacheCxt, bool needDescForSets); static void ShutdownFuncExpr(Datum arg); static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, TupleDesc *cache_field, ExprContext *econtext); static void ShutdownTupleDescRef(Datum arg); static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo, List *argList, ExprContext *econtext); +static void ExecPrepareTuplestoreResult(FuncExprState *fcache, + ExprContext *econtext, + Tuplestorestate *resultStore, + TupleDesc resultDesc); +static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); +static Datum ExecMakeFunctionResult(FuncExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone); static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -432,6 +446,27 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, return econtext->ecxt_aggvalues[aggref->aggno]; } +/* ---------------------------------------------------------------- + * ExecEvalWindowFunc + * + * Returns a Datum whose value is the value of the precomputed + * window function found in the given expression context. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + if (isDone) + *isDone = ExprSingleResult; + + if (econtext->ecxt_aggvalues == NULL) /* safety check */ + elog(ERROR, "no window functions in this expression context"); + + *isNull = econtext->ecxt_aggnulls[wfunc->wfuncno]; + return econtext->ecxt_aggvalues[wfunc->wfuncno]; +} + /* ---------------------------------------------------------------- * ExecEvalVar * @@ -581,8 +616,11 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("table row type and query-specified row type do not match"), - errdetail("Table row contains %d attributes, but query expects %d.", - slot_tupdesc->natts, var_tupdesc->natts))); + errdetail_plural("Table row contains %d attribute, but query expects %d.", + "Table row contains %d attributes, but query expects %d.", + slot_tupdesc->natts, + slot_tupdesc->natts, + var_tupdesc->natts))); else if (var_tupdesc->natts < slot_tupdesc->natts) needslow = true; @@ -986,8 +1024,9 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) /* * init_fcache - initialize a FuncExprState node during first use */ -void -init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) +static void +init_fcache(Oid foid, FuncExprState *fcache, + MemoryContext fcacheCxt, bool needDescForSets) { AclResult aclresult; @@ -1005,16 +1044,67 @@ init_fcache(Oid foid, FuncExprState *fcache, MemoryContext fcacheCxt) if (list_length(fcache->args) > FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("cannot pass more than %d arguments to a function", - FUNC_MAX_ARGS))); + errmsg_plural("cannot pass more than %d argument to a function", + "cannot pass more than %d arguments to a function", + FUNC_MAX_ARGS, + FUNC_MAX_ARGS))); /* Set up the primary fmgr lookup information */ fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); + fcache->func.fn_expr = (Node *) fcache->xprstate.expr; + + /* If function returns set, prepare expected tuple descriptor */ + if (fcache->func.fn_retset && needDescForSets) + { + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + MemoryContext oldcontext; - /* Initialize additional info */ + functypclass = get_expr_result_type(fcache->func.fn_expr, + &funcrettype, + &tupdesc); + + /* Must save tupdesc in fcache's context */ + oldcontext = MemoryContextSwitchTo(fcacheCxt); + + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + /* Must copy it out of typcache for safety */ + fcache->funcResultDesc = CreateTupleDescCopy(tupdesc); + fcache->funcReturnsTuple = true; + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + NULL, + funcrettype, + -1, + 0); + fcache->funcResultDesc = tupdesc; + fcache->funcReturnsTuple = false; + } + else + { + /* Else, we will complain if function wants materialize mode */ + fcache->funcResultDesc = NULL; + } + + MemoryContextSwitchTo(oldcontext); + } + else + fcache->funcResultDesc = NULL; + + /* Initialize additional state */ + fcache->funcResultStore = NULL; + fcache->funcResultSlot = NULL; fcache->setArgsValid = false; fcache->shutdown_reg = false; - fcache->func.fn_expr = (Node *) fcache->xprstate.expr; } /* @@ -1026,6 +1116,15 @@ ShutdownFuncExpr(Datum arg) { FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg); + /* If we have a slot, make sure it's let go of any tuplestore pointer */ + if (fcache->funcResultSlot) + ExecClearTuple(fcache->funcResultSlot); + + /* Release any open tuplestore */ + if (fcache->funcResultStore) + tuplestore_end(fcache->funcResultStore); + fcache->funcResultStore = NULL; + /* Clear any active set-argument state */ fcache->setArgsValid = false; @@ -1134,39 +1233,215 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo, return argIsDone; } +/* + * ExecPrepareTuplestoreResult + * + * Subroutine for ExecMakeFunctionResult: prepare to extract rows from a + * tuplestore function result. We must set up a funcResultSlot (unless + * already done in a previous call cycle) and verify that the function + * returned the expected tuple descriptor. + */ +static void +ExecPrepareTuplestoreResult(FuncExprState *fcache, + ExprContext *econtext, + Tuplestorestate *resultStore, + TupleDesc resultDesc) +{ + fcache->funcResultStore = resultStore; + + if (fcache->funcResultSlot == NULL) + { + /* Create a slot so we can read data out of the tuplestore */ + MemoryContext oldcontext; + + /* We must have been able to determine the result rowtype */ + if (fcache->funcResultDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning setof record called in " + "context that cannot accept type record"))); + + oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt); + fcache->funcResultSlot = + MakeSingleTupleTableSlot(fcache->funcResultDesc); + MemoryContextSwitchTo(oldcontext); + } + + /* + * If function provided a tupdesc, cross-check it. We only really + * need to do this for functions returning RECORD, but might as well + * do it always. + */ + if (resultDesc) + { + if (fcache->funcResultDesc) + tupledesc_match(fcache->funcResultDesc, resultDesc); + + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we must avoid + * leaking it across multiple usages. + */ + if (resultDesc->tdrefcount == -1) + FreeTupleDesc(resultDesc); + } + + /* Register cleanup callback if we didn't already */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownFuncExpr, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } +} + +/* + * Check that function result tuple type (src_tupdesc) matches or can + * be considered to match what the query expects (dst_tupdesc). If + * they don't match, ereport. + * + * We really only care about number of attributes and data type. + * Also, we can ignore type mismatch on columns that are dropped in the + * destination type, so long as the physical storage matches. This is + * helpful in some cases involving out-of-date cached plans. + */ +static void +tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) +{ + int i; + + if (dst_tupdesc->natts != src_tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail_plural("Returned row contains %d attribute, but query expects %d.", + "Returned row contains %d attributes, but query expects %d.", + src_tupdesc->natts, + src_tupdesc->natts, dst_tupdesc->natts))); + + for (i = 0; i < dst_tupdesc->natts; i++) + { + Form_pg_attribute dattr = dst_tupdesc->attrs[i]; + Form_pg_attribute sattr = src_tupdesc->attrs[i]; + + if (dattr->atttypid == sattr->atttypid) + continue; /* no worries */ + if (!dattr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail("Returned type %s at ordinal position %d, but query expects %s.", + format_type_be(sattr->atttypid), + i + 1, + format_type_be(dattr->atttypid)))); + + if (dattr->attlen != sattr->attlen || + dattr->attalign != sattr->attalign) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } +} + /* * ExecMakeFunctionResult * * Evaluate the arguments to a function and then the function itself. + * init_fcache is presumed already run on the FuncExprState. + * + * This function handles the most general case, wherein the function or + * one of its arguments might (or might not) return a set. If we find + * no sets involved, we will change the FuncExprState's function pointer + * to use a simpler method on subsequent calls. */ -Datum +static Datum ExecMakeFunctionResult(FuncExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - List *arguments = fcache->args; + List *arguments; Datum result; - FunctionCallInfoData fcinfo; + FunctionCallInfoData fcinfo_data; + FunctionCallInfo fcinfo; + PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; /* for functions returning sets */ ExprDoneCond argDone; bool hasSetArg; int i; +restart: + /* Guard against stack overflow due to overly complex expressions */ check_stack_depth(); + /* + * If a previous call of the function returned a set result in the form + * of a tuplestore, continue reading rows from the tuplestore until it's + * empty. + */ + if (fcache->funcResultStore) + { + Assert(isDone); /* it was provided before ... */ + if (tuplestore_gettupleslot(fcache->funcResultStore, true, false, + fcache->funcResultSlot)) + { + *isDone = ExprMultipleResult; + if (fcache->funcReturnsTuple) + { + /* We must return the whole tuple as a Datum. */ + *isNull = false; + return ExecFetchSlotTupleDatum(fcache->funcResultSlot); + } + else + { + /* Extract the first column and return it as a scalar. */ + return slot_getattr(fcache->funcResultSlot, 1, isNull); + } + } + /* Exhausted the tuplestore, so clean up */ + tuplestore_end(fcache->funcResultStore); + fcache->funcResultStore = NULL; + /* We are done unless there was a set-valued argument */ + if (!fcache->setHasSetArg) + { + *isDone = ExprEndResult; + *isNull = true; + return (Datum) 0; + } + /* If there was, continue evaluating the argument values */ + Assert(!fcache->setArgsValid); + } + + /* + * For non-set-returning functions, we just use a local-variable + * FunctionCallInfoData. For set-returning functions we keep the callinfo + * record in fcache->setArgs so that it can survive across multiple + * value-per-call invocations. (The reason we don't just do the latter + * all the time is that plpgsql expects to be able to use simple expression + * trees re-entrantly. Which might not be a good idea, but the penalty + * for not doing so is high.) + */ + if (fcache->func.fn_retset) + fcinfo = &fcache->setArgs; + else + fcinfo = &fcinfo_data; + /* * arguments is a list of expressions to evaluate before passing to the * function manager. We skip the evaluation if it was already done in the * previous call (ie, we are continuing the evaluation of a set-valued * function). Otherwise, collect the current argument values into fcinfo. */ + arguments = fcache->args; if (!fcache->setArgsValid) { /* Need to prep callinfo structure */ - InitFunctionCallInfoData(fcinfo, &(fcache->func), 0, NULL, NULL); - argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext); + InitFunctionCallInfoData(*fcinfo, &(fcache->func), 0, NULL, NULL); + argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext); if (argDone == ExprEndResult) { /* input is an empty set, so return an empty set. */ @@ -1183,32 +1458,14 @@ ExecMakeFunctionResult(FuncExprState *fcache, } else { - /* Copy callinfo from previous evaluation */ - memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo)); + /* Re-use callinfo from previous evaluation */ hasSetArg = fcache->setHasSetArg; /* Reset flag (we may set it again below) */ fcache->setArgsValid = false; } /* - * If function returns set, prepare a resultinfo node for communication - */ - if (fcache->func.fn_retset) - { - fcinfo.resultinfo = (Node *) &rsinfo; - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = NULL; - rsinfo.allowedModes = (int) SFRM_ValuePerCall; - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - } - - /* - * now return the value gotten by calling the function manager, passing - * the function the evaluated parameter values. + * Now call the function, passing the evaluated parameter values. */ if (fcache->func.fn_retset || hasSetArg) { @@ -1221,6 +1478,23 @@ ExecMakeFunctionResult(FuncExprState *fcache, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); + /* + * Prepare a resultinfo node for communication. If the function + * doesn't itself return set, we don't pass the resultinfo to the + * function, but we need to fill it in anyway for internal use. + */ + if (fcache->func.fn_retset) + fcinfo->resultinfo = (Node *) &rsinfo; + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = fcache->funcResultDesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + /* note we do not set SFRM_Materialize_Random or _Preferred */ + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + /* * This loop handles the situation where we have both a set argument * and a set-valued function. Once we have exhausted the function's @@ -1239,9 +1513,9 @@ ExecMakeFunctionResult(FuncExprState *fcache, if (fcache->func.fn_strict) { - for (i = 0; i < fcinfo.nargs; i++) + for (i = 0; i < fcinfo->nargs; i++) { - if (fcinfo.argnull[i]) + if (fcinfo->argnull[i]) { callit = false; break; @@ -1251,11 +1525,16 @@ ExecMakeFunctionResult(FuncExprState *fcache, if (callit) { - fcinfo.isnull = false; + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; rsinfo.isDone = ExprSingleResult; - result = FunctionCallInvoke(&fcinfo); - *isNull = fcinfo.isnull; + result = FunctionCallInvoke(fcinfo); + *isNull = fcinfo->isnull; *isDone = rsinfo.isDone; + + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); } else { @@ -1264,43 +1543,76 @@ ExecMakeFunctionResult(FuncExprState *fcache, *isDone = ExprEndResult; } - if (*isDone != ExprEndResult) + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) { - /* - * Got a result from current argument. If function itself - * returns set, save the current argument values to re-use on - * the next call. - */ - if (fcache->func.fn_retset && *isDone == ExprMultipleResult) + if (*isDone != ExprEndResult) { - memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo)); - fcache->setHasSetArg = hasSetArg; - fcache->setArgsValid = true; - /* Register cleanup callback if we didn't already */ - if (!fcache->shutdown_reg) + /* + * Got a result from current argument. If function itself + * returns set, save the current argument values to re-use + * on the next call. + */ + if (fcache->func.fn_retset && + *isDone == ExprMultipleResult) { - RegisterExprContextCallback(econtext, - ShutdownFuncExpr, - PointerGetDatum(fcache)); - fcache->shutdown_reg = true; + Assert(fcinfo == &fcache->setArgs); + fcache->setHasSetArg = hasSetArg; + fcache->setArgsValid = true; + /* Register cleanup callback if we didn't already */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownFuncExpr, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } } - } - /* - * Make sure we say we are returning a set, even if the - * function itself doesn't return sets. - */ - if (hasSetArg) - *isDone = ExprMultipleResult; - break; + /* + * Make sure we say we are returning a set, even if the + * function itself doesn't return sets. + */ + if (hasSetArg) + *isDone = ExprMultipleResult; + break; + } + } + else if (rsinfo.returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (rsinfo.isDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("table-function protocol for materialize mode was not followed"))); + if (rsinfo.setResult != NULL) + { + /* prepare to return values from the tuplestore */ + ExecPrepareTuplestoreResult(fcache, econtext, + rsinfo.setResult, + rsinfo.setDesc); + /* remember whether we had set arguments */ + fcache->setHasSetArg = hasSetArg; + /* loop back to top to start returning from tuplestore */ + goto restart; + } + /* if setResult was left null, treat it as empty set */ + *isDone = ExprEndResult; + *isNull = true; + result = (Datum) 0; } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo.returnMode))); /* Else, done with this argument */ if (!hasSetArg) break; /* input not a set, so done */ /* Re-eval args to get the next element of the input set */ - argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext); + argDone = ExecEvalFuncArgs(fcinfo, arguments, econtext); if (argDone != ExprMultipleResult) { @@ -1338,18 +1650,23 @@ ExecMakeFunctionResult(FuncExprState *fcache, */ if (fcache->func.fn_strict) { - for (i = 0; i < fcinfo.nargs; i++) + for (i = 0; i < fcinfo->nargs; i++) { - if (fcinfo.argnull[i]) + if (fcinfo->argnull[i]) { *isNull = true; return (Datum) 0; } } } - fcinfo.isnull = false; - result = FunctionCallInvoke(&fcinfo); - *isNull = fcinfo.isnull; + + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + result = FunctionCallInvoke(fcinfo); + *isNull = fcinfo->isnull; + + pgstat_end_function_usage(&fcusage, true); } return result; @@ -1370,6 +1687,7 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache, ListCell *arg; Datum result; FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; int i; /* Guard against stack overflow due to overly complex expressions */ @@ -1408,10 +1726,15 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache, } } } + + pgstat_init_function_usage(&fcinfo, &fcusage); + /* fcinfo.isnull = false; */ /* handled by InitFunctionCallInfoData */ result = FunctionCallInvoke(&fcinfo); *isNull = fcinfo.isnull; + pgstat_end_function_usage(&fcusage, true); + return result; } @@ -1420,14 +1743,13 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache, * ExecMakeTableFunctionResult * * Evaluate a table function, producing a materialized result in a Tuplestore - * object. *returnDesc is set to the tupledesc actually returned by the - * function, or NULL if it didn't provide one. + * object. */ Tuplestorestate * ExecMakeTableFunctionResult(ExprState *funcexpr, ExprContext *econtext, TupleDesc expectedDesc, - TupleDesc *returnDesc) + bool randomAccess) { Tuplestorestate *tupstore = NULL; TupleDesc tupdesc = NULL; @@ -1435,6 +1757,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, bool returnsTuple; bool returnsSet = false; FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; HeapTupleData tmptup; MemoryContext callerContext; @@ -1459,7 +1782,9 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; rsinfo.expectedDesc = expectedDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); + if (randomAccess) + rsinfo.allowedModes |= (int) SFRM_Materialize_Random; rsinfo.returnMode = SFRM_ValuePerCall; /* isDone is filled below */ rsinfo.setResult = NULL; @@ -1493,7 +1818,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, { FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + init_fcache(func->funcid, fcache, + econtext->ecxt_per_query_memory, false); } returnsSet = fcache->func.fn_retset; @@ -1560,9 +1886,14 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, /* Call the function or expression one time */ if (direct_function_call) { + pgstat_init_function_usage(&fcinfo, &fcusage); + fcinfo.isnull = false; rsinfo.isDone = ExprSingleResult; result = FunctionCallInvoke(&fcinfo); + + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); } else { @@ -1627,7 +1958,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr, -1, 0); } - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); MemoryContextSwitchTo(oldcontext); rsinfo.setResult = tupstore; rsinfo.setDesc = tupdesc; @@ -1694,7 +2025,7 @@ no_function_result: if (rsinfo.setResult == NULL) { MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); rsinfo.setResult = tupstore; if (!returnsSet) { @@ -1711,10 +2042,27 @@ no_function_result: } } + /* + * If function provided a tupdesc, cross-check it. We only really + * need to do this for functions returning RECORD, but might as well + * do it always. + */ + if (rsinfo.setDesc) + { + tupledesc_match(expectedDesc, rsinfo.setDesc); + + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we must avoid + * leaking it across multiple usages. + */ + if (rsinfo.setDesc->tdrefcount == -1) + FreeTupleDesc(rsinfo.setDesc); + } + MemoryContextSwitchTo(callerContext); - /* The returned pointers are those in rsinfo */ - *returnDesc = rsinfo.setDesc; + /* All done, pass back the tuplestore */ return rsinfo.setResult; } @@ -1742,7 +2090,7 @@ ExecEvalFunc(FuncExprState *fcache, FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; /* Initialize function lookup info */ - init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory); + init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory, true); /* Go directly to ExecMakeFunctionResult on subsequent uses */ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; @@ -1764,7 +2112,7 @@ ExecEvalOper(FuncExprState *fcache, OpExpr *op = (OpExpr *) fcache->xprstate.expr; /* Initialize function lookup info */ - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory, true); /* Go directly to ExecMakeFunctionResult on subsequent uses */ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult; @@ -1806,7 +2154,8 @@ ExecEvalDistinct(FuncExprState *fcache, { DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr; - init_fcache(op->opfuncid, fcache, econtext->ecxt_per_query_memory); + init_fcache(op->opfuncid, fcache, + econtext->ecxt_per_query_memory, true); Assert(!fcache->func.fn_retset); } @@ -1886,7 +2235,7 @@ ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, if (sstate->fxprstate.func.fn_oid == InvalidOid) { init_fcache(opexpr->opfuncid, &sstate->fxprstate, - econtext->ecxt_per_query_memory); + econtext->ecxt_per_query_memory, true); Assert(!sstate->fxprstate.func.fn_retset); } @@ -2845,13 +3194,10 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; - text *result; - StringInfoData buf; Datum value; bool isnull; ListCell *arg; ListCell *narg; - int i; if (isDone) *isDone = ExprSingleResult; @@ -2877,12 +3223,16 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, *isNull = false; return PointerGetDatum(xmlconcat(values)); } + else + return (Datum) 0; } break; case IS_XMLFOREST: + { + StringInfoData buf; + initStringInfo(&buf); - i = 0; forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names) { ExprState *e = (ExprState *) lfirst(arg); @@ -2897,11 +3247,25 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, argname); *isNull = false; } - i++; } + + if (*isNull) + { + pfree(buf.data); + return (Datum) 0; + } + else + { + text *result; + + result = cstring_to_text_with_len(buf.data, buf.len); + pfree(buf.data); + + return PointerGetDatum(result); + } + } break; - /* The remaining cases don't need to set up buf */ case IS_XMLELEMENT: *isNull = false; return PointerGetDatum(xmlelement(xmlExpr, econtext)); @@ -3036,13 +3400,8 @@ ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, break; } - if (*isNull) - result = NULL; - else - result = cstring_to_text_with_len(buf.data, buf.len); - - pfree(buf.data); - return PointerGetDatum(result); + elog(ERROR, "unrecognized XML operation"); + return (Datum) 0; } /* ---------------------------------------------------------------- @@ -3073,7 +3432,8 @@ ExecEvalNullIf(FuncExprState *nullIfExpr, { NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr; - init_fcache(op->opfuncid, nullIfExpr, econtext->ecxt_per_query_memory); + init_fcache(op->opfuncid, nullIfExpr, + econtext->ecxt_per_query_memory, true); Assert(!nullIfExpr->func.fn_retset); } @@ -3733,12 +4093,12 @@ ExecEvalExprSwitchContext(ExprState *expression, * executions of the expression are needed. Typically the context will be * the same as the per-query context of the associated ExprContext. * - * Any Aggref and SubPlan nodes found in the tree are added to the lists - * of such nodes held by the parent PlanState. Otherwise, we do very little - * initialization here other than building the state-node tree. Any nontrivial - * work associated with initializing runtime info for a node should happen - * during the first actual evaluation of that node. (This policy lets us - * avoid work if the node is never actually evaluated.) + * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the + * lists of such nodes held by the parent PlanState. Otherwise, we do very + * little initialization here other than building the state-node tree. Any + * nontrivial work associated with initializing runtime info for a node should + * happen during the first actual evaluation of that node. (This policy lets + * us avoid work if the node is never actually evaluated.) * * Note: there is no ExecEndExpr function; we assume that any resource * cleanup needed will be handled by just releasing the memory context @@ -3816,11 +4176,49 @@ ExecInitExpr(Expr *node, PlanState *parent) else { /* planner messed up */ - elog(ERROR, "aggref found in non-Agg plan node"); + elog(ERROR, "Aggref found in non-Agg plan node"); } state = (ExprState *) astate; } break; + case T_WindowFunc: + { + WindowFunc *wfunc = (WindowFunc *) node; + WindowFuncExprState *wfstate = makeNode(WindowFuncExprState); + + wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc; + if (parent && IsA(parent, WindowAggState)) + { + WindowAggState *winstate = (WindowAggState *) parent; + int nfuncs; + + winstate->funcs = lcons(wfstate, winstate->funcs); + nfuncs = ++winstate->numfuncs; + if (wfunc->winagg) + winstate->numaggs++; + + wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args, + parent); + + /* + * Complain if the windowfunc's arguments contain any + * windowfuncs; nested window functions are semantically + * nonsensical. (This should have been caught earlier, + * but we defend against it here anyway.) + */ + if (nfuncs != winstate->numfuncs) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("window function calls cannot be nested"))); + } + else + { + /* planner messed up */ + elog(ERROR, "WindowFunc found in non-WindowAgg plan node"); + } + state = (ExprState *) wfstate; + } + break; case T_ArrayRef: { ArrayRef *aref = (ArrayRef *) node; @@ -3929,11 +4327,24 @@ ExecInitExpr(Expr *node, PlanState *parent) sstate = ExecInitSubPlan(subplan, parent); /* Add SubPlanState nodes to parent->subPlan */ - parent->subPlan = lcons(sstate, parent->subPlan); + parent->subPlan = lappend(parent->subPlan, sstate); state = (ExprState *) sstate; } break; + case T_AlternativeSubPlan: + { + AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; + AlternativeSubPlanState *asstate; + + if (!parent) + elog(ERROR, "AlternativeSubPlan found with no parent plan"); + + asstate = ExecInitAlternativeSubPlan(asplan, parent); + + state = (ExprState *) asstate; + } + break; case T_FieldSelect: { FieldSelect *fselect = (FieldSelect *) node; @@ -4261,27 +4672,16 @@ ExecInitExpr(Expr *node, PlanState *parent) XmlExprState *xstate = makeNode(XmlExprState); List *outlist; ListCell *arg; - int i; xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml; - xstate->named_outfuncs = (FmgrInfo *) - palloc0(list_length(xexpr->named_args) * sizeof(FmgrInfo)); outlist = NIL; - i = 0; foreach(arg, xexpr->named_args) { Expr *e = (Expr *) lfirst(arg); ExprState *estate; - Oid typOutFunc; - bool typIsVarlena; estate = ExecInitExpr(e, parent); outlist = lappend(outlist, estate); - - getTypeOutputInfo(exprType((Node *) e), - &typOutFunc, &typIsVarlena); - fmgr_info(typOutFunc, &xstate->named_outfuncs[i]); - i++; } xstate->named_args = outlist; @@ -4390,10 +4790,11 @@ ExecInitExpr(Expr *node, PlanState *parent) * Plan tree context. * * This differs from ExecInitExpr in that we don't assume the caller is - * already running in the EState's per-query context. Also, we apply - * fix_opfuncids() to the passed expression tree to be sure it is ready - * to run. (In ordinary Plan trees the planner will have fixed opfuncids, - * but callers outside the executor will not have done this.) + * already running in the EState's per-query context. Also, we run the + * passed expression tree through expression_planner() to prepare it for + * execution. (In ordinary Plan trees the regular planning process will have + * made the appropriate transformations on expressions, but for standalone + * expressions this won't have happened.) */ ExprState * ExecPrepareExpr(Expr *node, EState *estate) @@ -4401,10 +4802,10 @@ ExecPrepareExpr(Expr *node, EState *estate) ExprState *result; MemoryContext oldcontext; - fix_opfuncids((Node *) node); - oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + node = expression_planner(node); + result = ExecInitExpr(node, NULL); MemoryContextSwitchTo(oldcontext); @@ -4555,6 +4956,7 @@ ExecCleanTargetListLength(List *targetlist) * prepared to deal with sets of result tuples. Otherwise, a return * of *isDone = ExprMultipleResult signifies a set element, and a return * of *isDone = ExprEndResult signifies end of the set of tuple. + * We assume that *isDone has been initialized to ExprSingleResult by caller. */ static bool ExecTargetList(List *targetlist, @@ -4576,9 +4978,6 @@ ExecTargetList(List *targetlist, /* * evaluate all the expressions in the target list */ - if (isDone) - *isDone = ExprSingleResult; /* until proven otherwise */ - haveDoneSets = false; /* any exhausted set exprs in tlist? */ foreach(tl, targetlist) @@ -4693,50 +5092,6 @@ ExecTargetList(List *targetlist, return true; } -/* - * ExecVariableList - * Evaluates a simple-Variable-list projection. - * - * Results are stored into the passed values and isnull arrays. - */ -static void -ExecVariableList(ProjectionInfo *projInfo, - Datum *values, - bool *isnull) -{ - ExprContext *econtext = projInfo->pi_exprContext; - int *varSlotOffsets = projInfo->pi_varSlotOffsets; - int *varNumbers = projInfo->pi_varNumbers; - int i; - - /* - * Force extraction of all input values that we need. - */ - if (projInfo->pi_lastInnerVar > 0) - slot_getsomeattrs(econtext->ecxt_innertuple, - projInfo->pi_lastInnerVar); - if (projInfo->pi_lastOuterVar > 0) - slot_getsomeattrs(econtext->ecxt_outertuple, - projInfo->pi_lastOuterVar); - if (projInfo->pi_lastScanVar > 0) - slot_getsomeattrs(econtext->ecxt_scantuple, - projInfo->pi_lastScanVar); - - /* - * Assign to result by direct extraction of fields from source slots ... a - * mite ugly, but fast ... - */ - for (i = list_length(projInfo->pi_targetlist) - 1; i >= 0; i--) - { - char *slotptr = ((char *) econtext) + varSlotOffsets[i]; - TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); - int varNumber = varNumbers[i] - 1; - - values[i] = varSlot->tts_values[varNumber]; - isnull[i] = varSlot->tts_isnull[varNumber]; - } -} - /* * ExecProject * @@ -4754,6 +5109,8 @@ TupleTableSlot * ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) { TupleTableSlot *slot; + ExprContext *econtext; + int numSimpleVars; /* * sanity checks @@ -4764,6 +5121,11 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) * get the projection info we want */ slot = projInfo->pi_slot; + econtext = projInfo->pi_exprContext; + + /* Assume single result row until proven otherwise */ + if (isDone) + *isDone = ExprSingleResult; /* * Clear any former contents of the result slot. This makes it safe for @@ -4773,29 +5135,84 @@ ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone) ExecClearTuple(slot); /* - * form a new result tuple (if possible); if successful, mark the result - * slot as containing a valid virtual tuple + * Force extraction of all input values that we'll need. The + * Var-extraction loops below depend on this, and we are also prefetching + * all attributes that will be referenced in the generic expressions. */ - if (projInfo->pi_isVarList) + if (projInfo->pi_lastInnerVar > 0) + slot_getsomeattrs(econtext->ecxt_innertuple, + projInfo->pi_lastInnerVar); + if (projInfo->pi_lastOuterVar > 0) + slot_getsomeattrs(econtext->ecxt_outertuple, + projInfo->pi_lastOuterVar); + if (projInfo->pi_lastScanVar > 0) + slot_getsomeattrs(econtext->ecxt_scantuple, + projInfo->pi_lastScanVar); + + /* + * Assign simple Vars to result by direct extraction of fields from source + * slots ... a mite ugly, but fast ... + */ + numSimpleVars = projInfo->pi_numSimpleVars; + if (numSimpleVars > 0) { - /* simple Var list: this always succeeds with one result row */ - if (isDone) - *isDone = ExprSingleResult; - ExecVariableList(projInfo, - slot->tts_values, - slot->tts_isnull); - ExecStoreVirtualTuple(slot); + Datum *values = slot->tts_values; + bool *isnull = slot->tts_isnull; + int *varSlotOffsets = projInfo->pi_varSlotOffsets; + int *varNumbers = projInfo->pi_varNumbers; + int i; + + if (projInfo->pi_directMap) + { + /* especially simple case where vars go to output in order */ + for (i = 0; i < numSimpleVars; i++) + { + char *slotptr = ((char *) econtext) + varSlotOffsets[i]; + TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); + int varNumber = varNumbers[i] - 1; + + values[i] = varSlot->tts_values[varNumber]; + isnull[i] = varSlot->tts_isnull[varNumber]; + } + } + else + { + /* we have to pay attention to varOutputCols[] */ + int *varOutputCols = projInfo->pi_varOutputCols; + + for (i = 0; i < numSimpleVars; i++) + { + char *slotptr = ((char *) econtext) + varSlotOffsets[i]; + TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); + int varNumber = varNumbers[i] - 1; + int varOutputCol = varOutputCols[i] - 1; + + values[varOutputCol] = varSlot->tts_values[varNumber]; + isnull[varOutputCol] = varSlot->tts_isnull[varNumber]; + } + } } - else + + /* + * If there are any generic expressions, evaluate them. It's possible + * that there are set-returning functions in such expressions; if so + * and we have reached the end of the set, we return the result slot, + * which we already marked empty. + */ + if (projInfo->pi_targetlist) { - if (ExecTargetList(projInfo->pi_targetlist, - projInfo->pi_exprContext, - slot->tts_values, - slot->tts_isnull, - projInfo->pi_itemIsDone, - isDone)) - ExecStoreVirtualTuple(slot); + if (!ExecTargetList(projInfo->pi_targetlist, + econtext, + slot->tts_values, + slot->tts_isnull, + projInfo->pi_itemIsDone, + isDone)) + return slot; /* no more result rows, return empty slot */ } - return slot; + /* + * Successfully formed a result row. Mark the result slot as containing a + * valid virtual tuple. + */ + return ExecStoreVirtualTuple(slot); }