1 /*-------------------------------------------------------------------------
4 * Routines to handle functions called from the executor
6 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.48 2002/02/26 22:47:05 tgl Exp $
13 *-------------------------------------------------------------------------
17 #include "access/heapam.h"
18 #include "catalog/pg_proc.h"
19 #include "catalog/pg_type.h"
20 #include "executor/execdefs.h"
21 #include "executor/executor.h"
22 #include "executor/functions.h"
23 #include "tcop/pquery.h"
24 #include "tcop/tcopprot.h"
25 #include "tcop/utility.h"
26 #include "utils/builtins.h"
27 #include "utils/syscache.h"
31 * We have an execution_state record for each query in the function.
35 F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
38 typedef struct local_es
42 struct local_es *next;
46 #define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *) NULL)
50 * An SQLFunctionCache record is built during the first call,
51 * and linked to from the fn_extra field of the FmgrInfo struct.
56 int typlen; /* length of the return type */
57 bool typbyval; /* true if return type is pass by value */
58 bool returnsTuple; /* true if return type is a tuple */
60 TupleTableSlot *funcSlot; /* if one result we need to copy it before
61 * we end execution of the function and
64 /* head of linked list of execution_state records */
65 execution_state *func_state;
68 typedef SQLFunctionCache *SQLFunctionCachePtr;
71 /* non-export function prototypes */
72 static execution_state *init_execution_state(char *src,
73 Oid *argOidVect, int nargs);
74 static void init_sql_fcache(FmgrInfo *finfo);
75 static void postquel_start(execution_state *es);
76 static TupleTableSlot *postquel_getnext(execution_state *es);
77 static void postquel_end(execution_state *es);
78 static void postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo);
79 static Datum postquel_execute(execution_state *es,
80 FunctionCallInfo fcinfo,
81 SQLFunctionCachePtr fcache);
84 static execution_state *
85 init_execution_state(char *src, Oid *argOidVect, int nargs)
87 execution_state *newes;
88 execution_state *nextes;
89 execution_state *preves;
93 newes = (execution_state *) palloc(sizeof(execution_state));
95 preves = (execution_state *) NULL;
97 queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
99 foreach(qtl_item, queryTree_list)
101 Query *queryTree = lfirst(qtl_item);
105 planTree = pg_plan_query(queryTree);
108 nextes = (execution_state *) palloc(sizeof(execution_state));
110 preves->next = nextes;
113 nextes->status = F_EXEC_START;
114 nextes->qd = CreateQueryDesc(queryTree,
117 estate = CreateExecutorState();
122 ParamListInfo paramLI;
124 paramLI = (ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData));
126 MemSet(paramLI, 0, nargs * sizeof(ParamListInfoData));
128 estate->es_param_list_info = paramLI;
130 for (i = 0; i < nargs; paramLI++, i++)
132 paramLI->kind = PARAM_NUM;
134 paramLI->isnull = false;
135 paramLI->value = (Datum) NULL;
137 paramLI->kind = PARAM_INVALID;
140 estate->es_param_list_info = (ParamListInfo) NULL;
141 nextes->estate = estate;
143 nextes = (execution_state *) NULL;
151 init_sql_fcache(FmgrInfo *finfo)
153 Oid foid = finfo->fn_oid;
154 HeapTuple procedureTuple;
156 Form_pg_proc procedureStruct;
157 Form_pg_type typeStruct;
158 SQLFunctionCachePtr fcache;
166 * get the procedure tuple corresponding to the given function Oid
168 procedureTuple = SearchSysCache(PROCOID,
169 ObjectIdGetDatum(foid),
171 if (!HeapTupleIsValid(procedureTuple))
172 elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u",
175 procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
178 * get the return type from the procedure tuple
180 typeTuple = SearchSysCache(TYPEOID,
181 ObjectIdGetDatum(procedureStruct->prorettype),
183 if (!HeapTupleIsValid(typeTuple))
184 elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u",
185 procedureStruct->prorettype);
187 typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
189 fcache = (SQLFunctionCachePtr) palloc(sizeof(SQLFunctionCache));
190 MemSet(fcache, 0, sizeof(SQLFunctionCache));
193 * get the type length and by-value flag from the type tuple
195 fcache->typlen = typeStruct->typlen;
196 if (typeStruct->typrelid == InvalidOid)
198 /* The return type is not a relation, so just use byval */
199 fcache->typbyval = typeStruct->typbyval;
200 fcache->returnsTuple = false;
205 * This is a hack. We assume here that any function returning a
206 * tuple returns it by reference. This needs to be fixed, since
207 * actually the mechanism isn't quite like return-by-reference.
209 fcache->typbyval = false;
210 fcache->returnsTuple = true;
214 * If we are returning exactly one result then we have to copy tuples
215 * and by reference results because we have to end the execution
216 * before we return the results. When you do this everything
217 * allocated by the executor (i.e. slots and tuples) is freed.
219 if (!finfo->fn_retset && !fcache->typbyval)
220 fcache->funcSlot = MakeTupleTableSlot();
222 fcache->funcSlot = NULL;
224 nargs = procedureStruct->pronargs;
228 argOidVect = (Oid *) palloc(nargs * sizeof(Oid));
230 procedureStruct->proargtypes,
231 nargs * sizeof(Oid));
234 argOidVect = (Oid *) NULL;
236 tmp = SysCacheGetAttr(PROCOID,
241 elog(ERROR, "init_sql_fcache: null prosrc for procedure %u",
243 src = DatumGetCString(DirectFunctionCall1(textout, tmp));
245 fcache->func_state = init_execution_state(src, argOidVect, nargs);
249 ReleaseSysCache(typeTuple);
250 ReleaseSysCache(procedureTuple);
252 finfo->fn_extra = (void *) fcache;
257 postquel_start(execution_state *es)
260 * Do nothing for utility commands. (create, destroy...) DZ -
263 if (es->qd->operation == CMD_UTILITY)
265 ExecutorStart(es->qd, es->estate);
268 static TupleTableSlot *
269 postquel_getnext(execution_state *es)
273 if (es->qd->operation == CMD_UTILITY)
276 * Process a utility command. (create, destroy...) DZ - 30-8-1996
278 ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->dest, NULL);
279 if (!LAST_POSTQUEL_COMMAND(es))
280 CommandCounterIncrement();
281 return (TupleTableSlot *) NULL;
284 feature = (LAST_POSTQUEL_COMMAND(es)) ? EXEC_RETONE : EXEC_RUN;
286 return ExecutorRun(es->qd, es->estate, feature, 0L);
290 postquel_end(execution_state *es)
293 * Do nothing for utility commands. (create, destroy...) DZ -
296 if (es->qd->operation == CMD_UTILITY)
298 ExecutorEnd(es->qd, es->estate);
302 postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo)
305 ParamListInfo paramLI;
308 paramLI = estate->es_param_list_info;
310 while (paramLI->kind != PARAM_INVALID)
312 if (paramLI->kind == PARAM_NUM)
314 Assert(paramLI->id <= fcinfo->nargs);
315 paramLI->value = fcinfo->arg[paramLI->id - 1];
316 paramLI->isnull = fcinfo->argnull[paramLI->id - 1];
322 static TupleTableSlot *
323 copy_function_result(SQLFunctionCachePtr fcache,
324 TupleTableSlot *resultSlot)
326 TupleTableSlot *funcSlot;
328 HeapTuple resultTuple;
331 Assert(!TupIsNull(resultSlot));
332 resultTuple = resultSlot->val;
334 funcSlot = fcache->funcSlot;
336 if (funcSlot == NULL)
337 return resultSlot; /* no need to copy result */
340 * If first time through, we have to initialize the funcSlot's tuple
343 if (funcSlot->ttc_tupleDescriptor == NULL)
345 resultTd = CreateTupleDescCopy(resultSlot->ttc_tupleDescriptor);
346 ExecSetSlotDescriptor(funcSlot, resultTd, true);
347 ExecSetSlotDescriptorIsNew(funcSlot, true);
350 newTuple = heap_copytuple(resultTuple);
352 return ExecStoreTuple(newTuple, funcSlot, InvalidBuffer, true);
356 postquel_execute(execution_state *es,
357 FunctionCallInfo fcinfo,
358 SQLFunctionCachePtr fcache)
360 TupleTableSlot *slot;
364 * It's more right place to do it (before
365 * postquel_start->ExecutorStart). Now
366 * ExecutorStart->ExecInitIndexScan->ExecEvalParam works ok. (But
367 * note: I HOPE we can do it here). - vadim 01/22/97
369 if (fcinfo->nargs > 0)
370 postquel_sub_params(es, fcinfo);
372 if (es->status == F_EXEC_START)
375 es->status = F_EXEC_RUN;
378 slot = postquel_getnext(es);
383 es->status = F_EXEC_DONE;
384 fcinfo->isnull = true;
387 * If this isn't the last command for the function we have to
388 * increment the command counter so that subsequent commands can
389 * see changes made by previous ones.
391 if (!LAST_POSTQUEL_COMMAND(es))
392 CommandCounterIncrement();
396 if (LAST_POSTQUEL_COMMAND(es))
398 TupleTableSlot *resSlot;
401 * Copy the result. copy_function_result is smart enough to do
402 * nothing when no action is called for. This helps reduce the
403 * logic and code redundancy here.
405 resSlot = copy_function_result(fcache, slot);
408 * If we are supposed to return a tuple, we return the tuple slot
409 * pointer converted to Datum. If we are supposed to return a
410 * simple value, then project out the first attribute of the
411 * result tuple (ie, take the first result column of the final
414 if (fcache->returnsTuple)
417 * XXX do we need to remove junk attrs from the result tuple?
418 * Probably OK to leave them, as long as they are at the end.
420 value = PointerGetDatum(resSlot);
421 fcinfo->isnull = false;
425 value = heap_getattr(resSlot->val,
427 resSlot->ttc_tupleDescriptor,
431 * Note: if result type is pass-by-reference then we are
432 * returning a pointer into the tuple copied by
433 * copy_function_result. This is OK.
438 * If this is a single valued function we have to end the function
441 if (!fcinfo->flinfo->fn_retset)
444 es->status = F_EXEC_DONE;
451 * If this isn't the last command for the function, we don't return
452 * any results, but we have to increment the command counter so that
453 * subsequent commands can see changes made by previous ones.
455 CommandCounterIncrement();
460 fmgr_sql(PG_FUNCTION_ARGS)
462 MemoryContext oldcontext;
463 SQLFunctionCachePtr fcache;
469 * Switch to context in which the fcache lives. This ensures that
470 * parsetrees, plans, etc, will have sufficient lifetime. The
471 * sub-executor is responsible for deleting per-tuple information.
473 oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
476 * Before we start do anything we must save CurrentScanCommandId to
477 * restore it before return to upper Executor. Also, we have to set
478 * CurrentScanCommandId equal to CurrentCommandId. - vadim 08/29/97
480 savedId = GetScanCommandId();
481 SetScanCommandId(GetCurrentCommandId());
484 * Initialize fcache and execution state if first time through.
486 fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
489 init_sql_fcache(fcinfo->flinfo);
490 fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
492 es = fcache->func_state;
496 * Find first unfinished query in function.
498 while (es && es->status == F_EXEC_DONE)
504 * Execute each command in the function one after another until we're
505 * executing the final command and get a result or we run out of
508 while (es != (execution_state *) NULL)
510 result = postquel_execute(es, fcinfo, fcache);
511 if (es->status != F_EXEC_DONE)
517 * Restore outer command ID.
519 SetScanCommandId(savedId);
522 * If we've gone through every command in this function, we are done.
524 if (es == (execution_state *) NULL)
527 * Reset the execution states to start over again
529 es = fcache->func_state;
532 es->status = F_EXEC_START;
537 * Let caller know we're finished.
539 if (fcinfo->flinfo->fn_retset)
541 ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
543 if (rsi && IsA(rsi, ReturnSetInfo))
544 rsi->isDone = ExprEndResult;
546 elog(ERROR, "Set-valued function called in context that cannot accept a set");
547 fcinfo->isnull = true;
551 MemoryContextSwitchTo(oldcontext);
557 * If we got a result from a command within the function it has to be
558 * the final command. All others shouldn't be returning anything.
560 Assert(LAST_POSTQUEL_COMMAND(es));
563 * Let caller know we're not finished.
565 if (fcinfo->flinfo->fn_retset)
567 ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
569 if (rsi && IsA(rsi, ReturnSetInfo))
570 rsi->isDone = ExprMultipleResult;
572 elog(ERROR, "Set-valued function called in context that cannot accept a set");
575 MemoryContextSwitchTo(oldcontext);