]> granicus.if.org Git - postgresql/blobdiff - src/backend/executor/functions.c
Expose more cursor-related functionality in SPI: specifically, allow
[postgresql] / src / backend / executor / functions.c
index 51ea6655e9dddfcfd7a4925377b93cecc44cadda..b59228b0c9ee199112e1a999cded253e936b94da 100644 (file)
@@ -1,45 +1,41 @@
 /*-------------------------------------------------------------------------
  *
- * functions.c--
- *       Routines to handle functions called from the executor
- *       Putting this stuff in fmgr makes the postmaster a mess....
+ * functions.c
+ *       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.22 1999/02/03 21:16:12 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
@@ -47,373 +43,604 @@ typedef enum
 
 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)
+#define LAST_POSTQUEL_COMMAND(es) ((es)->next == NULL)
+
+
+/*
+ * An SQLFunctionCache record is built during the first call,
+ * and linked to from the fn_extra field of the FmgrInfo struct.
+ */
+typedef struct
+{
+       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 */
+
+       ParamListInfo paramLI;          /* Param list representing current args */
+
+       JunkFilter *junkFilter;         /* used only if returnsTuple */
+
+       /* head of linked list of execution_state records */
+       execution_state *func_state;
+} SQLFunctionCache;
+
+typedef SQLFunctionCache *SQLFunctionCachePtr;
+
 
 /* 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 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(execution_state *es, int nargs,
-                                       char *args[], bool *nullV);
-static Datum postquel_execute(execution_state *es, FunctionCachePtr fcache,
-                                List *fTlist, char **args, bool *isNull);
+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);
 
 
-Datum
-ProjectAttribute(TupleDesc TD,
-                                TargetEntry *tlist,
-                                HeapTuple tup,
-                                bool *isnullP)
+static execution_state *
+init_execution_state(List *queryTree_list, bool readonly_func)
 {
-       Datum           val,
-                               valueP;
-       Var                *attrVar = (Var *) tlist->expr;
-       AttrNumber      attrno = attrVar->varattno;
+       execution_state *firstes = NULL;
+       execution_state *preves = NULL;
+       ListCell   *qtl_item;
+
+       foreach(qtl_item, queryTree_list)
+       {
+               Query      *queryTree = lfirst(qtl_item);
+               Node       *stmt;
+               execution_state *newes;
 
+               Assert(IsA(queryTree, Query));
 
-       val = heap_getattr(tup, attrno, TD, isnullP);
-       if (*isnullP)
-               return (Datum) NULL;
+               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 = newes;
+               else
+                       firstes = newes;
+
+               newes->next = NULL;
+               newes->status = F_EXEC_START;
+               newes->stmt = stmt;
+               newes->qd = NULL;
 
-       valueP = datumCopy(val,
-                                          TD->attrs[attrno - 1]->atttypid,
-                                          TD->attrs[attrno - 1]->attbyval,
-                                          (Size) TD->attrs[attrno - 1]->attlen);
-       return valueP;
+               preves = newes;
+       }
+
+       return firstes;
 }
 
-static execution_state *
-init_execution_state(FunctionCachePtr fcache,
-                                        char *args[])
+
+static void
+init_sql_fcache(FmgrInfo *finfo)
 {
-       execution_state *newes;
-       execution_state *nextes;
-       execution_state *preves;
-       QueryTreeList *queryTree_list;
-       int                     i;
-       List       *planTree_list;
+       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;
 
-       nargs = fcache->nargs;
-
-       newes = (execution_state *) palloc(sizeof(execution_state));
-       nextes = newes;
-       preves = (execution_state *) NULL;
+       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);
 
-       planTree_list = (List *)
-               pg_parse_and_plan(fcache->src, fcache->argOidVect, nargs, &queryTree_list, None, FALSE);
+       /*
+        * 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;
 
-       for (i = 0; i < queryTree_list->len; i++)
+       if (IsPolymorphicType(rettype))
        {
-               EState     *estate;
-               Query      *queryTree = (Query *) (queryTree_list->qtrees[i]);
-               Plan       *planTree = lfirst(planTree_list);
-
-               if (!nextes)
-                       nextes = (execution_state *) palloc(sizeof(execution_state));
-               if (preves)
-                       preves->next = nextes;
-
-               nextes->next = NULL;
-               nextes->status = F_EXEC_START;
-               nextes->qd = CreateQueryDesc(queryTree,
-                                                                        planTree,
-                                                                        None);
-               estate = CreateExecutorState();
+               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))));
+       }
 
-               if (nargs > 0)
-               {
-                       int                     i;
-                       ParamListInfo paramLI;
+       fcache->rettype = rettype;
 
-                       paramLI = (ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData));
+       /* Fetch the typlen and byval info for the result type */
+       get_typlenbyval(rettype, &fcache->typlen, &fcache->typbyval);
 
-                       MemSet(paramLI, 0, nargs * sizeof(ParamListInfoData));
+       /* Remember if function is STABLE/IMMUTABLE */
+       fcache->readonly_func =
+               (procedureStruct->provolatile != PROVOLATILE_VOLATILE);
 
-                       estate->es_param_list_info = paramLI;
+       /*
+        * 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];
 
-                       for (i = 0; i < nargs; paramLI++, i++)
+                       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;
+
+       Assert(es->qd == NULL);
 
-#ifdef FUNC_UTIL_PATCH
-       if (es->qd->operation == CMD_UTILITY)
+       /*
+        * 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, 0);
+       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;
+
+               /* sizeof(ParamListInfoData) includes the first array element */
+               paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
+                                                                          (nargs - 1) *sizeof(ParamExternData));
+               paramLI->numParams = nargs;
 
-               while (i < oldTuple->t_data->t_natts)
+               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 = &paramLI->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;
@@ -422,13 +649,12 @@ postquel_function(Func *funcNode, char **args, bool *isNull, bool *isDone)
        /*
         * 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;
@@ -438,18 +664,408 @@ postquel_function(Func *funcNode, char **args, bool *isNull, bool *isDone)
                /*
                 * 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;
+}