aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/functions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r--src/backend/executor/functions.c238
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);