]> granicus.if.org Git - postgresql/blobdiff - src/backend/executor/spi.c
Modify all callers of datatype input and receive functions so that if these
[postgresql] / src / backend / executor / spi.c
index 6650da9b6262930c97f0601ad863fb70a4077f68..3563ba23d3ad2f7cbb8f2fed207402b3714936b9 100644 (file)
@@ -3,12 +3,12 @@
  * spi.c
  *                             Server Programming Interface
  *
- * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.127 2004/09/13 20:06:46 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.150 2006/04/04 19:35:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,7 @@
 #include "executor/spi_priv.h"
 #include "tcop/tcopprot.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/typcache.h"
 
 
@@ -37,15 +38,15 @@ static int  _SPI_curid = -1;
 static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
 
 static int _SPI_execute_plan(_SPI_plan *plan,
-                                                        Datum *Values, const char *Nulls,
-                                                        Snapshot snapshot, Snapshot crosscheck_snapshot,
-                                                        bool read_only, int tcount);
+                                 Datum *Values, const char *Nulls,
+                                 Snapshot snapshot, Snapshot crosscheck_snapshot,
+                                 bool read_only, long tcount);
 
-static int _SPI_pquery(QueryDesc *queryDesc, int tcount);
+static int     _SPI_pquery(QueryDesc *queryDesc, long tcount);
 
 static void _SPI_error_callback(void *arg);
 
-static void _SPI_cursor_operation(Portal portal, bool forward, int count,
+static void _SPI_cursor_operation(Portal portal, bool forward, long count,
                                          DestReceiver *dest);
 
 static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
@@ -65,8 +66,8 @@ SPI_connect(void)
        int                     newdepth;
 
        /*
-        * When procedure called by Executor _SPI_curid expected to be equal
-        * to _SPI_connected
+        * When procedure called by Executor _SPI_curid expected to be equal to
+        * _SPI_connected
         */
        if (_SPI_curid != _SPI_connected)
                return SPI_ERROR_CONNECT;
@@ -103,27 +104,30 @@ SPI_connect(void)
 
        _SPI_current = &(_SPI_stack[_SPI_connected]);
        _SPI_current->processed = 0;
+       _SPI_current->lastoid = InvalidOid;
        _SPI_current->tuptable = NULL;
-       _SPI_current->connectXid = GetCurrentTransactionId();
+       _SPI_current->procCxt = NULL;           /* in case we fail to create 'em */
+       _SPI_current->execCxt = NULL;
+       _SPI_current->connectSubid = GetCurrentSubTransactionId();
 
        /*
         * Create memory contexts for this procedure
         *
         * XXX it would be better to use PortalContext as the parent context, but
-        * we may not be inside a portal (consider deferred-trigger
-        * execution).  Perhaps CurTransactionContext would do?  For now it
-        * doesn't matter because we clean up explicitly in AtEOSubXact_SPI().
+        * we may not be inside a portal (consider deferred-trigger execution).
+        * Perhaps CurTransactionContext would do?      For now it doesn't matter
+        * because we clean up explicitly in AtEOSubXact_SPI().
         */
        _SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
                                                                                                  "SPI Proc",
-                                                                                               ALLOCSET_DEFAULT_MINSIZE,
-                                                                                          ALLOCSET_DEFAULT_INITSIZE,
-                                                                                          ALLOCSET_DEFAULT_MAXSIZE);
+                                                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                                                 ALLOCSET_DEFAULT_MAXSIZE);
        _SPI_current->execCxt = AllocSetContextCreate(TopTransactionContext,
                                                                                                  "SPI Exec",
-                                                                                               ALLOCSET_DEFAULT_MINSIZE,
-                                                                                          ALLOCSET_DEFAULT_INITSIZE,
-                                                                                          ALLOCSET_DEFAULT_MAXSIZE);
+                                                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                                                 ALLOCSET_DEFAULT_MAXSIZE);
        /* ... and switch to procedure's context */
        _SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt);
 
@@ -144,7 +148,9 @@ SPI_finish(void)
 
        /* Release memory used in procedure call */
        MemoryContextDelete(_SPI_current->execCxt);
+       _SPI_current->execCxt = NULL;
        MemoryContextDelete(_SPI_current->procCxt);
+       _SPI_current->procCxt = NULL;
 
        /*
         * Reset result variables, especially SPI_tuptable which is probably
@@ -155,9 +161,9 @@ SPI_finish(void)
        SPI_tuptable = NULL;
 
        /*
-        * After _SPI_begin_call _SPI_connected == _SPI_curid. Now we are
-        * closing connection to SPI and returning to upper Executor and so
-        * _SPI_connected must be equal to _SPI_curid.
+        * After _SPI_begin_call _SPI_connected == _SPI_curid. Now we are closing
+        * connection to SPI and returning to upper Executor and so _SPI_connected
+        * must be equal to _SPI_curid.
         */
        _SPI_connected--;
        _SPI_curid--;
@@ -176,15 +182,15 @@ void
 AtEOXact_SPI(bool isCommit)
 {
        /*
-        * Note that memory contexts belonging to SPI stack entries will be
-        * freed automatically, so we can ignore them here.  We just need to
-        * restore our static variables to initial state.
+        * Note that memory contexts belonging to SPI stack entries will be freed
+        * automatically, so we can ignore them here.  We just need to restore our
+        * static variables to initial state.
         */
        if (isCommit && _SPI_connected != -1)
                ereport(WARNING,
                                (errcode(ERRCODE_WARNING),
                                 errmsg("transaction left non-empty SPI stack"),
-                                errhint("Check for missing \"SPI_finish\" calls")));
+                                errhint("Check for missing \"SPI_finish\" calls.")));
 
        _SPI_current = _SPI_stack = NULL;
        _SPI_stack_depth = 0;
@@ -198,10 +204,10 @@ AtEOXact_SPI(bool isCommit)
  * Clean up SPI state at subtransaction commit or abort.
  *
  * During commit, there shouldn't be any unclosed entries remaining from
- * the current transaction; we throw them away if found.
+ * the current subtransaction; we emit a warning if any are found.
  */
 void
-AtEOSubXact_SPI(bool isCommit, TransactionId childXid)
+AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
 {
        bool            found = false;
 
@@ -209,16 +215,29 @@ AtEOSubXact_SPI(bool isCommit, TransactionId childXid)
        {
                _SPI_connection *connection = &(_SPI_stack[_SPI_connected]);
 
-               if (connection->connectXid != childXid)
+               if (connection->connectSubid != mySubid)
                        break;                          /* couldn't be any underneath it either */
 
                found = true;
 
+               /*
+                * Release procedure memory explicitly (see note in SPI_connect)
+                */
+               if (connection->execCxt)
+               {
+                       MemoryContextDelete(connection->execCxt);
+                       connection->execCxt = NULL;
+               }
+               if (connection->procCxt)
+               {
+                       MemoryContextDelete(connection->procCxt);
+                       connection->procCxt = NULL;
+               }
+
                /*
                 * Pop the stack entry and reset global variables.      Unlike
-                * SPI_finish(), we don't risk switching to memory contexts that
-                * might be already gone, or deleting memory contexts that have
-                * been or will be thrown away anyway.
+                * SPI_finish(), we don't risk switching to memory contexts that might
+                * be already gone.
                 */
                _SPI_connected--;
                _SPI_curid = _SPI_connected;
@@ -235,7 +254,7 @@ AtEOSubXact_SPI(bool isCommit, TransactionId childXid)
                ereport(WARNING,
                                (errcode(ERRCODE_WARNING),
                                 errmsg("subtransaction left non-empty SPI stack"),
-                                errhint("Check for missing \"SPI_finish\" calls")));
+                                errhint("Check for missing \"SPI_finish\" calls.")));
 }
 
 
@@ -253,9 +272,17 @@ SPI_pop(void)
        _SPI_curid--;
 }
 
-/* Parse, plan, and execute a querystring */
+/* Restore state of SPI stack after aborting a subtransaction */
+void
+SPI_restore_connection(void)
+{
+       Assert(_SPI_connected >= 0);
+       _SPI_curid = _SPI_connected - 1;
+}
+
+/* Parse, plan, and execute a query string */
 int
-SPI_execute(const char *src, bool read_only, int tcount)
+SPI_execute(const char *src, bool read_only, long tcount)
 {
        _SPI_plan       plan;
        int                     res;
@@ -284,7 +311,7 @@ SPI_execute(const char *src, bool read_only, int tcount)
 
 /* Obsolete version of SPI_execute */
 int
-SPI_exec(const char *src, int tcount)
+SPI_exec(const char *src, long tcount)
 {
        return SPI_execute(src, false, tcount);
 }
@@ -292,7 +319,7 @@ SPI_exec(const char *src, int tcount)
 /* Execute a previously prepared plan */
 int
 SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
-                                bool read_only, int tcount)
+                                bool read_only, long tcount)
 {
        int                     res;
 
@@ -317,7 +344,7 @@ SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
 
 /* Obsolete version of SPI_execute_plan */
 int
-SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
+SPI_execp(void *plan, Datum *Values, const char *Nulls, long tcount)
 {
        return SPI_execute_plan(plan, Values, Nulls, false, tcount);
 }
@@ -331,11 +358,11 @@ SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
  * Passing snapshot == InvalidSnapshot will select the normal behavior of
  * fetching a new snapshot for each query.
  */
-extern int
+int
 SPI_execute_snapshot(void *plan,
                                         Datum *Values, const char *Nulls,
                                         Snapshot snapshot, Snapshot crosscheck_snapshot,
-                                        bool read_only, int tcount)
+                                        bool read_only, long tcount)
 {
        int                     res;
 
@@ -533,8 +560,8 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
                mtuple = heap_formtuple(rel->rd_att, v, n);
 
                /*
-                * copy the identification info of the old tuple: t_ctid, t_self,
-                * and OID (if any)
+                * copy the identification info of the old tuple: t_ctid, t_self, and
+                * OID (if any)
                 */
                mtuple->t_data->t_ctid = tuple->t_data->t_ctid;
                mtuple->t_self = tuple->t_self;
@@ -602,14 +629,12 @@ SPI_fname(TupleDesc tupdesc, int fnumber)
 char *
 SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
 {
+       char       *result;
        Datum           origval,
-                               val,
-                               result;
+                               val;
        bool            isnull;
        Oid                     typoid,
-                               foutoid,
-                               typioparam;
-       int32           typmod;
+                               foutoid;
        bool            typisvarlena;
 
        SPI_result = 0;
@@ -626,37 +651,28 @@ SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
                return NULL;
 
        if (fnumber > 0)
-       {
                typoid = tupdesc->attrs[fnumber - 1]->atttypid;
-               typmod = tupdesc->attrs[fnumber - 1]->atttypmod;
-       }
        else
-       {
                typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
-               typmod = -1;
-       }
 
-       getTypeOutputInfo(typoid, &foutoid, &typioparam, &typisvarlena);
+       getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
 
        /*
-        * If we have a toasted datum, forcibly detoast it here to avoid
-        * memory leakage inside the type's output routine.
+        * If we have a toasted datum, forcibly detoast it here to avoid memory
+        * leakage inside the type's output routine.
         */
        if (typisvarlena)
                val = PointerGetDatum(PG_DETOAST_DATUM(origval));
        else
                val = origval;
 
-       result = OidFunctionCall3(foutoid,
-                                                         val,
-                                                         ObjectIdGetDatum(typioparam),
-                                                         Int32GetDatum(typmod));
+       result = OidOutputFunctionCall(foutoid, val);
 
        /* Clean up detoasted copy, if any */
        if (val != origval)
                pfree(DatumGetPointer(val));
 
-       return DatumGetCString(result);
+       return result;
 }
 
 Datum
@@ -735,6 +751,12 @@ SPI_getrelname(Relation rel)
        return pstrdup(RelationGetRelationName(rel));
 }
 
+char *
+SPI_getnspname(Relation rel)
+{
+       return get_namespace_name(RelationGetNamespace(rel));
+}
+
 void *
 SPI_palloc(Size size)
 {
@@ -807,7 +829,7 @@ SPI_cursor_open(const char *name, void *plan,
        Portal          portal;
        int                     k;
 
-       /* Ensure that the plan contains only one regular SELECT query */
+       /* Ensure that the plan contains only one query */
        if (list_length(ptlist) != 1 || list_length(qtlist) != 1)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
@@ -815,16 +837,29 @@ SPI_cursor_open(const char *name, void *plan,
        queryTree = (Query *) linitial((List *) linitial(qtlist));
        planTree = (Plan *) linitial(ptlist);
 
-       if (queryTree->commandType != CMD_SELECT)
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-                                errmsg("cannot open non-SELECT query as cursor")));
-       if (queryTree->into != NULL)
-               ereport(ERROR,
-                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-                                errmsg("cannot open SELECT INTO query as cursor")));
+       /* Must be a query that returns tuples */
+       switch (queryTree->commandType)
+       {
+               case CMD_SELECT:
+                       if (queryTree->into != NULL)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+                                                errmsg("cannot open SELECT INTO query as cursor")));
+                       break;
+               case CMD_UTILITY:
+                       if (!UtilityReturnsTuples(queryTree->utilityStmt))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+                                                errmsg("cannot open non-SELECT query as cursor")));
+                       break;
+               default:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+                                        errmsg("cannot open non-SELECT query as cursor")));
+                       break;
+       }
 
-       /* Reset SPI result */
+       /* Reset SPI result (note we deliberately don't touch lastoid) */
        SPI_processed = 0;
        SPI_tuptable = NULL;
        _SPI_current->processed = 0;
@@ -885,8 +920,8 @@ SPI_cursor_open(const char *name, void *plan,
         * Set up the portal.
         */
        PortalDefineQuery(portal,
-                                         NULL,         /* unfortunately don't have sourceText */
-                                         "SELECT", /* cursor's query is always a SELECT */
+                                         spiplan->query,
+                                         "SELECT", /* don't have the raw parse tree... */
                                          list_make1(queryTree),
                                          list_make1(planTree),
                                          PortalGetHeapMemory(portal));
@@ -897,14 +932,14 @@ SPI_cursor_open(const char *name, void *plan,
         * Set up options for portal.
         */
        portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
-       if (ExecSupportsBackwardScan(plan))
+       if (planTree == NULL || ExecSupportsBackwardScan(planTree))
                portal->cursorOptions |= CURSOR_OPT_SCROLL;
        else
                portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
 
        /*
-        * Set up the snapshot to use.  (PortalStart will do CopySnapshot,
-        * so we skip that here.)
+        * Set up the snapshot to use.  (PortalStart will do CopySnapshot, so we
+        * skip that here.)
         */
        if (read_only)
                snapshot = ActiveSnapshot;
@@ -919,7 +954,8 @@ SPI_cursor_open(const char *name, void *plan,
         */
        PortalStart(portal, paramLI, snapshot);
 
-       Assert(portal->strategy == PORTAL_ONE_SELECT);
+       Assert(portal->strategy == PORTAL_ONE_SELECT ||
+                  portal->strategy == PORTAL_UTIL_SELECT);
 
        /* Return the created portal */
        return portal;
@@ -944,11 +980,11 @@ SPI_cursor_find(const char *name)
  *     Fetch rows in a cursor
  */
 void
-SPI_cursor_fetch(Portal portal, bool forward, int count)
+SPI_cursor_fetch(Portal portal, bool forward, long count)
 {
        _SPI_cursor_operation(portal, forward, count,
-                                                 CreateDestReceiver(SPI, NULL));
-       /* we know that the SPI receiver doesn't need a destroy call */
+                                                 CreateDestReceiver(DestSPI, NULL));
+       /* we know that the DestSPI receiver doesn't need a destroy call */
 }
 
 
@@ -958,7 +994,7 @@ SPI_cursor_fetch(Portal portal, bool forward, int count)
  *     Move in a cursor
  */
 void
-SPI_cursor_move(Portal portal, bool forward, int count)
+SPI_cursor_move(Portal portal, bool forward, long count)
 {
        _SPI_cursor_operation(portal, forward, count, None_Receiver);
 }
@@ -1152,7 +1188,7 @@ spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
  *             of current SPI procedure
  */
 void
-spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
+spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 {
        SPITupleTable *tuptable;
        MemoryContext oldcxt;
@@ -1177,10 +1213,11 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
                tuptable->free = 256;
                tuptable->alloced += tuptable->free;
                tuptable->vals = (HeapTuple *) repalloc(tuptable->vals,
-                                                                 tuptable->alloced * sizeof(HeapTuple));
+                                                                         tuptable->alloced * sizeof(HeapTuple));
        }
 
-       tuptable->vals[tuptable->alloced - tuptable->free] = heap_copytuple(tuple);
+       tuptable->vals[tuptable->alloced - tuptable->free] =
+               ExecCopySlotTuple(slot);
        (tuptable->free)--;
 
        MemoryContextSwitchTo(oldcxt);
@@ -1209,9 +1246,9 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
        int                     nargs = plan->nargs;
 
        /*
-        * Increment CommandCounter to see changes made by now.  We must do
-        * this to be sure of seeing any schema changes made by a just-preceding
-        * SPI command.  (But we don't bother advancing the snapshot, since the
+        * Increment CommandCounter to see changes made by now.  We must do this
+        * to be sure of seeing any schema changes made by a just-preceding SPI
+        * command.  (But we don't bother advancing the snapshot, since the
         * planner generally operates under SnapshotNow rules anyway.)
         */
        CommandCounterIncrement();
@@ -1232,9 +1269,9 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
        /*
         * Do parse analysis and rule rewrite for each raw parsetree.
         *
-        * We save the querytrees from each raw parsetree as a separate
-        * sublist.  This allows _SPI_execute_plan() to know where the
-        * boundaries between original queries fall.
+        * We save the querytrees from each raw parsetree as a separate sublist.
+        * This allows _SPI_execute_plan() to know where the boundaries between
+        * original queries fall.
         */
        query_list_list = NIL;
        plan_list = NIL;
@@ -1244,7 +1281,7 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
                Node       *parsetree = (Node *) lfirst(list_item);
                List       *query_list;
 
-               query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
+               query_list = pg_analyze_and_rewrite(parsetree, src, argtypes, nargs);
 
                query_list_list = lappend(query_list_list, query_list);
 
@@ -1273,9 +1310,12 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
 static int
 _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                                  Snapshot snapshot, Snapshot crosscheck_snapshot,
-                                 bool read_only, int tcount)
+                                 bool read_only, long tcount)
 {
        volatile int res = 0;
+       volatile uint32 my_processed = 0;
+       volatile Oid my_lastoid = InvalidOid;
+       SPITupleTable *volatile my_tuptable = NULL;
        Snapshot        saveActiveSnapshot;
 
        /* Be sure to restore ActiveSnapshot on error exit */
@@ -1310,12 +1350,6 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                else
                        paramLI = NULL;
 
-               /* Reset state (only needed in case string is empty) */
-               SPI_processed = 0;
-               SPI_lastoid = InvalidOid;
-               SPI_tuptable = NULL;
-               _SPI_current->tuptable = NULL;
-
                /*
                 * Setup error traceback support for ereport()
                 */
@@ -1329,13 +1363,6 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                        List       *query_list = lfirst(query_list_list_item);
                        ListCell   *query_list_item;
 
-                       /* Reset state for each original parsetree */
-                       /* (at most one of its querytrees will be marked canSetTag) */
-                       SPI_processed = 0;
-                       SPI_lastoid = InvalidOid;
-                       SPI_tuptable = NULL;
-                       _SPI_current->tuptable = NULL;
-
                        foreach(query_list_item, query_list)
                        {
                                Query      *queryTree = (Query *) lfirst(query_list_item);
@@ -1346,6 +1373,10 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                                planTree = lfirst(plan_list_item);
                                plan_list_item = lnext(plan_list_item);
 
+                               _SPI_current->processed = 0;
+                               _SPI_current->lastoid = InvalidOid;
+                               _SPI_current->tuptable = NULL;
+
                                if (queryTree->commandType == CMD_UTILITY)
                                {
                                        if (IsA(queryTree->utilityStmt, CopyStmt))
@@ -1375,9 +1406,10 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                                if (read_only && !QueryIsReadOnly(queryTree))
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        /* translator: %s is a SQL statement name */
-                                                        errmsg("%s is not allowed in a non-volatile function",
-                                                                       CreateQueryTag(queryTree))));
+                                       /* translator: %s is a SQL statement name */
+                                          errmsg("%s is not allowed in a non-volatile function",
+                                                         CreateQueryTag(queryTree))));
+
                                /*
                                 * If not read-only mode, advance the command counter before
                                 * each command.
@@ -1385,7 +1417,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                                if (!read_only)
                                        CommandCounterIncrement();
 
-                               dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None,
+                               dest = CreateDestReceiver(queryTree->canSetTag ? DestSPI : DestNone,
                                                                                  NULL);
 
                                if (snapshot == InvalidSnapshot)
@@ -1430,6 +1462,24 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
                                }
                                FreeSnapshot(ActiveSnapshot);
                                ActiveSnapshot = NULL;
+
+                               /*
+                                * The last canSetTag query sets the auxiliary values returned
+                                * to the caller.  Be careful to free any tuptables not
+                                * returned, to avoid intratransaction memory leak.
+                                */
+                               if (queryTree->canSetTag)
+                               {
+                                       my_processed = _SPI_current->processed;
+                                       my_lastoid = _SPI_current->lastoid;
+                                       SPI_freetuptable(my_tuptable);
+                                       my_tuptable = _SPI_current->tuptable;
+                               }
+                               else
+                               {
+                                       SPI_freetuptable(_SPI_current->tuptable);
+                                       _SPI_current->tuptable = NULL;
+                               }
                                /* we know that the receiver doesn't need a destroy call */
                                if (res < 0)
                                        goto fail;
@@ -1453,25 +1503,34 @@ fail:
 
        ActiveSnapshot = saveActiveSnapshot;
 
+       /* Save results for caller */
+       SPI_processed = my_processed;
+       SPI_lastoid = my_lastoid;
+       SPI_tuptable = my_tuptable;
+
        return res;
 }
 
 static int
-_SPI_pquery(QueryDesc *queryDesc, int tcount)
+_SPI_pquery(QueryDesc *queryDesc, long tcount)
 {
        int                     operation = queryDesc->operation;
        int                     res;
-       Oid                     save_lastoid;
 
        switch (operation)
        {
                case CMD_SELECT:
                        res = SPI_OK_SELECT;
-                       if (queryDesc->parsetree->into != NULL)         /* select into table */
+                       if (queryDesc->parsetree->into)         /* select into table? */
                        {
                                res = SPI_OK_SELINTO;
                                queryDesc->dest = None_Receiver;                /* don't output results */
                        }
+                       else if (queryDesc->dest->mydest != DestSPI)
+                       {
+                               /* Don't return SPI_OK_SELECT if we're discarding result */
+                               res = SPI_OK_UTILITY;
+                       }
                        break;
                case CMD_INSERT:
                        res = SPI_OK_INSERT;
@@ -1493,35 +1552,23 @@ _SPI_pquery(QueryDesc *queryDesc, int tcount)
 
        AfterTriggerBeginQuery();
 
-       ExecutorStart(queryDesc, false);
+       ExecutorStart(queryDesc, 0);
 
-       ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
+       ExecutorRun(queryDesc, ForwardScanDirection, tcount);
 
        _SPI_current->processed = queryDesc->estate->es_processed;
-       save_lastoid = queryDesc->estate->es_lastoid;
+       _SPI_current->lastoid = queryDesc->estate->es_lastoid;
 
-       if (operation == CMD_SELECT && queryDesc->dest->mydest == SPI)
+       if (operation == CMD_SELECT && queryDesc->dest->mydest == DestSPI)
        {
                if (_SPI_checktuples())
                        elog(ERROR, "consistency check on SPI tuple count failed");
        }
 
-       ExecutorEnd(queryDesc);
-
        /* Take care of any queued AFTER triggers */
-       AfterTriggerEndQuery();
+       AfterTriggerEndQuery(queryDesc->estate);
 
-       if (queryDesc->dest->mydest == SPI)
-       {
-               SPI_processed = _SPI_current->processed;
-               SPI_lastoid = save_lastoid;
-               SPI_tuptable = _SPI_current->tuptable;
-       }
-       else if (res == SPI_OK_SELECT)
-       {
-               /* Don't return SPI_OK_SELECT if we discarded the result */
-               res = SPI_OK_UTILITY;
-       }
+       ExecutorEnd(queryDesc);
 
 #ifdef SPI_EXECUTOR_STATS
        if (ShowExecutorStats)
@@ -1543,8 +1590,8 @@ _SPI_error_callback(void *arg)
        int                     syntaxerrposition;
 
        /*
-        * If there is a syntax error position, convert to internal syntax
-        * error; otherwise treat the query as an item of context stack
+        * If there is a syntax error position, convert to internal syntax error;
+        * otherwise treat the query as an item of context stack
         */
        syntaxerrposition = geterrposition();
        if (syntaxerrposition > 0)
@@ -1554,7 +1601,7 @@ _SPI_error_callback(void *arg)
                internalerrquery(query);
        }
        else
-               errcontext("SQL query \"%s\"", query);
+               errcontext("SQL statement \"%s\"", query);
 }
 
 /*
@@ -1563,7 +1610,7 @@ _SPI_error_callback(void *arg)
  *     Do a FETCH or MOVE in a cursor
  */
 static void
-_SPI_cursor_operation(Portal portal, bool forward, int count,
+_SPI_cursor_operation(Portal portal, bool forward, long count,
                                          DestReceiver *dest)
 {
        long            nfetched;
@@ -1576,7 +1623,7 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
        if (_SPI_begin_call(true) < 0)
                elog(ERROR, "SPI cursor operation called while not connected");
 
-       /* Reset the SPI result */
+       /* Reset the SPI result (note we deliberately don't touch lastoid) */
        SPI_processed = 0;
        SPI_tuptable = NULL;
        _SPI_current->processed = 0;
@@ -1585,21 +1632,20 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
        /* Run the cursor */
        nfetched = PortalRunFetch(portal,
                                                          forward ? FETCH_FORWARD : FETCH_BACKWARD,
-                                                         (long) count,
+                                                         count,
                                                          dest);
 
        /*
-        * Think not to combine this store with the preceding function call.
-        * If the portal contains calls to functions that use SPI, then
-        * SPI_stack is likely to move around while the portal runs.  When
-        * control returns, _SPI_current will point to the correct stack
-        * entry... but the pointer may be different than it was beforehand.
-        * So we must be sure to re-fetch the pointer after the function call
-        * completes.
+        * Think not to combine this store with the preceding function call. If
+        * the portal contains calls to functions that use SPI, then SPI_stack is
+        * likely to move around while the portal runs.  When control returns,
+        * _SPI_current will point to the correct stack entry... but the pointer
+        * may be different than it was beforehand. So we must be sure to re-fetch
+        * the pointer after the function call completes.
         */
        _SPI_current->processed = nfetched;
 
-       if (dest->mydest == SPI && _SPI_checktuples())
+       if (dest->mydest == DestSPI && _SPI_checktuples())
                elog(ERROR, "consistency check on SPI tuple count failed");
 
        /* Put the result into place for access by caller */
@@ -1612,13 +1658,13 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
 
 
 static MemoryContext
-_SPI_execmem()
+_SPI_execmem(void)
 {
        return MemoryContextSwitchTo(_SPI_current->execCxt);
 }
 
 static MemoryContext
-_SPI_procmem()
+_SPI_procmem(void)
 {
        return MemoryContextSwitchTo(_SPI_current->procCxt);
 }
@@ -1693,12 +1739,12 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
        else if (location == _SPI_CPLAN_TOPCXT)
                parentcxt = TopMemoryContext;
        else
-/* (this case not currently used) */
+               /* (this case not currently used) */
                parentcxt = CurrentMemoryContext;
 
        /*
-        * Create a memory context for the plan.  We don't expect the plan to
-        * be very large, so use smaller-than-default alloc parameters.
+        * Create a memory context for the plan.  We don't expect the plan to be
+        * very large, so use smaller-than-default alloc parameters.
         */
        plancxt = AllocSetContextCreate(parentcxt,
                                                                        "SPI Plan",