diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2000-08-24 03:29:15 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2000-08-24 03:29:15 +0000 |
commit | 782c16c6a154e760bf1608d633488538cd52da93 (patch) | |
tree | 902da787593da21a979bd2f74b0b44acf9c427b0 /src/backend/executor/functions.c | |
parent | 87523ab8db34859ae3fb980a3fab9f29dfc4c97a (diff) | |
download | postgresql-782c16c6a154e760bf1608d633488538cd52da93.tar.gz postgresql-782c16c6a154e760bf1608d633488538cd52da93.zip |
SQL-language functions are now callable in ordinary fmgr contexts ...
for example, an SQL function can be used in a functional index. (I make
no promises about speed, but it'll work ;-).) Clean up and simplify
handling of functions returning sets.
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r-- | src/backend/executor/functions.c | 238 |
1 files changed, 213 insertions, 25 deletions
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 001feb267ff..58fb68a6113 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,22 +8,29 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.37 2000/08/08 15:41:22 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.38 2000/08/24 03:29:03 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" #include "executor/execdefs.h" #include "executor/executor.h" #include "executor/functions.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" +#include "utils/builtins.h" #include "utils/datum.h" +#include "utils/syscache.h" +/* + * We have an execution_state record for each query in the function. + */ typedef enum { F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE @@ -39,15 +46,40 @@ typedef struct local_es #define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *) NULL) + +/* + * An SQLFunctionCache record is built during the first call, + * and linked to from the fn_extra field of the FmgrInfo struct. + */ + +typedef struct +{ + int typlen; /* length of the return type */ + bool typbyval; /* true if return type is pass by value */ + bool returnsTuple; /* true if return type is a tuple */ + + TupleTableSlot *funcSlot; /* if one result we need to copy it before + * we end execution of the function and + * free stuff */ + + /* 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(char *src, + Oid *argOidVect, int nargs); +static void init_sql_fcache(FmgrInfo *finfo); static TupleDesc postquel_start(execution_state *es); -static execution_state *init_execution_state(FunctionCachePtr fcache); static TupleTableSlot *postquel_getnext(execution_state *es); static void postquel_end(execution_state *es); static void postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo); static Datum postquel_execute(execution_state *es, FunctionCallInfo fcinfo, - FunctionCachePtr fcache); + SQLFunctionCachePtr fcache); static Datum @@ -69,21 +101,19 @@ ProjectAttribute(HeapTuple tup, } static execution_state * -init_execution_state(FunctionCachePtr fcache) +init_execution_state(char *src, Oid *argOidVect, int nargs) { execution_state *newes; execution_state *nextes; execution_state *preves; List *queryTree_list, *qtl_item; - int nargs = fcache->nargs; newes = (execution_state *) palloc(sizeof(execution_state)); nextes = newes; preves = (execution_state *) NULL; - queryTree_list = pg_parse_and_rewrite(fcache->src, - fcache->argOidVect, nargs); + queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs); foreach(qtl_item, queryTree_list) { @@ -138,6 +168,134 @@ init_execution_state(FunctionCachePtr fcache) return newes; } + +static void +init_sql_fcache(FmgrInfo *finfo) +{ + Oid foid = finfo->fn_oid; + HeapTuple procedureTuple; + HeapTuple typeTuple; + Form_pg_proc procedureStruct; + Form_pg_type typeStruct; + SQLFunctionCachePtr fcache; + Oid *argOidVect; + char *src; + int nargs; + Datum tmp; + bool isNull; + + /* ---------------- + * get the procedure tuple corresponding to the given function Oid + * + * NB: use SearchSysCacheTupleCopy to ensure tuple lives long enough + * ---------------- + */ + procedureTuple = SearchSysCacheTupleCopy(PROCOID, + ObjectIdGetDatum(foid), + 0, 0, 0); + + if (!HeapTupleIsValid(procedureTuple)) + elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u", + foid); + + procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + + /* ---------------- + * get the return type from the procedure tuple + * ---------------- + */ + typeTuple = SearchSysCacheTuple(TYPEOID, + ObjectIdGetDatum(procedureStruct->prorettype), + 0, 0, 0); + + if (!HeapTupleIsValid(typeTuple)) + elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u", + procedureStruct->prorettype); + + typeStruct = (Form_pg_type) GETSTRUCT(typeTuple); + + fcache = (SQLFunctionCachePtr) palloc(sizeof(SQLFunctionCache)); + MemSet(fcache, 0, sizeof(SQLFunctionCache)); + + /* ---------------- + * get the type length and by-value flag from the type tuple + * ---------------- + */ + fcache->typlen = typeStruct->typlen; + if (typeStruct->typrelid == InvalidOid) + { + /* The return type is not a relation, so just use byval */ + fcache->typbyval = typeStruct->typbyval; + fcache->returnsTuple = false; + } + else + { + + /* + * This is a hack. We assume here that any function returning a + * tuple returns it by reference. This needs to be fixed, since + * actually the mechanism isn't quite like return-by-reference. + */ + fcache->typbyval = false; + fcache->returnsTuple = true; + } + + /* + * If we are returning exactly one result then we have to copy tuples + * and by reference results because we have to end the execution + * before we return the results. When you do this everything + * allocated by the executor (i.e. slots and tuples) is freed. + */ + if (!finfo->fn_retset && !fcache->typbyval) + { + TupleTableSlot *slot; + + slot = makeNode(TupleTableSlot); + slot->val = (HeapTuple) NULL; + slot->ttc_shouldFree = true; + slot->ttc_descIsNew = true; + slot->ttc_tupleDescriptor = (TupleDesc) NULL; + slot->ttc_buffer = InvalidBuffer; + slot->ttc_whichplan = -1; + + fcache->funcSlot = slot; + } + else + fcache->funcSlot = NULL; + + nargs = procedureStruct->pronargs; + + if (nargs > 0) + { + argOidVect = (Oid *) palloc(nargs * sizeof(Oid)); + memcpy(argOidVect, + procedureStruct->proargtypes, + nargs * sizeof(Oid)); + } + else + { + argOidVect = (Oid *) NULL; + } + + tmp = SysCacheGetAttr(PROCOID, + procedureTuple, + Anum_pg_proc_prosrc, + &isNull); + if (isNull) + elog(ERROR, "init_sql_fcache: null prosrc for procedure %u", + foid); + src = DatumGetCString(DirectFunctionCall1(textout, tmp)); + + fcache->func_state = init_execution_state(src, argOidVect, nargs); + + pfree(src); + + heap_freetuple(procedureTuple); + + finfo->fn_extra = (void *) fcache; +} + + static TupleDesc postquel_start(execution_state *es) { @@ -208,7 +366,7 @@ postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo) } static TupleTableSlot * -copy_function_result(FunctionCachePtr fcache, +copy_function_result(SQLFunctionCachePtr fcache, TupleTableSlot *resultSlot) { TupleTableSlot *funcSlot; @@ -219,10 +377,10 @@ copy_function_result(FunctionCachePtr fcache, Assert(!TupIsNull(resultSlot)); resultTuple = resultSlot->val; - funcSlot = (TupleTableSlot *) fcache->funcSlot; + funcSlot = fcache->funcSlot; - if (funcSlot == (TupleTableSlot *) NULL) - return resultSlot; + if (funcSlot == NULL) + return resultSlot; /* no need to copy result */ /* * If first time through, we have to initialize the funcSlot's @@ -243,7 +401,7 @@ copy_function_result(FunctionCachePtr fcache, static Datum postquel_execute(execution_state *es, FunctionCallInfo fcinfo, - FunctionCachePtr fcache) + SQLFunctionCachePtr fcache) { TupleTableSlot *slot; Datum value; @@ -319,7 +477,7 @@ postquel_execute(execution_state *es, * If this is a single valued function we have to end the function * execution now. */ - if (!fcache->returnsSet) + if (!fcinfo->flinfo->fn_retset) { postquel_end(es); es->status = F_EXEC_DONE; @@ -338,11 +496,10 @@ postquel_execute(execution_state *es, } Datum -postquel_function(FunctionCallInfo fcinfo, - FunctionCachePtr fcache, - bool *isDone) +fmgr_sql(PG_FUNCTION_ARGS) { MemoryContext oldcontext; + SQLFunctionCachePtr fcache; execution_state *es; Datum result = 0; CommandId savedId; @@ -352,7 +509,7 @@ postquel_function(FunctionCallInfo fcinfo, * parsetrees, plans, etc, will have sufficient lifetime. The * sub-executor is responsible for deleting per-tuple information. */ - oldcontext = MemoryContextSwitchTo(fcache->fcacheCxt); + oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); /* * Before we start do anything we must save CurrentScanCommandId to @@ -362,13 +519,21 @@ postquel_function(FunctionCallInfo fcinfo, savedId = GetScanCommandId(); SetScanCommandId(GetCurrentCommandId()); - es = (execution_state *) fcache->func_state; - if (es == NULL) + /* + * Initialize fcache and execution state if first time through. + */ + fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra; + if (fcache == NULL) { - es = init_execution_state(fcache); - fcache->func_state = (char *) es; + init_sql_fcache(fcinfo->flinfo); + fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra; } + es = fcache->func_state; + Assert(es); + /* + * Find first unfinished query in function. + */ while (es && es->status == F_EXEC_DONE) es = es->next; @@ -401,7 +566,7 @@ postquel_function(FunctionCallInfo fcinfo, /* * Reset the execution states to start over again */ - es = (execution_state *) fcache->func_state; + es = fcache->func_state; while (es) { es->status = F_EXEC_START; @@ -411,9 +576,21 @@ postquel_function(FunctionCallInfo fcinfo, /* * Let caller know we're finished. */ - *isDone = true; + if (fcinfo->flinfo->fn_retset) + { + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + if (rsi && IsA(rsi, ReturnSetInfo)) + rsi->isDone = ExprEndResult; + else + elog(ERROR, "Set-valued function called in context that cannot accept a set"); + fcinfo->isnull = true; + result = (Datum) 0; + } + MemoryContextSwitchTo(oldcontext); - return (fcache->returnsSet) ? (Datum) NULL : result; + + return result; } /* @@ -422,7 +599,18 @@ postquel_function(FunctionCallInfo fcinfo, */ Assert(LAST_POSTQUEL_COMMAND(es)); - *isDone = false; + /* + * Let caller know we're not finished. + */ + if (fcinfo->flinfo->fn_retset) + { + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + if (rsi && IsA(rsi, ReturnSetInfo)) + rsi->isDone = ExprMultipleResult; + else + elog(ERROR, "Set-valued function called in context that cannot accept a set"); + } MemoryContextSwitchTo(oldcontext); |