1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* functions.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* Routines to handle functions called from the executor
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2002-06-20 22:29:54 +02:00
|
|
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2002-09-04 22:31:48 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.57 2002/09/04 20:31:18 momjian Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "access/heapam.h"
|
2000-08-24 05:29:15 +02:00
|
|
|
#include "catalog/pg_proc.h"
|
|
|
|
#include "catalog/pg_type.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "executor/execdefs.h"
|
|
|
|
#include "executor/executor.h"
|
|
|
|
#include "executor/functions.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
#include "tcop/pquery.h"
|
|
|
|
#include "tcop/tcopprot.h"
|
1996-10-26 06:15:05 +02:00
|
|
|
#include "tcop/utility.h"
|
2000-08-24 05:29:15 +02:00
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/syscache.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
/*
|
2002-05-12 22:10:05 +02:00
|
|
|
* We have an execution_state record for each query in a function.
|
2000-08-24 05:29:15 +02:00
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
typedef enum
|
|
|
|
{
|
2001-10-28 07:26:15 +01:00
|
|
|
F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
|
1997-09-08 23:56:23 +02:00
|
|
|
} ExecStatus;
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
typedef struct local_es
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
QueryDesc *qd;
|
|
|
|
EState *estate;
|
1997-09-07 07:04:48 +02:00
|
|
|
struct local_es *next;
|
1997-09-08 04:41:22 +02:00
|
|
|
ExecStatus status;
|
1997-09-08 23:56:23 +02:00
|
|
|
} execution_state;
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2000-05-28 19:56:29 +02:00
|
|
|
#define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *) NULL)
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 */
|
2002-05-12 22:10:05 +02:00
|
|
|
bool shutdown_reg; /* true if registered shutdown callback */
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/* non-export function prototypes */
|
2000-08-24 05:29:15 +02:00
|
|
|
static execution_state *init_execution_state(char *src,
|
2001-03-22 05:01:46 +01:00
|
|
|
Oid *argOidVect, int nargs);
|
2000-08-24 05:29:15 +02:00
|
|
|
static void init_sql_fcache(FmgrInfo *finfo);
|
2001-01-29 01:39:20 +01:00
|
|
|
static void postquel_start(execution_state *es);
|
1997-09-08 23:56:23 +02:00
|
|
|
static TupleTableSlot *postquel_getnext(execution_state *es);
|
|
|
|
static void postquel_end(execution_state *es);
|
2000-05-28 19:56:29 +02:00
|
|
|
static void postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo);
|
|
|
|
static Datum postquel_execute(execution_state *es,
|
2001-03-22 05:01:46 +01:00
|
|
|
FunctionCallInfo fcinfo,
|
|
|
|
SQLFunctionCachePtr fcache);
|
2002-05-12 22:10:05 +02:00
|
|
|
static void ShutdownSQLFunction(Datum arg);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
static execution_state *
|
2000-08-24 05:29:15 +02:00
|
|
|
init_execution_state(char *src, Oid *argOidVect, int nargs)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2002-08-23 18:41:38 +02:00
|
|
|
execution_state *firstes;
|
1997-09-07 07:04:48 +02:00
|
|
|
execution_state *preves;
|
1999-05-13 09:29:22 +02:00
|
|
|
List *queryTree_list,
|
|
|
|
*qtl_item;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-08-23 18:41:38 +02:00
|
|
|
firstes = NULL;
|
|
|
|
preves = NULL;
|
|
|
|
|
1999-05-25 18:15:34 +02:00
|
|
|
foreach(qtl_item, queryTree_list)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
1999-05-13 09:29:22 +02:00
|
|
|
Query *queryTree = lfirst(qtl_item);
|
2000-04-04 23:44:40 +02:00
|
|
|
Plan *planTree;
|
2002-08-23 18:41:38 +02:00
|
|
|
execution_state *newes;
|
1999-05-13 09:29:22 +02:00
|
|
|
EState *estate;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-04-04 23:44:40 +02:00
|
|
|
planTree = pg_plan_query(queryTree);
|
|
|
|
|
2002-08-23 18:41:38 +02:00
|
|
|
newes = (execution_state *) palloc(sizeof(execution_state));
|
1997-09-07 07:04:48 +02:00
|
|
|
if (preves)
|
2002-08-23 18:41:38 +02:00
|
|
|
preves->next = newes;
|
|
|
|
else
|
|
|
|
firstes = newes;
|
2002-02-27 20:36:13 +01:00
|
|
|
|
2002-08-23 18:41:38 +02:00
|
|
|
newes->next = NULL;
|
|
|
|
newes->status = F_EXEC_START;
|
|
|
|
newes->qd = CreateQueryDesc(queryTree, planTree, None, NULL);
|
|
|
|
newes->estate = estate = CreateExecutorState();
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (nargs > 0)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
int i;
|
|
|
|
ParamListInfo paramLI;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-02-03 22:18:02 +01:00
|
|
|
paramLI = (ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-08-23 18:41:38 +02:00
|
|
|
MemSet(paramLI, 0, (nargs + 1) * sizeof(ParamListInfoData));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
estate->es_param_list_info = paramLI;
|
|
|
|
|
|
|
|
for (i = 0; i < nargs; paramLI++, i++)
|
|
|
|
{
|
|
|
|
paramLI->kind = PARAM_NUM;
|
|
|
|
paramLI->id = i + 1;
|
|
|
|
paramLI->isnull = false;
|
|
|
|
paramLI->value = (Datum) NULL;
|
|
|
|
}
|
|
|
|
paramLI->kind = PARAM_INVALID;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
estate->es_param_list_info = (ParamListInfo) NULL;
|
2002-08-23 18:41:38 +02:00
|
|
|
|
|
|
|
preves = newes;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-08-23 18:41:38 +02:00
|
|
|
return firstes;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* get the procedure tuple corresponding to the given function Oid
|
2000-08-24 05:29:15 +02:00
|
|
|
*/
|
2000-11-16 23:30:52 +01:00
|
|
|
procedureTuple = SearchSysCache(PROCOID,
|
|
|
|
ObjectIdGetDatum(foid),
|
|
|
|
0, 0, 0);
|
2000-08-24 05:29:15 +02:00
|
|
|
if (!HeapTupleIsValid(procedureTuple))
|
|
|
|
elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u",
|
|
|
|
foid);
|
|
|
|
|
|
|
|
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* get the return type from the procedure tuple
|
2000-08-24 05:29:15 +02:00
|
|
|
*/
|
2000-11-16 23:30:52 +01:00
|
|
|
typeTuple = SearchSysCache(TYPEOID,
|
2001-03-22 05:01:46 +01:00
|
|
|
ObjectIdGetDatum(procedureStruct->prorettype),
|
2000-11-16 23:30:52 +01:00
|
|
|
0, 0, 0);
|
2000-08-24 05:29:15 +02:00
|
|
|
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));
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* get the type length and by-value flag from the type tuple
|
2000-08-24 05:29:15 +02:00
|
|
|
*/
|
|
|
|
fcache->typlen = typeStruct->typlen;
|
Attached are two patches to implement and document anonymous composite
types for Table Functions, as previously proposed on HACKERS. Here is a
brief explanation:
1. Creates a new pg_type typtype: 'p' for pseudo type (currently either
'b' for base or 'c' for catalog, i.e. a class).
2. Creates new builtin type of typtype='p' named RECORD. This is the
first of potentially several pseudo types.
3. Modify FROM clause grammer to accept:
SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...)
where m is the table alias, colname1, etc are the column names, and
type1, etc are the column types.
4. When typtype == 'p' and the function return type is RECORD, a list
of column defs is required, and when typtype != 'p', it is
disallowed.
5. A check was added to ensure that the tupdesc provide via the parser
and the actual return tupdesc match in number and type of
attributes.
When creating a function you can do:
CREATE FUNCTION foo(text) RETURNS setof RECORD ...
When using it you can do:
SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp)
or
SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp)
or
SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp)
Included in the patches are adjustments to the regression test sql and
expected files, and documentation.
p.s.
This potentially solves (or at least improves) the issue of builtin
Table Functions. They can be bootstrapped as returning RECORD, and
we can wrap system views around them with properly specified column
defs. For example:
CREATE VIEW pg_settings AS
SELECT s.name, s.setting
FROM show_all_settings()AS s(name text, setting text);
Then we can also add the UPDATE RULE that I previously posted to
pg_settings, and have pg_settings act like a virtual table, allowing
settings to be queried and set.
Joe Conway
2002-08-04 21:48:11 +02:00
|
|
|
|
2002-08-29 02:17:06 +02:00
|
|
|
if (typeStruct->typtype != 'c' &&
|
|
|
|
procedureStruct->prorettype != RECORDOID)
|
2000-08-24 05:29:15 +02:00
|
|
|
{
|
2002-08-29 02:17:06 +02:00
|
|
|
/* The return type is not a composite type, so just use byval */
|
2000-08-24 05:29:15 +02:00
|
|
|
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)
|
2001-01-29 01:39:20 +01:00
|
|
|
fcache->funcSlot = MakeTupleTableSlot();
|
2000-08-24 05:29:15 +02:00
|
|
|
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);
|
|
|
|
|
2000-11-16 23:30:52 +01:00
|
|
|
ReleaseSysCache(typeTuple);
|
|
|
|
ReleaseSysCache(procedureTuple);
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
finfo->fn_extra = (void *) fcache;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-01-29 01:39:20 +01:00
|
|
|
static void
|
1997-09-08 23:56:23 +02:00
|
|
|
postquel_start(execution_state *es)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
|
|
|
* Do nothing for utility commands. (create, destroy...) DZ -
|
|
|
|
* 30-8-1996
|
|
|
|
*/
|
|
|
|
if (es->qd->operation == CMD_UTILITY)
|
2001-01-29 01:39:20 +01:00
|
|
|
return;
|
|
|
|
ExecutorStart(es->qd, es->estate);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static TupleTableSlot *
|
1997-09-08 23:56:23 +02:00
|
|
|
postquel_getnext(execution_state *es)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2002-09-04 22:31:48 +02:00
|
|
|
long count;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (es->qd->operation == CMD_UTILITY)
|
|
|
|
{
|
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Process a utility command. (create, destroy...) DZ - 30-8-1996
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2002-02-26 23:47:12 +01:00
|
|
|
ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->dest, NULL);
|
1997-09-07 07:04:48 +02:00
|
|
|
if (!LAST_POSTQUEL_COMMAND(es))
|
|
|
|
CommandCounterIncrement();
|
|
|
|
return (TupleTableSlot *) NULL;
|
|
|
|
}
|
1996-09-16 07:36:38 +02:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
/* If it's not the last command, just run it to completion */
|
|
|
|
count = (LAST_POSTQUEL_COMMAND(es)) ? 1L : 0L;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2002-02-27 20:36:13 +01:00
|
|
|
return ExecutorRun(es->qd, es->estate, ForwardScanDirection, count);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
1997-09-08 23:56:23 +02:00
|
|
|
postquel_end(execution_state *es)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
|
|
|
* Do nothing for utility commands. (create, destroy...) DZ -
|
|
|
|
* 30-8-1996
|
|
|
|
*/
|
|
|
|
if (es->qd->operation == CMD_UTILITY)
|
|
|
|
return;
|
|
|
|
ExecutorEnd(es->qd, es->estate);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2000-05-28 19:56:29 +02:00
|
|
|
postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
EState *estate;
|
2000-05-28 19:56:29 +02:00
|
|
|
ParamListInfo paramLI;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
estate = es->estate;
|
|
|
|
paramLI = estate->es_param_list_info;
|
|
|
|
|
|
|
|
while (paramLI->kind != PARAM_INVALID)
|
|
|
|
{
|
|
|
|
if (paramLI->kind == PARAM_NUM)
|
|
|
|
{
|
2000-05-28 19:56:29 +02:00
|
|
|
Assert(paramLI->id <= fcinfo->nargs);
|
|
|
|
paramLI->value = fcinfo->arg[paramLI->id - 1];
|
|
|
|
paramLI->isnull = fcinfo->argnull[paramLI->id - 1];
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
paramLI++;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static TupleTableSlot *
|
2000-08-24 05:29:15 +02:00
|
|
|
copy_function_result(SQLFunctionCachePtr fcache,
|
1997-09-08 23:56:23 +02:00
|
|
|
TupleTableSlot *resultSlot)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
TupleTableSlot *funcSlot;
|
1997-09-08 04:41:22 +02:00
|
|
|
TupleDesc resultTd;
|
2000-08-08 17:43:12 +02:00
|
|
|
HeapTuple resultTuple;
|
1997-09-08 04:41:22 +02:00
|
|
|
HeapTuple newTuple;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
Assert(!TupIsNull(resultSlot));
|
2000-08-08 17:43:12 +02:00
|
|
|
resultTuple = resultSlot->val;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
funcSlot = fcache->funcSlot;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
if (funcSlot == NULL)
|
|
|
|
return resultSlot; /* no need to copy result */
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* If first time through, we have to initialize the funcSlot's tuple
|
|
|
|
* descriptor.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2001-01-29 01:39:20 +01:00
|
|
|
if (funcSlot->ttc_tupleDescriptor == NULL)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2001-01-29 01:39:20 +01:00
|
|
|
resultTd = CreateTupleDescCopy(resultSlot->ttc_tupleDescriptor);
|
|
|
|
ExecSetSlotDescriptor(funcSlot, resultTd, true);
|
|
|
|
ExecSetSlotDescriptorIsNew(funcSlot, true);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-08-08 17:43:12 +02:00
|
|
|
newTuple = heap_copytuple(resultTuple);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
return ExecStoreTuple(newTuple, funcSlot, InvalidBuffer, true);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
1997-09-08 04:41:22 +02:00
|
|
|
static Datum
|
1997-09-08 23:56:23 +02:00
|
|
|
postquel_execute(execution_state *es,
|
2000-05-28 19:56:29 +02:00
|
|
|
FunctionCallInfo fcinfo,
|
2000-08-24 05:29:15 +02:00
|
|
|
SQLFunctionCachePtr fcache)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
TupleTableSlot *slot;
|
1997-09-08 04:41:22 +02:00
|
|
|
Datum value;
|
1997-01-22 06:26:50 +01:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
|
|
|
* It's more right place to do it (before
|
|
|
|
* postquel_start->ExecutorStart). Now
|
|
|
|
* ExecutorStart->ExecInitIndexScan->ExecEvalParam works ok. (But
|
|
|
|
* note: I HOPE we can do it here). - vadim 01/22/97
|
|
|
|
*/
|
2000-05-28 19:56:29 +02:00
|
|
|
if (fcinfo->nargs > 0)
|
|
|
|
postquel_sub_params(es, fcinfo);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (es->status == F_EXEC_START)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
postquel_start(es);
|
|
|
|
es->status = F_EXEC_RUN;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
slot = postquel_getnext(es);
|
|
|
|
|
|
|
|
if (TupIsNull(slot))
|
|
|
|
{
|
|
|
|
postquel_end(es);
|
|
|
|
es->status = F_EXEC_DONE;
|
2000-05-28 19:56:29 +02:00
|
|
|
fcinfo->isnull = true;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If this isn't the last command for the function we have to
|
|
|
|
* increment the command counter so that subsequent commands can
|
|
|
|
* see changes made by previous ones.
|
|
|
|
*/
|
|
|
|
if (!LAST_POSTQUEL_COMMAND(es))
|
|
|
|
CommandCounterIncrement();
|
|
|
|
return (Datum) NULL;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (LAST_POSTQUEL_COMMAND(es))
|
|
|
|
{
|
|
|
|
TupleTableSlot *resSlot;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy the result. copy_function_result is smart enough to do
|
|
|
|
* nothing when no action is called for. This helps reduce the
|
|
|
|
* logic and code redundancy here.
|
|
|
|
*/
|
|
|
|
resSlot = copy_function_result(fcache, slot);
|
|
|
|
|
2000-08-08 17:43:12 +02:00
|
|
|
/*
|
|
|
|
* If we are supposed to return a tuple, we return the tuple slot
|
2001-03-22 05:01:46 +01:00
|
|
|
* pointer converted to Datum. If we are supposed to return a
|
|
|
|
* simple value, then project out the first attribute of the
|
|
|
|
* result tuple (ie, take the first result column of the final
|
|
|
|
* SELECT).
|
2000-08-08 17:43:12 +02:00
|
|
|
*/
|
|
|
|
if (fcache->returnsTuple)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2000-08-08 17:43:12 +02:00
|
|
|
/*
|
|
|
|
* XXX do we need to remove junk attrs from the result tuple?
|
|
|
|
* Probably OK to leave them, as long as they are at the end.
|
|
|
|
*/
|
2000-05-28 19:56:29 +02:00
|
|
|
value = PointerGetDatum(resSlot);
|
|
|
|
fcinfo->isnull = false;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2000-08-08 17:43:12 +02:00
|
|
|
else
|
|
|
|
{
|
2001-01-29 01:39:20 +01:00
|
|
|
value = heap_getattr(resSlot->val,
|
|
|
|
1,
|
|
|
|
resSlot->ttc_tupleDescriptor,
|
|
|
|
&(fcinfo->isnull));
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2001-01-29 01:39:20 +01:00
|
|
|
/*
|
|
|
|
* Note: if result type is pass-by-reference then we are
|
|
|
|
* returning a pointer into the tuple copied by
|
|
|
|
* copy_function_result. This is OK.
|
|
|
|
*/
|
2000-08-08 17:43:12 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is a single valued function we have to end the function
|
|
|
|
* execution now.
|
|
|
|
*/
|
2000-08-24 05:29:15 +02:00
|
|
|
if (!fcinfo->flinfo->fn_retset)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
|
|
|
postquel_end(es);
|
|
|
|
es->status = F_EXEC_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* If this isn't the last command for the function, we don't return
|
|
|
|
* any results, but we have to increment the command counter so that
|
|
|
|
* subsequent commands can see changes made by previous ones.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
CommandCounterIncrement();
|
|
|
|
return (Datum) NULL;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
2000-08-24 05:29:15 +02:00
|
|
|
fmgr_sql(PG_FUNCTION_ARGS)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2000-07-12 04:37:39 +02:00
|
|
|
MemoryContext oldcontext;
|
2000-08-24 05:29:15 +02:00
|
|
|
SQLFunctionCachePtr fcache;
|
1997-09-07 07:04:48 +02:00
|
|
|
execution_state *es;
|
1997-09-08 04:41:22 +02:00
|
|
|
Datum result = 0;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-07-12 04:37:39 +02:00
|
|
|
/*
|
|
|
|
* Switch to context in which the fcache lives. This ensures that
|
|
|
|
* parsetrees, plans, etc, will have sufficient lifetime. The
|
|
|
|
* sub-executor is responsible for deleting per-tuple information.
|
|
|
|
*/
|
2000-08-24 05:29:15 +02:00
|
|
|
oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
|
2000-07-12 04:37:39 +02:00
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
/*
|
|
|
|
* Initialize fcache and execution state if first time through.
|
|
|
|
*/
|
|
|
|
fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
|
|
|
|
if (fcache == NULL)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2000-08-24 05:29:15 +02:00
|
|
|
init_sql_fcache(fcinfo->flinfo);
|
|
|
|
fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
2000-08-24 05:29:15 +02:00
|
|
|
es = fcache->func_state;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
/*
|
|
|
|
* Find first unfinished query in function.
|
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
while (es && es->status == F_EXEC_DONE)
|
|
|
|
es = es->next;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Execute each command in the function one after another until we're
|
|
|
|
* executing the final command and get a result or we run out of
|
|
|
|
* commands.
|
|
|
|
*/
|
2002-08-23 18:41:38 +02:00
|
|
|
while (es)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2000-08-08 17:43:12 +02:00
|
|
|
result = postquel_execute(es, fcinfo, fcache);
|
1997-09-07 07:04:48 +02:00
|
|
|
if (es->status != F_EXEC_DONE)
|
|
|
|
break;
|
|
|
|
es = es->next;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1997-08-29 11:05:57 +02:00
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* If we've gone through every command in this function, we are done.
|
1997-08-29 11:05:57 +02:00
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
if (es == (execution_state *) NULL)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
/*
|
|
|
|
* Reset the execution states to start over again
|
|
|
|
*/
|
2000-08-24 05:29:15 +02:00
|
|
|
es = fcache->func_state;
|
1997-09-07 07:04:48 +02:00
|
|
|
while (es)
|
|
|
|
{
|
|
|
|
es->status = F_EXEC_START;
|
|
|
|
es = es->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Let caller know we're finished.
|
|
|
|
*/
|
2000-08-24 05:29:15 +02:00
|
|
|
if (fcinfo->flinfo->fn_retset)
|
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
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;
|
2002-05-12 22:10:05 +02:00
|
|
|
|
|
|
|
/* Deregister shutdown callback, if we made one */
|
|
|
|
if (fcache->shutdown_reg)
|
|
|
|
{
|
|
|
|
UnregisterExprContextCallback(rsi->econtext,
|
|
|
|
ShutdownSQLFunction,
|
|
|
|
PointerGetDatum(fcache));
|
|
|
|
fcache->shutdown_reg = false;
|
|
|
|
}
|
2000-08-24 05:29:15 +02:00
|
|
|
}
|
|
|
|
|
2000-07-12 04:37:39 +02:00
|
|
|
MemoryContextSwitchTo(oldcontext);
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
return result;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1997-08-29 11:05:57 +02:00
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* If we got a result from a command within the function it has to be
|
2000-05-28 19:56:29 +02:00
|
|
|
* the final command. All others shouldn't be returning anything.
|
1997-08-29 11:05:57 +02:00
|
|
|
*/
|
1997-09-07 07:04:48 +02:00
|
|
|
Assert(LAST_POSTQUEL_COMMAND(es));
|
|
|
|
|
2000-08-24 05:29:15 +02:00
|
|
|
/*
|
|
|
|
* Let caller know we're not finished.
|
|
|
|
*/
|
|
|
|
if (fcinfo->flinfo->fn_retset)
|
|
|
|
{
|
2001-03-22 05:01:46 +01:00
|
|
|
ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
|
2000-08-24 05:29:15 +02:00
|
|
|
|
|
|
|
if (rsi && IsA(rsi, ReturnSetInfo))
|
|
|
|
rsi->isDone = ExprMultipleResult;
|
|
|
|
else
|
|
|
|
elog(ERROR, "Set-valued function called in context that cannot accept a set");
|
2002-05-12 22:10:05 +02:00
|
|
|
|
|
|
|
/*
|
2002-09-04 22:31:48 +02:00
|
|
|
* Ensure we will get shut down cleanly if the exprcontext is not
|
|
|
|
* run to completion.
|
2002-05-12 22:10:05 +02:00
|
|
|
*/
|
|
|
|
if (!fcache->shutdown_reg)
|
|
|
|
{
|
|
|
|
RegisterExprContextCallback(rsi->econtext,
|
|
|
|
ShutdownSQLFunction,
|
|
|
|
PointerGetDatum(fcache));
|
|
|
|
fcache->shutdown_reg = true;
|
|
|
|
}
|
2000-08-24 05:29:15 +02:00
|
|
|
}
|
2000-07-12 04:37:39 +02:00
|
|
|
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
return result;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
2002-05-12 22:10:05 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* callback function in case a function-returning-set needs to be shut down
|
|
|
|
* before it has been run to completion
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ShutdownSQLFunction(Datum arg)
|
|
|
|
{
|
|
|
|
SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) DatumGetPointer(arg);
|
|
|
|
execution_state *es = fcache->func_state;
|
|
|
|
|
|
|
|
while (es != NULL)
|
|
|
|
{
|
|
|
|
/* Shut down anything still running */
|
|
|
|
if (es->status == F_EXEC_RUN)
|
|
|
|
postquel_end(es);
|
|
|
|
/* Reset states to START in case we're called again */
|
|
|
|
es->status = F_EXEC_START;
|
|
|
|
es = es->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* execUtils will deregister the callback... */
|
|
|
|
fcache->shutdown_reg = false;
|
|
|
|
}
|