/*-------------------------------------------------------------------------
*
* functions.c
- * Routines to handle functions called from the executor
- * Putting this stuff in fmgr makes the postmaster a mess....
+ * Execution of SQL-language functions
*
- * Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.26 1999/05/25 16:08:39 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.115 2007/04/16 01:14:56 tgl Exp $
*
*-------------------------------------------------------------------------
*/
-#include <string.h>
#include "postgres.h"
-#include "nodes/primnodes.h"
-#include "nodes/relation.h"
-#include "nodes/execnodes.h"
-#include "nodes/plannodes.h"
-
+#include "access/xact.h"
#include "catalog/pg_proc.h"
-#include "tcop/pquery.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/functions.h"
+#include "funcapi.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
-#include "nodes/params.h"
-#include "fmgr.h"
-#include "utils/fcache.h"
+#include "utils/builtins.h"
#include "utils/datum.h"
-#include "utils/elog.h"
-#include "utils/palloc.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
-#include "catalog/pg_language.h"
-#include "access/heapam.h"
-#include "access/xact.h"
-#include "executor/executor.h"
-#include "executor/execdefs.h"
-#include "executor/functions.h"
+#include "utils/typcache.h"
-#undef new
+/*
+ * We have an execution_state record for each query in a function. Each
+ * record contains a plantree for its query. If the query is currently in
+ * F_EXEC_RUN state then there's a QueryDesc too.
+ */
typedef enum
{
F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
typedef struct local_es
{
- QueryDesc *qd;
- EState *estate;
struct local_es *next;
ExecStatus status;
+ Node *stmt; /* PlannedStmt or utility statement */
+ QueryDesc *qd; /* null unless status == RUN */
} execution_state;
-#define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *)NULL)
-
-/* non-export function prototypes */
-static TupleDesc postquel_start(execution_state *es);
-static execution_state *init_execution_state(FunctionCachePtr fcache,
- char *args[]);
-static TupleTableSlot *postquel_getnext(execution_state *es);
-static void postquel_end(execution_state *es);
-static void postquel_sub_params(execution_state *es, int nargs,
- char *args[], bool *nullV);
-static Datum postquel_execute(execution_state *es, FunctionCachePtr fcache,
- List *fTlist, char **args, bool *isNull);
+#define LAST_POSTQUEL_COMMAND(es) ((es)->next == NULL)
-Datum
-ProjectAttribute(TupleDesc TD,
- TargetEntry *tlist,
- HeapTuple tup,
- bool *isnullP)
+/*
+ * An SQLFunctionCache record is built during the first call,
+ * and linked to from the fn_extra field of the FmgrInfo struct.
+ */
+typedef struct
{
- Datum val,
- valueP;
- Var *attrVar = (Var *) tlist->expr;
- AttrNumber attrno = attrVar->varattno;
+ char *src; /* function body text (for error msgs) */
+ Oid *argtypes; /* resolved types of arguments */
+ Oid rettype; /* actual return type */
+ int16 typlen; /* length of the return type */
+ bool typbyval; /* true if return type is pass by value */
+ bool returnsTuple; /* true if returning whole tuple result */
+ bool shutdown_reg; /* true if registered shutdown callback */
+ bool readonly_func; /* true to run in "read only" mode */
- val = heap_getattr(tup, attrno, TD, isnullP);
- if (*isnullP)
- return (Datum) NULL;
+ ParamListInfo paramLI; /* Param list representing current args */
- valueP = datumCopy(val,
- TD->attrs[attrno - 1]->atttypid,
- TD->attrs[attrno - 1]->attbyval,
- (Size) TD->attrs[attrno - 1]->attlen);
- return valueP;
-}
+ JunkFilter *junkFilter; /* used only if returnsTuple */
-static execution_state *
-init_execution_state(FunctionCachePtr fcache,
- char *args[])
-{
- execution_state *newes;
- execution_state *nextes;
- execution_state *preves;
- List *queryTree_list,
- *planTree_list,
- *qtl_item;
- int nargs = fcache->nargs;
+ /* head of linked list of execution_state records */
+ execution_state *func_state;
+} SQLFunctionCache;
- newes = (execution_state *) palloc(sizeof(execution_state));
- nextes = newes;
- preves = (execution_state *) NULL;
+typedef SQLFunctionCache *SQLFunctionCachePtr;
- planTree_list = pg_parse_and_plan(fcache->src, fcache->argOidVect,
- nargs, &queryTree_list, None, FALSE);
+
+/* non-export function prototypes */
+static execution_state *init_execution_state(List *queryTree_list,
+ bool readonly_func);
+static void init_sql_fcache(FmgrInfo *finfo);
+static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
+static TupleTableSlot *postquel_getnext(execution_state *es,
+ SQLFunctionCachePtr fcache);
+static void postquel_end(execution_state *es);
+static void postquel_sub_params(SQLFunctionCachePtr fcache,
+ FunctionCallInfo fcinfo);
+static Datum postquel_execute(execution_state *es,
+ FunctionCallInfo fcinfo,
+ SQLFunctionCachePtr fcache,
+ MemoryContext resultcontext);
+static void sql_exec_error_callback(void *arg);
+static void ShutdownSQLFunction(Datum arg);
+
+
+static execution_state *
+init_execution_state(List *queryTree_list, bool readonly_func)
+{
+ execution_state *firstes = NULL;
+ execution_state *preves = NULL;
+ ListCell *qtl_item;
foreach(qtl_item, queryTree_list)
{
Query *queryTree = lfirst(qtl_item);
- Plan *planTree = lfirst(planTree_list);
- EState *estate;
+ Node *stmt;
+ execution_state *newes;
+
+ Assert(IsA(queryTree, Query));
- if (!nextes)
- nextes = (execution_state *) palloc(sizeof(execution_state));
+ if (queryTree->commandType == CMD_UTILITY)
+ stmt = queryTree->utilityStmt;
+ else
+ stmt = (Node *) pg_plan_query(queryTree, 0, NULL);
+
+ /* Precheck all commands for validity in a function */
+ if (IsA(stmt, TransactionStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a SQL function",
+ CreateCommandTag(stmt))));
+
+ if (readonly_func && !CommandIsReadOnly(stmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a non-volatile function",
+ CreateCommandTag(stmt))));
+
+ newes = (execution_state *) palloc(sizeof(execution_state));
if (preves)
- preves->next = nextes;
+ preves->next = newes;
+ else
+ firstes = newes;
- nextes->next = NULL;
- nextes->status = F_EXEC_START;
- nextes->qd = CreateQueryDesc(queryTree,
- planTree,
- None);
- estate = CreateExecutorState();
+ newes->next = NULL;
+ newes->status = F_EXEC_START;
+ newes->stmt = stmt;
+ newes->qd = NULL;
- if (queryTree->limitOffset != NULL || queryTree->limitCount != NULL)
- elog(ERROR, "LIMIT clause from SQL functions not yet implemented");
+ preves = newes;
+ }
- if (nargs > 0)
- {
- int i;
- ParamListInfo paramLI;
+ return firstes;
+}
+
+
+static void
+init_sql_fcache(FmgrInfo *finfo)
+{
+ Oid foid = finfo->fn_oid;
+ Oid rettype;
+ HeapTuple procedureTuple;
+ Form_pg_proc procedureStruct;
+ SQLFunctionCachePtr fcache;
+ Oid *argOidVect;
+ int nargs;
+ List *queryTree_list;
+ Datum tmp;
+ bool isNull;
+
+ fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
+
+ /*
+ * get the procedure tuple corresponding to the given function Oid
+ */
+ procedureTuple = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(foid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(procedureTuple))
+ elog(ERROR, "cache lookup failed for function %u", foid);
+ procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
- paramLI = (ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData));
+ /*
+ * get the result type from the procedure tuple, and check for polymorphic
+ * result type; if so, find out the actual result type.
+ */
+ rettype = procedureStruct->prorettype;
- MemSet(paramLI, 0, nargs * sizeof(ParamListInfoData));
+ if (IsPolymorphicType(rettype))
+ {
+ rettype = get_fn_expr_rettype(finfo);
+ if (rettype == InvalidOid) /* this probably should not happen */
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not determine actual result type for function declared to return type %s",
+ format_type_be(procedureStruct->prorettype))));
+ }
+
+ fcache->rettype = rettype;
- estate->es_param_list_info = paramLI;
+ /* Fetch the typlen and byval info for the result type */
+ get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval);
- for (i = 0; i < nargs; paramLI++, i++)
+ /* Remember if function is STABLE/IMMUTABLE */
+ fcache->readonly_func =
+ (procedureStruct->provolatile != PROVOLATILE_VOLATILE);
+
+ /*
+ * We need the actual argument types to pass to the parser.
+ */
+ nargs = procedureStruct->pronargs;
+ if (nargs > 0)
+ {
+ int argnum;
+
+ argOidVect = (Oid *) palloc(nargs * sizeof(Oid));
+ memcpy(argOidVect,
+ procedureStruct->proargtypes.values,
+ nargs * sizeof(Oid));
+ /* Resolve any polymorphic argument types */
+ for (argnum = 0; argnum < nargs; argnum++)
+ {
+ Oid argtype = argOidVect[argnum];
+
+ if (IsPolymorphicType(argtype))
{
- paramLI->kind = PARAM_NUM;
- paramLI->id = i + 1;
- paramLI->isnull = false;
- paramLI->value = (Datum) NULL;
+ argtype = get_fn_expr_argtype(finfo, argnum);
+ if (argtype == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not determine actual type of argument declared %s",
+ format_type_be(argOidVect[argnum]))));
+ argOidVect[argnum] = argtype;
}
- paramLI->kind = PARAM_INVALID;
}
- else
- estate->es_param_list_info = (ParamListInfo) NULL;
- nextes->estate = estate;
- preves = nextes;
- nextes = (execution_state *) NULL;
-
- planTree_list = lnext(planTree_list);
}
+ else
+ argOidVect = NULL;
+ fcache->argtypes = argOidVect;
- return newes;
-}
+ /*
+ * And of course we need the function body text.
+ */
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc for function %u", foid);
+ fcache->src = DatumGetCString(DirectFunctionCall1(textout, tmp));
-static TupleDesc
-postquel_start(execution_state *es)
-{
-#ifdef FUNC_UTIL_PATCH
+ /*
+ * Parse and rewrite the queries in the function text.
+ */
+ queryTree_list = pg_parse_and_rewrite(fcache->src, argOidVect, nargs);
/*
- * Do nothing for utility commands. (create, destroy...) DZ -
- * 30-8-1996
+ * Check that the function returns the type it claims to. Although
+ * in simple cases this was already done when the function was defined,
+ * we have to recheck because database objects used in the function's
+ * queries might have changed type. We'd have to do it anyway if the
+ * function had any polymorphic arguments.
+ *
+ * Note: we set fcache->returnsTuple according to whether we are
+ * returning the whole tuple result or just a single column. In the
+ * latter case we clear returnsTuple because we need not act different
+ * from the scalar result case, even if it's a rowtype column.
+ *
+ * In the returnsTuple case, check_sql_fn_retval will also construct a
+ * JunkFilter we can use to coerce the returned rowtype to the desired
+ * form.
*/
- if (es->qd->operation == CMD_UTILITY)
- return (TupleDesc) NULL;
-#endif
- return ExecutorStart(es->qd, es->estate);
+ fcache->returnsTuple = check_sql_fn_retval(foid,
+ rettype,
+ queryTree_list,
+ &fcache->junkFilter);
+
+ /* Finally, plan the queries */
+ fcache->func_state = init_execution_state(queryTree_list,
+ fcache->readonly_func);
+
+ ReleaseSysCache(procedureTuple);
+
+ finfo->fn_extra = (void *) fcache;
}
-static TupleTableSlot *
-postquel_getnext(execution_state *es)
+
+static void
+postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
- int feature;
+ Snapshot snapshot;
-#ifdef FUNC_UTIL_PATCH
- if (es->qd->operation == CMD_UTILITY)
+ Assert(es->qd == NULL);
+
+ /*
+ * In a read-only function, use the surrounding query's snapshot;
+ * otherwise take a new snapshot for each query. The snapshot should
+ * include a fresh command ID so that all work to date in this transaction
+ * is visible. We copy in both cases so that postquel_end can
+ * unconditionally do FreeSnapshot.
+ */
+ if (fcache->readonly_func)
+ snapshot = CopySnapshot(ActiveSnapshot);
+ else
{
+ CommandCounterIncrement();
+ snapshot = CopySnapshot(GetTransactionSnapshot());
+ }
+ if (IsA(es->stmt, PlannedStmt))
+ es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
+ snapshot, InvalidSnapshot,
+ None_Receiver,
+ fcache->paramLI, false);
+ else
+ es->qd = CreateUtilityQueryDesc(es->stmt,
+ snapshot,
+ None_Receiver,
+ fcache->paramLI);
+
+ /* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
+
+ /* Utility commands don't need Executor. */
+ if (es->qd->operation != CMD_UTILITY)
+ {
/*
- * Process an utility command. (create, destroy...) DZ -
- * 30-8-1996
+ * Only set up to collect queued triggers if it's not a SELECT.
+ * This isn't just an optimization, but is necessary in case a SELECT
+ * returns multiple rows to caller --- we mustn't exit from the
+ * function execution with a stacked AfterTrigger level still active.
*/
- ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->dest);
- if (!LAST_POSTQUEL_COMMAND(es))
- CommandCounterIncrement();
- return (TupleTableSlot *) NULL;
+ if (es->qd->operation != CMD_SELECT)
+ AfterTriggerBeginQuery();
+ ExecutorStart(es->qd, 0);
}
-#endif
-
- feature = (LAST_POSTQUEL_COMMAND(es)) ? EXEC_RETONE : EXEC_RUN;
- return ExecutorRun(es->qd, es->estate, feature, (Node *) NULL, (Node *) NULL);
+ es->status = F_EXEC_RUN;
}
-static void
-postquel_end(execution_state *es)
+static TupleTableSlot *
+postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{
-#ifdef FUNC_UTIL_PATCH
+ TupleTableSlot *result;
+ Snapshot saveActiveSnapshot;
+ long count;
- /*
- * Do nothing for utility commands. (create, destroy...) DZ -
- * 30-8-1996
- */
- if (es->qd->operation == CMD_UTILITY)
- return;
-#endif
- ExecutorEnd(es->qd, es->estate);
+ /* Make our snapshot the active one for any called functions */
+ saveActiveSnapshot = ActiveSnapshot;
+ PG_TRY();
+ {
+ ActiveSnapshot = es->qd->snapshot;
+
+ if (es->qd->operation == CMD_UTILITY)
+ {
+ ProcessUtility(es->qd->utilitystmt,
+ fcache->src,
+ es->qd->params,
+ false, /* not top level */
+ es->qd->dest,
+ NULL);
+ result = NULL;
+ }
+ else
+ {
+ /*
+ * If it's the function's last command, and it's a SELECT, fetch
+ * one row at a time so we can return the results. Otherwise just
+ * run it to completion. (If we run to completion then
+ * ExecutorRun is guaranteed to return NULL.)
+ */
+ if (LAST_POSTQUEL_COMMAND(es) &&
+ es->qd->operation == CMD_SELECT &&
+ es->qd->plannedstmt->into == NULL)
+ count = 1L;
+ else
+ count = 0L;
+
+ result = ExecutorRun(es->qd, ForwardScanDirection, count);
+ }
+ }
+ PG_CATCH();
+ {
+ /* Restore global vars and propagate error */
+ ActiveSnapshot = saveActiveSnapshot;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ ActiveSnapshot = saveActiveSnapshot;
+
+ return result;
}
static void
-postquel_sub_params(execution_state *es,
- int nargs,
- char *args[],
- bool *nullV)
+postquel_end(execution_state *es)
{
- ParamListInfo paramLI;
- EState *estate;
+ Snapshot saveActiveSnapshot;
- estate = es->estate;
- paramLI = estate->es_param_list_info;
+ /* mark status done to ensure we don't do ExecutorEnd twice */
+ es->status = F_EXEC_DONE;
- while (paramLI->kind != PARAM_INVALID)
+ /* Utility commands don't need Executor. */
+ if (es->qd->operation != CMD_UTILITY)
{
- if (paramLI->kind == PARAM_NUM)
+ /* Make our snapshot the active one for any called functions */
+ saveActiveSnapshot = ActiveSnapshot;
+ PG_TRY();
+ {
+ ActiveSnapshot = es->qd->snapshot;
+
+ if (es->qd->operation != CMD_SELECT)
+ AfterTriggerEndQuery(es->qd->estate);
+ ExecutorEnd(es->qd);
+ }
+ PG_CATCH();
{
- Assert(paramLI->id <= nargs);
- paramLI->value = (Datum) args[(paramLI->id - 1)];
- paramLI->isnull = nullV[(paramLI->id - 1)];
+ /* Restore global vars and propagate error */
+ ActiveSnapshot = saveActiveSnapshot;
+ PG_RE_THROW();
}
- paramLI++;
+ PG_END_TRY();
+ ActiveSnapshot = saveActiveSnapshot;
}
+
+ FreeSnapshot(es->qd->snapshot);
+ FreeQueryDesc(es->qd);
+ es->qd = NULL;
}
-static TupleTableSlot *
-copy_function_result(FunctionCachePtr fcache,
- TupleTableSlot *resultSlot)
+/* Build ParamListInfo array representing current arguments */
+static void
+postquel_sub_params(SQLFunctionCachePtr fcache,
+ FunctionCallInfo fcinfo)
{
- TupleTableSlot *funcSlot;
- TupleDesc resultTd;
- HeapTuple newTuple;
- HeapTuple oldTuple;
-
- Assert(!TupIsNull(resultSlot));
- oldTuple = resultSlot->val;
-
- funcSlot = (TupleTableSlot *) fcache->funcSlot;
-
- if (funcSlot == (TupleTableSlot *) NULL)
- return resultSlot;
-
- resultTd = resultSlot->ttc_tupleDescriptor;
+ ParamListInfo paramLI;
+ int nargs = fcinfo->nargs;
- /*
- * When the funcSlot is NULL we have to initialize the funcSlot's
- * tuple descriptor.
- */
- if (TupIsNull(funcSlot))
+ if (nargs > 0)
{
- int i = 0;
- TupleDesc funcTd = funcSlot->ttc_tupleDescriptor;
+ int i;
- while (i < oldTuple->t_data->t_natts)
+ /* sizeof(ParamListInfoData) includes the first array element */
+ paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
+ (nargs - 1) *sizeof(ParamExternData));
+ paramLI->numParams = nargs;
+
+ for (i = 0; i < nargs; i++)
{
- funcTd->attrs[i] = (Form_pg_attribute) palloc(ATTRIBUTE_TUPLE_SIZE);
- memmove(funcTd->attrs[i],
- resultTd->attrs[i],
- ATTRIBUTE_TUPLE_SIZE);
- i++;
+ ParamExternData *prm = ¶mLI->params[i];
+
+ prm->value = fcinfo->arg[i];
+ prm->isnull = fcinfo->argnull[i];
+ prm->pflags = 0;
+ prm->ptype = fcache->argtypes[i];
}
}
+ else
+ paramLI = NULL;
- newTuple = heap_copytuple(oldTuple);
+ if (fcache->paramLI)
+ pfree(fcache->paramLI);
- return ExecStoreTuple(newTuple, funcSlot, InvalidBuffer, true);
+ fcache->paramLI = paramLI;
}
static Datum
postquel_execute(execution_state *es,
- FunctionCachePtr fcache,
- List *fTlist,
- char **args,
- bool *isNull)
+ FunctionCallInfo fcinfo,
+ SQLFunctionCachePtr fcache,
+ MemoryContext resultcontext)
{
TupleTableSlot *slot;
Datum value;
-
- /*
- * 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
- */
- if (fcache->nargs > 0)
- postquel_sub_params(es, fcache->nargs, args, fcache->nullVect);
+ MemoryContext oldcontext;
if (es->status == F_EXEC_START)
- {
- postquel_start(es);
- es->status = F_EXEC_RUN;
- }
+ postquel_start(es, fcache);
- slot = postquel_getnext(es);
+ slot = postquel_getnext(es, fcache);
if (TupIsNull(slot))
{
- postquel_end(es);
- es->status = F_EXEC_DONE;
- *isNull = true;
-
/*
- * 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.
+ * We fall out here for all cases except where we have obtained a row
+ * from a function's final SELECT.
*/
- if (!LAST_POSTQUEL_COMMAND(es))
- CommandCounterIncrement();
+ postquel_end(es);
+ fcinfo->isnull = true;
return (Datum) NULL;
}
- if (LAST_POSTQUEL_COMMAND(es))
+ /*
+ * If we got a row from a command within the function it has to be the
+ * final command. All others shouldn't be returning anything.
+ */
+ Assert(LAST_POSTQUEL_COMMAND(es));
+
+ /*
+ * Set up to return the function value. For pass-by-reference datatypes,
+ * be sure to allocate the result in resultcontext, not the current memory
+ * context (which has query lifespan).
+ */
+ oldcontext = MemoryContextSwitchTo(resultcontext);
+
+ if (fcache->returnsTuple)
{
- TupleTableSlot *resSlot;
+ /*
+ * We are returning the whole tuple, so filter it and apply the proper
+ * labeling to make it a valid Datum. There are several reasons why
+ * we do this:
+ *
+ * 1. To copy the tuple out of the child execution context and into
+ * the desired result context.
+ *
+ * 2. To remove any junk attributes present in the raw subselect
+ * result. (This is probably not absolutely necessary, but it seems
+ * like good policy.)
+ *
+ * 3. To insert dummy null columns if the declared result type has any
+ * attisdropped columns.
+ */
+ HeapTuple newtup;
+ HeapTupleHeader dtup;
+ uint32 t_len;
+ Oid dtuptype;
+ int32 dtuptypmod;
+
+ newtup = ExecRemoveJunk(fcache->junkFilter, slot);
/*
- * 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.
+ * Compress out the HeapTuple header data. We assume that
+ * heap_form_tuple made the tuple with header and body in one palloc'd
+ * chunk. We want to return a pointer to the chunk start so that it
+ * will work if someone tries to free it.
*/
- resSlot = copy_function_result(fcache, slot);
- if (fTlist != NIL)
- {
- TargetEntry *tle = lfirst(fTlist);
+ t_len = newtup->t_len;
+ dtup = (HeapTupleHeader) newtup;
+ memmove((char *) dtup, (char *) newtup->t_data, t_len);
- value = ProjectAttribute(resSlot->ttc_tupleDescriptor,
- tle,
- resSlot->val,
- isNull);
+ /*
+ * Use the declared return type if it's not RECORD; else take the type
+ * from the computed result, making sure a typmod has been assigned.
+ */
+ if (fcache->rettype != RECORDOID)
+ {
+ /* function has a named composite return type */
+ dtuptype = fcache->rettype;
+ dtuptypmod = -1;
}
else
{
- value = (Datum) resSlot;
- *isNull = false;
+ /* function is declared to return RECORD */
+ TupleDesc tupDesc = fcache->junkFilter->jf_cleanTupType;
+
+ if (tupDesc->tdtypeid == RECORDOID &&
+ tupDesc->tdtypmod < 0)
+ assign_record_type_typmod(tupDesc);
+ dtuptype = tupDesc->tdtypeid;
+ dtuptypmod = tupDesc->tdtypmod;
}
+ HeapTupleHeaderSetDatumLength(dtup, t_len);
+ HeapTupleHeaderSetTypeId(dtup, dtuptype);
+ HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
+
+ value = PointerGetDatum(dtup);
+ fcinfo->isnull = false;
+ }
+ else
+ {
/*
- * If this is a single valued function we have to end the function
- * execution now.
+ * Returning a scalar, which we have to extract from the first column
+ * of the SELECT result, and then copy into result context if needed.
*/
- if (fcache->oneResult)
- {
- postquel_end(es);
- es->status = F_EXEC_DONE;
- }
+ value = slot_getattr(slot, 1, &(fcinfo->isnull));
- return value;
+ if (!fcinfo->isnull)
+ value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
+ MemoryContextSwitchTo(oldcontext);
+
/*
- * 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.
+ * If this is a single valued function we have to end the function
+ * execution now.
*/
- CommandCounterIncrement();
- return (Datum) NULL;
+ if (!fcinfo->flinfo->fn_retset)
+ postquel_end(es);
+
+ return value;
}
Datum
-postquel_function(Func *funcNode, char **args, bool *isNull, bool *isDone)
+fmgr_sql(PG_FUNCTION_ARGS)
{
+ MemoryContext oldcontext;
+ SQLFunctionCachePtr fcache;
+ ErrorContextCallback sqlerrcontext;
execution_state *es;
Datum result = 0;
- FunctionCachePtr fcache = funcNode->func_fcache;
- CommandId savedId;
/*
- * Before we start do anything we must save CurrentScanCommandId to
- * restore it before return to upper Executor. Also, we have to set
- * CurrentScanCommandId equal to CurrentCommandId. - vadim 08/29/97
+ * 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.
*/
- savedId = GetScanCommandId();
- SetScanCommandId(GetCurrentCommandId());
+ oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
- es = (execution_state *) fcache->func_state;
- if (es == NULL)
+ /*
+ * Setup error traceback support for ereport()
+ */
+ sqlerrcontext.callback = sql_exec_error_callback;
+ sqlerrcontext.arg = fcinfo->flinfo;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
+
+ /*
+ * Initialize fcache (build plans) if first time through.
+ */
+ fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
+ if (fcache == NULL)
{
- es = init_execution_state(fcache, args);
- fcache->func_state = (char *) es;
+ init_sql_fcache(fcinfo->flinfo);
+ fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
}
+ es = fcache->func_state;
+ /*
+ * Convert params to appropriate format if starting a fresh execution. (If
+ * continuing execution, we can re-use prior params.)
+ */
+ if (es && es->status == F_EXEC_START)
+ postquel_sub_params(fcache, fcinfo);
+
+ /*
+ * Find first unfinished query in function.
+ */
while (es && es->status == F_EXEC_DONE)
es = es->next;
- Assert(es);
-
/*
* 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.
+ * executing the final command and get a result or we run out of commands.
*/
- while (es != (execution_state *) NULL)
+ while (es)
{
- result = postquel_execute(es,
- fcache,
- funcNode->func_tlist,
- args,
- isNull);
+ result = postquel_execute(es, fcinfo, fcache, oldcontext);
if (es->status != F_EXEC_DONE)
break;
es = es->next;
/*
* If we've gone through every command in this function, we are done.
*/
- if (es == (execution_state *) NULL)
+ if (es == NULL)
{
-
/*
- * Reset the execution states to start over again
+ * Reset the execution states to start over again on next call.
*/
- es = (execution_state *) fcache->func_state;
+ es = fcache->func_state;
while (es)
{
es->status = F_EXEC_START;
/*
* Let caller know we're finished.
*/
- *isDone = true;
- SetScanCommandId(savedId);
- return (fcache->oneResult) ? result : (Datum) NULL;
+ if (fcinfo->flinfo->fn_retset)
+ {
+ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (rsi && IsA(rsi, ReturnSetInfo))
+ rsi->isDone = ExprEndResult;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ fcinfo->isnull = true;
+ result = (Datum) 0;
+
+ /* Deregister shutdown callback, if we made one */
+ if (fcache->shutdown_reg)
+ {
+ UnregisterExprContextCallback(rsi->econtext,
+ ShutdownSQLFunction,
+ PointerGetDatum(fcache));
+ fcache->shutdown_reg = false;
+ }
+ }
+
+ error_context_stack = sqlerrcontext.previous;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
}
/*
- * If we got a result from a command within the function it has to be
- * the final command. All others shouldn't be returing anything.
+ * If we got a result from a command within the function it has to be the
+ * final command. All others shouldn't be returning anything.
*/
Assert(LAST_POSTQUEL_COMMAND(es));
- *isDone = false;
- SetScanCommandId(savedId);
+ /*
+ * 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
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ /*
+ * Ensure we will get shut down cleanly if the exprcontext is not run
+ * to completion.
+ */
+ if (!fcache->shutdown_reg)
+ {
+ RegisterExprContextCallback(rsi->econtext,
+ ShutdownSQLFunction,
+ PointerGetDatum(fcache));
+ fcache->shutdown_reg = true;
+ }
+ }
+
+ error_context_stack = sqlerrcontext.previous;
+
+ MemoryContextSwitchTo(oldcontext);
+
return result;
}
+
+
+/*
+ * error context callback to let us supply a call-stack traceback
+ */
+static void
+sql_exec_error_callback(void *arg)
+{
+ FmgrInfo *flinfo = (FmgrInfo *) arg;
+ SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) flinfo->fn_extra;
+ HeapTuple func_tuple;
+ Form_pg_proc functup;
+ char *fn_name;
+ int syntaxerrposition;
+
+ /* Need access to function's pg_proc tuple */
+ func_tuple = SearchSysCache(PROCOID,
+ ObjectIdGetDatum(flinfo->fn_oid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(func_tuple))
+ return; /* shouldn't happen */
+ functup = (Form_pg_proc) GETSTRUCT(func_tuple);
+ fn_name = NameStr(functup->proname);
+
+ /*
+ * If there is a syntax error position, convert to internal syntax error
+ */
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0 && fcache->src)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(fcache->src);
+ }
+
+ /*
+ * Try to determine where in the function we failed. If there is a query
+ * with non-null QueryDesc, finger it. (We check this rather than looking
+ * for F_EXEC_RUN state, so that errors during ExecutorStart or
+ * ExecutorEnd are blamed on the appropriate query; see postquel_start and
+ * postquel_end.)
+ */
+ if (fcache)
+ {
+ execution_state *es;
+ int query_num;
+
+ es = fcache->func_state;
+ query_num = 1;
+ while (es)
+ {
+ if (es->qd)
+ {
+ errcontext("SQL function \"%s\" statement %d",
+ fn_name, query_num);
+ break;
+ }
+ es = es->next;
+ query_num++;
+ }
+ if (es == NULL)
+ {
+ /*
+ * couldn't identify a running query; might be function entry,
+ * function exit, or between queries.
+ */
+ errcontext("SQL function \"%s\"", fn_name);
+ }
+ }
+ else
+ {
+ /* must have failed during init_sql_fcache() */
+ errcontext("SQL function \"%s\" during startup", fn_name);
+ }
+
+ ReleaseSysCache(func_tuple);
+}
+
+
+/*
+ * 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;
+}
+
+
+/*
+ * check_sql_fn_retval() -- check return value of a list of sql parse trees.
+ *
+ * The return value of a sql function is the value returned by
+ * the final query in the function. We do some ad-hoc type checking here
+ * to be sure that the user is returning the type he claims.
+ *
+ * For a polymorphic function the passed rettype must be the actual resolved
+ * output type of the function; we should never see ANYARRAY, ANYENUM or
+ * ANYELEMENT as rettype. (This means we can't check the type during function
+ * definition of a polymorphic function.)
+ *
+ * This function returns true if the sql function returns the entire tuple
+ * result of its final SELECT, and false otherwise. Note that because we
+ * allow "SELECT rowtype_expression", this may be false even when the declared
+ * function return type is a rowtype.
+ *
+ * If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined
+ * to convert the function's tuple result to the correct output tuple type.
+ * Whenever the result value is false (ie, the function isn't returning a
+ * tuple result), *junkFilter is set to NULL.
+ */
+bool
+check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
+ JunkFilter **junkFilter)
+{
+ Query *parse;
+ List *tlist;
+ ListCell *tlistitem;
+ int tlistlen;
+ char fn_typtype;
+ Oid restype;
+
+ if (junkFilter)
+ *junkFilter = NULL; /* default result */
+
+ /* guard against empty function body; OK only if void return type */
+ if (queryTreeList == NIL)
+ {
+ if (rettype != VOIDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Function's final statement must be a SELECT.")));
+ return false;
+ }
+
+ /* find the final query */
+ parse = (Query *) lfirst(list_tail(queryTreeList));
+
+ /*
+ * If the last query isn't a SELECT, the return type must be VOID.
+ *
+ * Note: eventually replace this test with QueryReturnsTuples? We'd need
+ * a more general method of determining the output type, though.
+ */
+ if (!(parse->commandType == CMD_SELECT && parse->into == NULL))
+ {
+ if (rettype != VOIDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Function's final statement must be a SELECT.")));
+ return false;
+ }
+
+ /*
+ * OK, it's a SELECT, so it must return something matching the declared
+ * type. (We used to insist that the declared type not be VOID in this
+ * case, but that makes it hard to write a void function that exits
+ * after calling another void function. Instead, we insist that the
+ * SELECT return void ... so void is treated as if it were a scalar type
+ * below.)
+ */
+
+ /*
+ * Count the non-junk entries in the result targetlist.
+ */
+ tlist = parse->targetList;
+ tlistlen = ExecCleanTargetListLength(tlist);
+
+ fn_typtype = get_typtype(rettype);
+
+ if (fn_typtype == TYPTYPE_BASE ||
+ fn_typtype == TYPTYPE_DOMAIN ||
+ fn_typtype == TYPTYPE_ENUM ||
+ rettype == VOIDOID)
+ {
+ /*
+ * For scalar-type returns, the target list should have exactly one
+ * entry, and its type should agree with what the user declared. (As
+ * of Postgres 7.2, we accept binary-compatible types too.)
+ */
+ if (tlistlen != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Final SELECT must return exactly one column.")));
+
+ restype = exprType((Node *) ((TargetEntry *) linitial(tlist))->expr);
+ if (!IsBinaryCoercible(restype, rettype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Actual return type is %s.",
+ format_type_be(restype))));
+ }
+ else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
+ {
+ /* Returns a rowtype */
+ TupleDesc tupdesc;
+ int tupnatts; /* physical number of columns in tuple */
+ int tuplogcols; /* # of nondeleted columns in tuple */
+ int colindex; /* physical column index */
+
+ /*
+ * If the target list is of length 1, and the type of the varnode in
+ * the target list matches the declared return type, this is okay.
+ * This can happen, for example, where the body of the function is
+ * 'SELECT func2()', where func2 has the same return type as the
+ * function that's calling it.
+ */
+ if (tlistlen == 1)
+ {
+ restype = exprType((Node *) ((TargetEntry *) linitial(tlist))->expr);
+ if (IsBinaryCoercible(restype, rettype))
+ return false; /* NOT returning whole tuple */
+ }
+
+ /* Is the rowtype fixed, or determined only at runtime? */
+ if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ {
+ /*
+ * Assume we are returning the whole tuple. Crosschecking against
+ * what the caller expects will happen at runtime.
+ */
+ if (junkFilter)
+ *junkFilter = ExecInitJunkFilter(tlist, false, NULL);
+ return true;
+ }
+ Assert(tupdesc);
+
+ /*
+ * Verify that the targetlist matches the return tuple type. We scan
+ * the non-deleted attributes to ensure that they match the datatypes
+ * of the non-resjunk columns.
+ */
+ tupnatts = tupdesc->natts;
+ tuplogcols = 0; /* we'll count nondeleted cols as we go */
+ colindex = 0;
+
+ foreach(tlistitem, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
+ Form_pg_attribute attr;
+ Oid tletype;
+ Oid atttype;
+
+ if (tle->resjunk)
+ continue;
+
+ do
+ {
+ colindex++;
+ if (colindex > tupnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Final SELECT returns too many columns.")));
+ attr = tupdesc->attrs[colindex - 1];
+ } while (attr->attisdropped);
+ tuplogcols++;
+
+ tletype = exprType((Node *) tle->expr);
+ atttype = attr->atttypid;
+ if (!IsBinaryCoercible(tletype, atttype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Final SELECT returns %s instead of %s at column %d.",
+ format_type_be(tletype),
+ format_type_be(atttype),
+ tuplogcols)));
+ }
+
+ for (;;)
+ {
+ colindex++;
+ if (colindex > tupnatts)
+ break;
+ if (!tupdesc->attrs[colindex - 1]->attisdropped)
+ tuplogcols++;
+ }
+
+ if (tlistlen != tuplogcols)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type mismatch in function declared to return %s",
+ format_type_be(rettype)),
+ errdetail("Final SELECT returns too few columns.")));
+
+ /* Set up junk filter if needed */
+ if (junkFilter)
+ *junkFilter = ExecInitJunkFilterConversion(tlist,
+ CreateTupleDescCopy(tupdesc),
+ NULL);
+
+ /* Report that we are returning entire tuple result */
+ return true;
+ }
+ else if (IsPolymorphicType(rettype))
+ {
+ /* This should already have been caught ... */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("cannot determine result data type"),
+ errdetail("A function returning a polymorphic type must have at least one polymorphic argument.")));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("return type %s is not supported for SQL functions",
+ format_type_be(rettype))));
+
+ return false;
+}