1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-30 21:42:05 +03:00

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.
This commit is contained in:
Tom Lane
2000-08-24 03:29:15 +00:00
parent 87523ab8db
commit 782c16c6a1
35 changed files with 889 additions and 921 deletions

View File

@ -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);