📄 functions.c
字号:
/*------------------------------------------------------------------------- * * functions.c * Execution of SQL-language functions * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.98.2.1 2005/11/22 18:23:08 momjian Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include "access/heapam.h"#include "catalog/pg_proc.h"#include "catalog/pg_type.h"#include "commands/trigger.h"#include "executor/executor.h"#include "executor/functions.h"#include "funcapi.h"#include "parser/parse_coerce.h"#include "parser/parse_expr.h"#include "parser/parse_type.h"#include "tcop/tcopprot.h"#include "tcop/utility.h"#include "utils/builtins.h"#include "utils/datum.h"#include "utils/lsyscache.h"#include "utils/syscache.h"#include "utils/typcache.h"/* * We have an execution_state record for each query in a function. Each * record contains a querytree and plantree for its query. If the query * is currently in F_EXEC_RUN state then there's a QueryDesc too. */typedef enum{ F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE} ExecStatus;typedef struct local_es{ struct local_es *next; ExecStatus status; Query *query; Plan *plan; QueryDesc *qd; /* null unless status == RUN */} execution_state;#define LAST_POSTQUEL_COMMAND(es) ((es)->next == NULL)/* * An SQLFunctionCache record is built during the first call, * and linked to from the fn_extra field of the FmgrInfo struct. */typedef struct{ Oid *argtypes; /* resolved types of arguments */ Oid rettype; /* actual return type */ int typlen; /* length of the return type */ bool typbyval; /* true if return type is pass by value */ bool returnsTuple; /* true if returning whole tuple result */ bool shutdown_reg; /* true if registered shutdown callback */ bool readonly_func; /* true to run in "read only" mode */ ParamListInfo paramLI; /* Param list representing current args */ JunkFilter *junkFilter; /* used only if returnsTuple */ /* head of linked list of execution_state records */ execution_state *func_state;} SQLFunctionCache;typedef SQLFunctionCache *SQLFunctionCachePtr;/* non-export function prototypes */static execution_state *init_execution_state(List *queryTree_list, bool readonly_func);static void init_sql_fcache(FmgrInfo *finfo);static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);static TupleTableSlot *postquel_getnext(execution_state *es);static void postquel_end(execution_state *es);static void postquel_sub_params(SQLFunctionCachePtr fcache, FunctionCallInfo fcinfo);static Datum postquel_execute(execution_state *es, FunctionCallInfo fcinfo, SQLFunctionCachePtr fcache, MemoryContext resultcontext);static void sql_exec_error_callback(void *arg);static void ShutdownSQLFunction(Datum arg);static execution_state *init_execution_state(List *queryTree_list, bool readonly_func){ execution_state *firstes = NULL; execution_state *preves = NULL; ListCell *qtl_item; foreach(qtl_item, queryTree_list) { Query *queryTree = lfirst(qtl_item); Plan *planTree; execution_state *newes; /* Precheck all commands for validity in a function */ if (queryTree->commandType == CMD_UTILITY && IsA(queryTree->utilityStmt, TransactionStmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a SQL function", CreateQueryTag(queryTree)))); if (readonly_func && !QueryIsReadOnly(queryTree)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a non-volatile function", CreateQueryTag(queryTree)))); planTree = pg_plan_query(queryTree, NULL); newes = (execution_state *) palloc(sizeof(execution_state)); if (preves) preves->next = newes; else firstes = newes; newes->next = NULL; newes->status = F_EXEC_START; newes->query = queryTree; newes->plan = planTree; newes->qd = NULL; preves = newes; } return firstes;}static voidinit_sql_fcache(FmgrInfo *finfo){ Oid foid = finfo->fn_oid; Oid rettype; HeapTuple procedureTuple; HeapTuple typeTuple; Form_pg_proc procedureStruct; Form_pg_type typeStruct; SQLFunctionCachePtr fcache; Oid *argOidVect; bool haspolyarg; char *src; int nargs; List *queryTree_list; Datum tmp; bool isNull; fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache)); /* * get the procedure tuple corresponding to the given function Oid */ procedureTuple = SearchSysCache(PROCOID, ObjectIdGetDatum(foid), 0, 0, 0); if (!HeapTupleIsValid(procedureTuple)) elog(ERROR, "cache lookup failed for function %u", foid); procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); /* * get the result type from the procedure tuple, and check for polymorphic * result type; if so, find out the actual result type. */ rettype = procedureStruct->prorettype; if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) { rettype = get_fn_expr_rettype(finfo); if (rettype == InvalidOid) /* this probably should not happen */ ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("could not determine actual result type for function declared to return type %s", format_type_be(procedureStruct->prorettype)))); } fcache->rettype = rettype; /* Remember if function is STABLE/IMMUTABLE */ fcache->readonly_func = (procedureStruct->provolatile != PROVOLATILE_VOLATILE); /* Now look up the actual result type */ typeTuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(rettype), 0, 0, 0); if (!HeapTupleIsValid(typeTuple)) elog(ERROR, "cache lookup failed for type %u", rettype); typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); /* * get the type length and by-value flag from the type tuple; also do a * preliminary check for returnsTuple (this may prove inaccurate, see * below). */ fcache->typlen = typeStruct->typlen; fcache->typbyval = typeStruct->typbyval; fcache->returnsTuple = (typeStruct->typtype == 'c' || rettype == RECORDOID); /* * Parse and rewrite the queries. We need the argument type info to pass * to the parser. */ nargs = procedureStruct->pronargs; haspolyarg = false; if (nargs > 0) { int argnum; argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); memcpy(argOidVect, procedureStruct->proargtypes.values, nargs * sizeof(Oid)); /* Resolve any polymorphic argument types */ for (argnum = 0; argnum < nargs; argnum++) { Oid argtype = argOidVect[argnum]; if (argtype == ANYARRAYOID || argtype == ANYELEMENTOID) { argtype = get_fn_expr_argtype(finfo, argnum); if (argtype == InvalidOid) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("could not determine actual type of argument declared %s", format_type_be(argOidVect[argnum])))); argOidVect[argnum] = argtype; haspolyarg = true; } } } else argOidVect = NULL; fcache->argtypes = argOidVect; tmp = SysCacheGetAttr(PROCOID, procedureTuple, Anum_pg_proc_prosrc, &isNull); if (isNull) elog(ERROR, "null prosrc for function %u", foid); src = DatumGetCString(DirectFunctionCall1(textout, tmp)); queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs); /* * If the function has any arguments declared as polymorphic types, then * it wasn't type-checked at definition time; must do so now. * * Also, force a type-check if the declared return type is a rowtype; we * need to find out whether we are actually returning the whole tuple * result, or just regurgitating a rowtype expression result. In the * latter case we clear returnsTuple because we need not act different * from the scalar result case. * * In the returnsTuple case, check_sql_fn_retval will also construct a * JunkFilter we can use to coerce the returned rowtype to the desired * form. */ if (haspolyarg || fcache->returnsTuple) fcache->returnsTuple = check_sql_fn_retval(foid, rettype, queryTree_list, &fcache->junkFilter); /* Finally, plan the queries */ fcache->func_state = init_execution_state(queryTree_list, fcache->readonly_func); pfree(src); ReleaseSysCache(typeTuple); ReleaseSysCache(procedureTuple); finfo->fn_extra = (void *) fcache;}static voidpostquel_start(execution_state *es, SQLFunctionCachePtr fcache){ Snapshot snapshot; Assert(es->qd == NULL); /* * In a read-only function, use the surrounding query's snapshot; * otherwise take a new snapshot for each query. The snapshot should * include a fresh command ID so that all work to date in this transaction * is visible. We copy in both cases so that postquel_end can * unconditionally do FreeSnapshot. */ if (fcache->readonly_func) snapshot = CopySnapshot(ActiveSnapshot); else { CommandCounterIncrement(); snapshot = CopySnapshot(GetTransactionSnapshot()); } es->qd = CreateQueryDesc(es->query, es->plan, snapshot, InvalidSnapshot, None_Receiver, fcache->paramLI, false); /* We assume we don't need to set up ActiveSnapshot for ExecutorStart */ /* Utility commands don't need Executor. */ if (es->qd->operation != CMD_UTILITY) { AfterTriggerBeginQuery(); ExecutorStart(es->qd, false); } es->status = F_EXEC_RUN;}static TupleTableSlot *postquel_getnext(execution_state *es){ TupleTableSlot *result; Snapshot saveActiveSnapshot; long count; /* Make our snapshot the active one for any called functions */ saveActiveSnapshot = ActiveSnapshot; PG_TRY(); { ActiveSnapshot = es->qd->snapshot; if (es->qd->operation == CMD_UTILITY) { ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params, es->qd->dest, NULL); result = NULL; } else { /* * If it's the function's last command, and it's a SELECT, fetch * one row at a time so we can return the results. Otherwise just * run it to completion. (If we run to completion then * ExecutorRun is guaranteed to return NULL.) */ if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT) count = 1L; else count = 0L; result = ExecutorRun(es->qd, ForwardScanDirection, count); } } PG_CATCH(); { /* Restore global vars and propagate error */ ActiveSnapshot = saveActiveSnapshot; PG_RE_THROW(); } PG_END_TRY(); ActiveSnapshot = saveActiveSnapshot; return result;}static voidpostquel_end(execution_state *es){ Snapshot saveActiveSnapshot; /* mark status done to ensure we don't do ExecutorEnd twice */ es->status = F_EXEC_DONE; /* Utility commands don't need Executor. */ if (es->qd->operation != CMD_UTILITY) { /* Make our snapshot the active one for any called functions */ saveActiveSnapshot = ActiveSnapshot; PG_TRY(); { ActiveSnapshot = es->qd->snapshot; AfterTriggerEndQuery(es->qd->estate); ExecutorEnd(es->qd); } PG_CATCH(); { /* Restore global vars and propagate error */ ActiveSnapshot = saveActiveSnapshot; PG_RE_THROW(); } PG_END_TRY(); ActiveSnapshot = saveActiveSnapshot; } FreeSnapshot(es->qd->snapshot); FreeQueryDesc(es->qd); es->qd = NULL;}/* Build ParamListInfo array representing current arguments */static voidpostquel_sub_params(SQLFunctionCachePtr fcache, FunctionCallInfo fcinfo){ ParamListInfo paramLI; int nargs = fcinfo->nargs; if (nargs > 0) { int i; paramLI = (ParamListInfo) palloc0((nargs + 1) * sizeof(ParamListInfoData)); for (i = 0; i < nargs; i++) { paramLI[i].kind = PARAM_NUM; paramLI[i].id = i + 1; paramLI[i].ptype = fcache->argtypes[i]; paramLI[i].value = fcinfo->arg[i]; paramLI[i].isnull = fcinfo->argnull[i]; } paramLI[nargs].kind = PARAM_INVALID; } else paramLI = NULL; if (fcache->paramLI) pfree(fcache->paramLI); fcache->paramLI = paramLI;}static Datumpostquel_execute(execution_state *es, FunctionCallInfo fcinfo, SQLFunctionCachePtr fcache, MemoryContext resultcontext){ TupleTableSlot *slot; Datum value; MemoryContext oldcontext; if (es->status == F_EXEC_START) postquel_start(es, fcache); slot = postquel_getnext(es); if (TupIsNull(slot)) { /* * We fall out here for all cases except where we have obtained a row * from a function's final SELECT. */ postquel_end(es); fcinfo->isnull = true; return (Datum) NULL; } /* * If we got a row from a command within the function it has to be the * final command. All others shouldn't be returning anything. */ Assert(LAST_POSTQUEL_COMMAND(es)); /* * Set up to return the function value. For pass-by-reference datatypes, * be sure to allocate the result in resultcontext, not the current memory * context (which has query lifespan). */ oldcontext = MemoryContextSwitchTo(resultcontext); if (fcache->returnsTuple) { /* * We are returning the whole tuple, so filter it and apply the proper * labeling to make it a valid Datum. There are several reasons why * we do this: * * 1. To copy the tuple out of the child execution context and into * the desired result context. * * 2. To remove any junk attributes present in the raw subselect * result. (This is probably not absolutely necessary, but it seems * like good policy.) * * 3. To insert dummy null columns if the declared result type has any * attisdropped columns. */ HeapTuple newtup; HeapTupleHeader dtup; uint32 t_len; Oid dtuptype; int32 dtuptypmod; newtup = ExecRemoveJunk(fcache->junkFilter, slot); /* * Compress out the HeapTuple header data. We assume that * heap_form_tuple made the tuple with header and body in one palloc'd * chunk. We want to return a pointer to the chunk start so that it * will work if someone tries to free it. */ t_len = newtup->t_len; dtup = (HeapTupleHeader) newtup; memmove((char *) dtup, (char *) newtup->t_data, t_len); /* * Use the declared return type if it's not RECORD; else take the type * from the computed result, making sure a typmod has been assigned. */ if (fcache->rettype != RECORDOID) { /* function has a named composite return type */ dtuptype = fcache->rettype;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -