| /*------------------------------------------------------------------------- |
| * |
| * execSRF.c |
| * Routines implementing the API for set-returning functions |
| * |
| * This file serves nodeFunctionscan.c and nodeProjectSet.c, providing |
| * common code for calling set-returning functions according to the |
| * ReturnSetInfo API. |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/executor/execSRF.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/htup_details.h" |
| #include "catalog/objectaccess.h" |
| #include "catalog/pg_proc.h" |
| #include "executor/execdebug.h" |
| #include "funcapi.h" |
| #include "miscadmin.h" |
| #include "nodes/nodeFuncs.h" |
| #include "parser/parse_coerce.h" |
| #include "pgstat.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| #include "utils/typcache.h" |
| |
| |
| /* static function decls */ |
| static void init_sexpr(Oid foid, Oid input_collation, Expr *node, |
| SetExprState *sexpr, PlanState *parent, |
| MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF); |
| static void ShutdownSetExpr(Datum arg); |
| static void ExecEvalFuncArgs(FunctionCallInfo fcinfo, |
| List *argList, ExprContext *econtext); |
| static void ExecPrepareTuplestoreResult(SetExprState *sexpr, |
| ExprContext *econtext, |
| Tuplestorestate *resultStore, |
| TupleDesc resultDesc); |
| static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); |
| |
| |
| /* |
| * Prepare function call in FROM (ROWS FROM) for execution. |
| * |
| * This is used by nodeFunctionscan.c. |
| */ |
| SetExprState * |
| ExecInitTableFunctionResult(Expr *expr, |
| ExprContext *econtext, PlanState *parent) |
| { |
| SetExprState *state = makeNode(SetExprState); |
| |
| state->funcReturnsSet = false; |
| state->expr = expr; |
| state->func.fn_oid = InvalidOid; |
| |
| /* |
| * Normally the passed expression tree will be a FuncExpr, 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. That code path will not |
| * support set-returning functions buried in the expression, though. |
| */ |
| if (IsA(expr, FuncExpr)) |
| { |
| FuncExpr *func = (FuncExpr *) expr; |
| |
| state->funcReturnsSet = func->funcretset; |
| state->args = ExecInitExprList(func->args, parent); |
| |
| init_sexpr(func->funcid, func->inputcollid, expr, state, parent, |
| econtext->ecxt_per_query_memory, func->funcretset, false); |
| } |
| else |
| { |
| state->elidedFuncState = ExecInitExpr(expr, parent); |
| } |
| |
| return state; |
| } |
| |
| /* |
| * ExecMakeTableFunctionResult |
| * |
| * Evaluate a table function, producing a materialized result in a Tuplestore |
| * object. |
| * |
| * This is used by nodeFunctionscan.c. |
| */ |
| Tuplestorestate * |
| ExecMakeTableFunctionResult(SetExprState *setexpr, |
| ExprContext *econtext, |
| MemoryContext argContext, |
| TupleDesc expectedDesc, |
| bool randomAccess, |
| uint64 operatorMemKB) |
| { |
| Tuplestorestate *tupstore = NULL; |
| TupleDesc tupdesc = NULL; |
| Oid funcrettype; |
| bool returnsTuple; |
| bool returnsSet = false; |
| FunctionCallInfo fcinfo; |
| PgStat_FunctionCallUsage fcusage; |
| ReturnSetInfo rsinfo; |
| HeapTupleData tmptup; |
| MemoryContext callerContext; |
| bool first_time = true; |
| |
| /* |
| * Execute per-tablefunc actions in appropriate context. |
| * |
| * The FunctionCallInfo needs to live across all the calls to a |
| * ValuePerCall function, so it can't be allocated in the per-tuple |
| * context. Similarly, the function arguments need to be evaluated in a |
| * context that is longer lived than the per-tuple context: The argument |
| * values would otherwise disappear when we reset that context in the |
| * inner loop. As the caller's CurrentMemoryContext is typically a |
| * query-lifespan context, we don't want to leak memory there. We require |
| * the caller to pass a separate memory context that can be used for this, |
| * and can be reset each time through to avoid bloat. |
| */ |
| MemoryContextReset(argContext); |
| callerContext = MemoryContextSwitchTo(argContext); |
| |
| funcrettype = exprType((Node *) setexpr->expr); |
| |
| returnsTuple = type_is_rowtype(funcrettype); |
| |
| /* |
| * Prepare a resultinfo node for communication. We always do this even if |
| * not expecting a set result, so that we can pass expectedDesc. In the |
| * generic-expression case, the expression doesn't actually get to see the |
| * resultinfo, but set it up anyway because we use some of the fields as |
| * our own state variables. |
| */ |
| rsinfo.type = T_ReturnSetInfo; |
| rsinfo.econtext = econtext; |
| rsinfo.expectedDesc = expectedDesc; |
| rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); |
| if (randomAccess) |
| rsinfo.allowedModes |= (int) SFRM_Materialize_Random; |
| rsinfo.returnMode = SFRM_ValuePerCall; |
| /* isDone is filled below */ |
| rsinfo.setResult = NULL; |
| rsinfo.setDesc = NULL; |
| |
| fcinfo = palloc(SizeForFunctionCallInfo(list_length(setexpr->args))); |
| |
| /* |
| * Normally the passed expression tree will be a SetExprState, since the |
| * grammar only allows a function call at the top level of a table |
| * function reference. However, if the function doesn't return set then |
| * the planner might have replaced the function call via constant-folding |
| * or inlining. So if we see any other kind of expression node, execute |
| * it via the general ExecEvalExpr() code; the only difference is that we |
| * don't get a chance to pass a special ReturnSetInfo to any functions |
| * buried in the expression. |
| */ |
| if (!setexpr->elidedFuncState) |
| { |
| /* |
| * This path is similar to ExecMakeFunctionResultSet. |
| */ |
| returnsSet = setexpr->funcReturnsSet; |
| InitFunctionCallInfoData(*fcinfo, &(setexpr->func), |
| list_length(setexpr->args), |
| setexpr->fcinfo->fncollation, |
| NULL, (Node *) &rsinfo); |
| /* evaluate the function's argument list */ |
| Assert(CurrentMemoryContext == argContext); |
| ExecEvalFuncArgs(fcinfo, setexpr->args, econtext); |
| |
| /* |
| * If function is strict, and there are any NULL arguments, skip |
| * calling the function and act like it returned NULL (or an empty |
| * set, in the returns-set case). |
| */ |
| if (setexpr->func.fn_strict) |
| { |
| int i; |
| |
| for (i = 0; i < fcinfo->nargs; i++) |
| { |
| if (fcinfo->args[i].isnull) |
| goto no_function_result; |
| } |
| } |
| } |
| else |
| { |
| /* Treat setexpr as a generic expression */ |
| InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL); |
| } |
| |
| /* |
| * Switch to short-lived context for calling the function or expression. |
| */ |
| MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); |
| |
| /* |
| * Loop to handle the ValuePerCall protocol (which is also the same |
| * behavior needed in the generic ExecEvalExpr path). |
| */ |
| for (;;) |
| { |
| Datum result; |
| |
| CHECK_FOR_INTERRUPTS(); |
| |
| if (QueryFinishPending) |
| break; |
| |
| /* |
| * Reset per-tuple memory context before each call of the function or |
| * expression. This cleans up any local memory the function may leak |
| * when called. |
| */ |
| ResetExprContext(econtext); |
| |
| /* Call the function or expression one time */ |
| if (!setexpr->elidedFuncState) |
| { |
| pgstat_init_function_usage(fcinfo, &fcusage); |
| |
| fcinfo->isnull = false; |
| rsinfo.isDone = ExprSingleResult; |
| result = FunctionCallInvoke(fcinfo); |
| |
| pgstat_end_function_usage(&fcusage, |
| rsinfo.isDone != ExprMultipleResult); |
| } |
| else |
| { |
| result = |
| ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull); |
| rsinfo.isDone = ExprSingleResult; |
| } |
| |
| /* Which protocol does function want to use? */ |
| if (rsinfo.returnMode == SFRM_ValuePerCall) |
| { |
| /* |
| * Check for end of result set. |
| */ |
| if (rsinfo.isDone == ExprEndResult) |
| break; |
| |
| /* |
| * If first time through, build tuplestore for result. For a |
| * scalar function result type, also make a suitable tupdesc. |
| */ |
| if (first_time) |
| { |
| MemoryContext oldcontext = |
| MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
| |
| tupstore = tuplestore_begin_heap(randomAccess, false, operatorMemKB); |
| rsinfo.setResult = tupstore; |
| if (!returnsTuple) |
| { |
| tupdesc = CreateTemplateTupleDesc(1); |
| TupleDescInitEntry(tupdesc, |
| (AttrNumber) 1, |
| "column", |
| funcrettype, |
| -1, |
| 0); |
| rsinfo.setDesc = tupdesc; |
| } |
| MemoryContextSwitchTo(oldcontext); |
| } |
| |
| /* |
| * Store current resultset item. |
| */ |
| if (returnsTuple) |
| { |
| if (!fcinfo->isnull) |
| { |
| HeapTupleHeader td = DatumGetHeapTupleHeader(result); |
| |
| if (tupdesc == NULL) |
| { |
| MemoryContext oldcontext = |
| MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
| |
| /* |
| * This is the first non-NULL result from the |
| * function. Use the type info embedded in the |
| * rowtype Datum to look up the needed tupdesc. Make |
| * a copy for the query. |
| */ |
| tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), |
| HeapTupleHeaderGetTypMod(td)); |
| rsinfo.setDesc = tupdesc; |
| MemoryContextSwitchTo(oldcontext); |
| } |
| else |
| { |
| /* |
| * Verify all later returned rows have same subtype; |
| * necessary in case the type is RECORD. |
| */ |
| if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid || |
| HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("rows returned by function are not all of the same row type"))); |
| } |
| |
| /* |
| * tuplestore_puttuple needs a HeapTuple not a bare |
| * HeapTupleHeader, but it doesn't need all the fields. |
| */ |
| tmptup.t_len = HeapTupleHeaderGetDatumLength(td); |
| tmptup.t_data = td; |
| |
| tuplestore_puttuple(tupstore, &tmptup); |
| } |
| else |
| { |
| /* |
| * NULL result from a tuple-returning function; expand it |
| * to a row of all nulls. We rely on the expectedDesc to |
| * form such rows. (Note: this would be problematic if |
| * tuplestore_putvalues saved the tdtypeid/tdtypmod from |
| * the provided descriptor, since that might not match |
| * what we get from the function itself. But it doesn't.) |
| */ |
| int natts = expectedDesc->natts; |
| bool *nullflags; |
| |
| nullflags = (bool *) palloc(natts * sizeof(bool)); |
| memset(nullflags, true, natts * sizeof(bool)); |
| tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); |
| } |
| } |
| else |
| { |
| /* Scalar-type case: just store the function result */ |
| tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo->isnull); |
| } |
| |
| /* |
| * Are we done? |
| */ |
| if (rsinfo.isDone != ExprMultipleResult) |
| break; |
| |
| /* |
| * Check that set-returning functions were properly declared. |
| * (Note: for historical reasons, we don't complain if a non-SRF |
| * returns ExprEndResult; that's treated as returning NULL.) |
| */ |
| if (!returnsSet) |
| ereport(ERROR, |
| (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), |
| errmsg("table-function protocol for value-per-call mode was not followed"))); |
| } |
| else if (rsinfo.returnMode == SFRM_Materialize) |
| { |
| /* check we're on the same page as the function author */ |
| if (!first_time || rsinfo.isDone != ExprSingleResult || !returnsSet) |
| 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; |
| } |
| |
| no_function_result: |
| |
| /* |
| * If we got nothing from the function (ie, an empty-set or NULL result), |
| * we have to create the tuplestore to return, and if it's a |
| * non-set-returning function then insert a single all-nulls row. As |
| * above, we depend on the expectedDesc to manufacture the dummy row. |
| */ |
| if (rsinfo.setResult == NULL) |
| { |
| MemoryContext oldcontext = |
| MemoryContextSwitchTo(econtext->ecxt_per_query_memory); |
| |
| tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); |
| rsinfo.setResult = tupstore; |
| MemoryContextSwitchTo(oldcontext); |
| |
| if (!returnsSet) |
| { |
| int natts = expectedDesc->natts; |
| bool *nullflags; |
| |
| nullflags = (bool *) palloc(natts * sizeof(bool)); |
| memset(nullflags, true, natts * sizeof(bool)); |
| tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); |
| } |
| } |
| |
| /* |
| * If function provided a tupdesc, cross-check it. We only really need to |
| * do this for functions returning RECORD, but might as well do it always. |
| */ |
| if (rsinfo.setDesc) |
| { |
| tupledesc_match(expectedDesc, rsinfo.setDesc); |
| |
| /* |
| * If it is a dynamically-allocated TupleDesc, free it: it is |
| * typically allocated in a per-query context, so we must avoid |
| * leaking it across multiple usages. |
| */ |
| if (rsinfo.setDesc->tdrefcount == -1) |
| FreeTupleDesc(rsinfo.setDesc); |
| } |
| |
| MemoryContextSwitchTo(callerContext); |
| |
| /* All done, pass back the tuplestore */ |
| return rsinfo.setResult; |
| } |
| |
| |
| /* |
| * Prepare targetlist SRF function call for execution. |
| * |
| * This is used by nodeProjectSet.c. |
| */ |
| SetExprState * |
| ExecInitFunctionResultSet(Expr *expr, |
| ExprContext *econtext, PlanState *parent) |
| { |
| SetExprState *state = makeNode(SetExprState); |
| |
| state->funcReturnsSet = true; |
| state->expr = expr; |
| state->func.fn_oid = InvalidOid; |
| |
| /* |
| * Initialize metadata. The expression node could be either a FuncExpr or |
| * an OpExpr. |
| */ |
| if (IsA(expr, FuncExpr)) |
| { |
| FuncExpr *func = (FuncExpr *) expr; |
| |
| state->args = ExecInitExprList(func->args, parent); |
| init_sexpr(func->funcid, func->inputcollid, expr, state, parent, |
| econtext->ecxt_per_query_memory, true, true); |
| } |
| else if (IsA(expr, OpExpr)) |
| { |
| OpExpr *op = (OpExpr *) expr; |
| |
| state->args = ExecInitExprList(op->args, parent); |
| init_sexpr(op->opfuncid, op->inputcollid, expr, state, parent, |
| econtext->ecxt_per_query_memory, true, true); |
| } |
| else |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(expr)); |
| |
| /* shouldn't get here unless the selected function returns set */ |
| Assert(state->func.fn_retset); |
| |
| return state; |
| } |
| |
| /* |
| * ExecMakeFunctionResultSet |
| * |
| * Evaluate the arguments to a set-returning function and then call the |
| * function itself. The argument expressions may not contain set-returning |
| * functions (the planner is supposed to have separated evaluation for those). |
| * |
| * This should be called in a short-lived (per-tuple) context, argContext |
| * needs to live until all rows have been returned (i.e. *isDone set to |
| * ExprEndResult or ExprSingleResult). |
| * |
| * This is used by nodeProjectSet.c. |
| */ |
| Datum |
| ExecMakeFunctionResultSet(SetExprState *fcache, |
| ExprContext *econtext, |
| MemoryContext argContext, |
| bool *isNull, |
| ExprDoneCond *isDone) |
| { |
| List *arguments; |
| Datum result; |
| FunctionCallInfo fcinfo; |
| PgStat_FunctionCallUsage fcusage; |
| ReturnSetInfo rsinfo; |
| bool callit; |
| 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) |
| { |
| TupleTableSlot *slot = fcache->funcResultSlot; |
| MemoryContext oldContext; |
| bool foundTup; |
| |
| /* |
| * Have to make sure tuple in slot lives long enough, otherwise |
| * clearing the slot could end up trying to free something already |
| * freed. |
| */ |
| oldContext = MemoryContextSwitchTo(slot->tts_mcxt); |
| foundTup = tuplestore_gettupleslot(fcache->funcResultStore, true, false, |
| fcache->funcResultSlot); |
| MemoryContextSwitchTo(oldContext); |
| |
| if (foundTup) |
| { |
| *isDone = ExprMultipleResult; |
| if (fcache->funcReturnsTuple) |
| { |
| /* We must return the whole tuple as a Datum. */ |
| *isNull = false; |
| return ExecFetchSlotHeapTupleDatum(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; |
| *isDone = ExprEndResult; |
| *isNull = true; |
| return (Datum) 0; |
| } |
| |
| /* |
| * 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. |
| * |
| * The arguments have to live in a context that lives at least until all |
| * rows from this SRF have been returned, otherwise ValuePerCall SRFs |
| * would reference freed memory after the first returned row. |
| */ |
| fcinfo = fcache->fcinfo; |
| arguments = fcache->args; |
| if (!fcache->setArgsValid) |
| { |
| MemoryContext oldContext = MemoryContextSwitchTo(argContext); |
| |
| ExecEvalFuncArgs(fcinfo, arguments, econtext); |
| MemoryContextSwitchTo(oldContext); |
| } |
| else |
| { |
| /* Reset flag (we may set it again below) */ |
| fcache->setArgsValid = false; |
| } |
| |
| /* |
| * Now call the function, passing the evaluated parameter values. |
| */ |
| |
| /* Prepare a resultinfo node for communication. */ |
| 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; |
| |
| /* |
| * If function is strict, and there are any NULL arguments, skip calling |
| * the function. |
| */ |
| callit = true; |
| if (fcache->func.fn_strict) |
| { |
| for (i = 0; i < fcinfo->nargs; i++) |
| { |
| if (fcinfo->args[i].isnull) |
| { |
| callit = false; |
| break; |
| } |
| } |
| } |
| |
| if (callit) |
| { |
| pgstat_init_function_usage(fcinfo, &fcusage); |
| |
| fcinfo->isnull = false; |
| rsinfo.isDone = ExprSingleResult; |
| result = FunctionCallInvoke(fcinfo); |
| *isNull = fcinfo->isnull; |
| *isDone = rsinfo.isDone; |
| |
| pgstat_end_function_usage(&fcusage, |
| rsinfo.isDone != ExprMultipleResult); |
| } |
| else |
| { |
| /* for a strict SRF, result for NULL is an empty set */ |
| result = (Datum) 0; |
| *isNull = true; |
| *isDone = ExprEndResult; |
| } |
| |
| /* Which protocol does function want to use? */ |
| if (rsinfo.returnMode == SFRM_ValuePerCall) |
| { |
| if (*isDone != ExprEndResult) |
| { |
| /* |
| * Save the current argument values to re-use on the next call. |
| */ |
| if (*isDone == ExprMultipleResult) |
| { |
| fcache->setArgsValid = true; |
| /* Register cleanup callback if we didn't already */ |
| if (!fcache->shutdown_reg) |
| { |
| RegisterExprContextCallback(econtext, |
| ShutdownSetExpr, |
| PointerGetDatum(fcache)); |
| fcache->shutdown_reg = true; |
| } |
| } |
| } |
| } |
| 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); |
| /* 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))); |
| |
| return result; |
| } |
| |
| |
| /* |
| * init_sexpr - initialize a SetExprState node during first use |
| */ |
| static void |
| init_sexpr(Oid foid, Oid input_collation, Expr *node, |
| SetExprState *sexpr, PlanState *parent, |
| MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF) |
| { |
| AclResult aclresult; |
| size_t numargs = list_length(sexpr->args); |
| |
| /* Check permission to call function */ |
| aclresult = object_aclcheck(ProcedureRelationId, foid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid)); |
| InvokeFunctionExecuteHook(foid); |
| |
| /* |
| * Safety check on nargs. Under normal circumstances this should never |
| * fail, as parser should check sooner. But possibly it might fail if |
| * server has been compiled with FUNC_MAX_ARGS smaller than some functions |
| * declared in pg_proc? |
| */ |
| if (list_length(sexpr->args) > FUNC_MAX_ARGS) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_ARGUMENTS), |
| errmsg_plural("cannot pass more than %d argument to a function", |
| "cannot pass more than %d arguments to a function", |
| FUNC_MAX_ARGS, |
| FUNC_MAX_ARGS))); |
| |
| /* Set up the primary fmgr lookup information */ |
| fmgr_info_cxt(foid, &(sexpr->func), sexprCxt); |
| fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func)); |
| |
| /* Initialize the function call parameter struct as well */ |
| sexpr->fcinfo = |
| (FunctionCallInfo) palloc(SizeForFunctionCallInfo(numargs)); |
| InitFunctionCallInfoData(*sexpr->fcinfo, &(sexpr->func), |
| numargs, |
| input_collation, NULL, NULL); |
| |
| /* If function returns set, check if that's allowed by caller */ |
| if (sexpr->func.fn_retset && !allowSRF) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("set-valued function called in context that cannot accept a set"), |
| parent ? executor_errposition(parent->state, |
| exprLocation((Node *) node)) : 0)); |
| |
| /* Otherwise, caller should have marked the sexpr correctly */ |
| Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet); |
| |
| /* If function returns set, prepare expected tuple descriptor */ |
| if (sexpr->func.fn_retset && needDescForSRF) |
| { |
| TypeFuncClass functypclass; |
| Oid funcrettype; |
| TupleDesc tupdesc; |
| MemoryContext oldcontext; |
| |
| functypclass = get_expr_result_type(sexpr->func.fn_expr, |
| &funcrettype, |
| &tupdesc); |
| |
| /* Must save tupdesc in sexpr's context */ |
| oldcontext = MemoryContextSwitchTo(sexprCxt); |
| |
| if (functypclass == TYPEFUNC_COMPOSITE || |
| functypclass == TYPEFUNC_COMPOSITE_DOMAIN) |
| { |
| /* Composite data type, e.g. a table's row type */ |
| Assert(tupdesc); |
| /* Must copy it out of typcache for safety */ |
| sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc); |
| sexpr->funcReturnsTuple = true; |
| } |
| else if (functypclass == TYPEFUNC_SCALAR) |
| { |
| /* Base data type, i.e. scalar */ |
| tupdesc = CreateTemplateTupleDesc(1); |
| TupleDescInitEntry(tupdesc, |
| (AttrNumber) 1, |
| NULL, |
| funcrettype, |
| -1, |
| 0); |
| sexpr->funcResultDesc = tupdesc; |
| sexpr->funcReturnsTuple = false; |
| } |
| else if (functypclass == TYPEFUNC_RECORD) |
| { |
| /* This will work if function doesn't need an expectedDesc */ |
| sexpr->funcResultDesc = NULL; |
| sexpr->funcReturnsTuple = true; |
| } |
| else |
| { |
| /* Else, we will fail if function needs an expectedDesc */ |
| sexpr->funcResultDesc = NULL; |
| } |
| |
| MemoryContextSwitchTo(oldcontext); |
| } |
| else |
| sexpr->funcResultDesc = NULL; |
| |
| /* Initialize additional state */ |
| sexpr->funcResultStore = NULL; |
| sexpr->funcResultSlot = NULL; |
| sexpr->shutdown_reg = false; |
| } |
| |
| /* |
| * callback function in case a SetExprState needs to be shut down before it |
| * has been run to completion |
| */ |
| static void |
| ShutdownSetExpr(Datum arg) |
| { |
| SetExprState *sexpr = castNode(SetExprState, DatumGetPointer(arg)); |
| |
| /* If we have a slot, make sure it's let go of any tuplestore pointer */ |
| if (sexpr->funcResultSlot) |
| ExecClearTuple(sexpr->funcResultSlot); |
| |
| /* Release any open tuplestore */ |
| if (sexpr->funcResultStore) |
| tuplestore_end(sexpr->funcResultStore); |
| sexpr->funcResultStore = NULL; |
| |
| /* Clear any active set-argument state */ |
| sexpr->setArgsValid = false; |
| |
| /* execUtils will deregister the callback... */ |
| sexpr->shutdown_reg = false; |
| } |
| |
| /* |
| * Evaluate arguments for a function. |
| */ |
| static void |
| ExecEvalFuncArgs(FunctionCallInfo fcinfo, |
| List *argList, |
| ExprContext *econtext) |
| { |
| int i; |
| ListCell *arg; |
| |
| i = 0; |
| foreach(arg, argList) |
| { |
| ExprState *argstate = (ExprState *) lfirst(arg); |
| |
| fcinfo->args[i].value = ExecEvalExpr(argstate, |
| econtext, |
| &fcinfo->args[i].isnull); |
| i++; |
| } |
| |
| Assert(i == fcinfo->nargs); |
| } |
| |
| /* |
| * ExecPrepareTuplestoreResult |
| * |
| * Subroutine for ExecMakeFunctionResultSet: 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(SetExprState *sexpr, |
| ExprContext *econtext, |
| Tuplestorestate *resultStore, |
| TupleDesc resultDesc) |
| { |
| sexpr->funcResultStore = resultStore; |
| |
| if (sexpr->funcResultSlot == NULL) |
| { |
| /* Create a slot so we can read data out of the tuplestore */ |
| TupleDesc slotDesc; |
| MemoryContext oldcontext; |
| |
| oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt); |
| |
| /* |
| * If we were not able to determine the result rowtype from context, |
| * and the function didn't return a tupdesc, we have to fail. |
| */ |
| if (sexpr->funcResultDesc) |
| slotDesc = sexpr->funcResultDesc; |
| else if (resultDesc) |
| { |
| /* don't assume resultDesc is long-lived */ |
| slotDesc = CreateTupleDescCopy(resultDesc); |
| } |
| else |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("function returning setof record called in " |
| "context that cannot accept type record"))); |
| slotDesc = NULL; /* keep compiler quiet */ |
| } |
| |
| sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc, |
| &TTSOpsMinimalTuple); |
| 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 (sexpr->funcResultDesc) |
| tupledesc_match(sexpr->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 (!sexpr->shutdown_reg) |
| { |
| RegisterExprContextCallback(econtext, |
| ShutdownSetExpr, |
| PointerGetDatum(sexpr)); |
| sexpr->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 = TupleDescAttr(dst_tupdesc, i); |
| Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i); |
| |
| if (IsBinaryCoercible(sattr->atttypid, dattr->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))); |
| } |
| } |