summaryrefslogtreecommitdiffstats
path: root/src/backend/executor/spi.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
commit5e45211a64149b3c659b90ff2de6fa982a5a93ed (patch)
tree739caf8c461053357daa9f162bef34516c7bf452 /src/backend/executor/spi.c
parentInitial commit. (diff)
downloadpostgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.tar.xz
postgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.zip
Adding upstream version 15.5.upstream/15.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/executor/spi.c')
-rw-r--r--src/backend/executor/spi.c3380
1 files changed, 3380 insertions, 0 deletions
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
new file mode 100644
index 0000000..1edbd43
--- /dev/null
+++ b/src/backend/executor/spi.c
@@ -0,0 +1,3380 @@
+/*-------------------------------------------------------------------------
+ *
+ * spi.c
+ * Server Programming Interface
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/spi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/printtup.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/heap.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/executor.h"
+#include "executor/spi_priv.h"
+#include "miscadmin.h"
+#include "tcop/pquery.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+
+/*
+ * These global variables are part of the API for various SPI functions
+ * (a horrible API choice, but it's too late now). To reduce the risk of
+ * interference between different SPI callers, we save and restore them
+ * when entering/exiting a SPI nesting level.
+ */
+uint64 SPI_processed = 0;
+SPITupleTable *SPI_tuptable = NULL;
+int SPI_result = 0;
+
+static _SPI_connection *_SPI_stack = NULL;
+static _SPI_connection *_SPI_current = NULL;
+static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
+static int _SPI_connected = -1; /* current stack index */
+
+typedef struct SPICallbackArg
+{
+ const char *query;
+ RawParseMode mode;
+} SPICallbackArg;
+
+static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
+ ParamListInfo paramLI, bool read_only);
+
+static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan);
+
+static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan);
+
+static int _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool fire_triggers);
+
+static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes,
+ Datum *Values, const char *Nulls);
+
+static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount);
+
+static void _SPI_error_callback(void *arg);
+
+static void _SPI_cursor_operation(Portal portal,
+ FetchDirection direction, long count,
+ DestReceiver *dest);
+
+static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan);
+static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
+
+static int _SPI_begin_call(bool use_exec);
+static int _SPI_end_call(bool use_exec);
+static MemoryContext _SPI_execmem(void);
+static MemoryContext _SPI_procmem(void);
+static bool _SPI_checktuples(void);
+
+
+/* =================== interface functions =================== */
+
+int
+SPI_connect(void)
+{
+ return SPI_connect_ext(0);
+}
+
+int
+SPI_connect_ext(int options)
+{
+ int newdepth;
+
+ /* Enlarge stack if necessary */
+ if (_SPI_stack == NULL)
+ {
+ if (_SPI_connected != -1 || _SPI_stack_depth != 0)
+ elog(ERROR, "SPI stack corrupted");
+ newdepth = 16;
+ _SPI_stack = (_SPI_connection *)
+ MemoryContextAlloc(TopMemoryContext,
+ newdepth * sizeof(_SPI_connection));
+ _SPI_stack_depth = newdepth;
+ }
+ else
+ {
+ if (_SPI_stack_depth <= 0 || _SPI_stack_depth <= _SPI_connected)
+ elog(ERROR, "SPI stack corrupted");
+ if (_SPI_stack_depth == _SPI_connected + 1)
+ {
+ newdepth = _SPI_stack_depth * 2;
+ _SPI_stack = (_SPI_connection *)
+ repalloc(_SPI_stack,
+ newdepth * sizeof(_SPI_connection));
+ _SPI_stack_depth = newdepth;
+ }
+ }
+
+ /* Enter new stack level */
+ _SPI_connected++;
+ Assert(_SPI_connected >= 0 && _SPI_connected < _SPI_stack_depth);
+
+ _SPI_current = &(_SPI_stack[_SPI_connected]);
+ _SPI_current->processed = 0;
+ _SPI_current->tuptable = NULL;
+ _SPI_current->execSubid = InvalidSubTransactionId;
+ slist_init(&_SPI_current->tuptables);
+ _SPI_current->procCxt = NULL; /* in case we fail to create 'em */
+ _SPI_current->execCxt = NULL;
+ _SPI_current->connectSubid = GetCurrentSubTransactionId();
+ _SPI_current->queryEnv = NULL;
+ _SPI_current->atomic = (options & SPI_OPT_NONATOMIC ? false : true);
+ _SPI_current->internal_xact = false;
+ _SPI_current->outer_processed = SPI_processed;
+ _SPI_current->outer_tuptable = SPI_tuptable;
+ _SPI_current->outer_result = SPI_result;
+
+ /*
+ * Create memory contexts for this procedure
+ *
+ * In atomic contexts (the normal case), we use TopTransactionContext,
+ * otherwise PortalContext, so that it lives across transaction
+ * boundaries.
+ *
+ * XXX It could be better to use PortalContext as the parent context in
+ * all cases, but we may not be inside a portal (consider deferred-trigger
+ * execution). Perhaps CurTransactionContext could be an option? For now
+ * it doesn't matter because we clean up explicitly in AtEOSubXact_SPI();
+ * but see also AtEOXact_SPI().
+ */
+ _SPI_current->procCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : PortalContext,
+ "SPI Proc",
+ ALLOCSET_DEFAULT_SIZES);
+ _SPI_current->execCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : _SPI_current->procCxt,
+ "SPI Exec",
+ ALLOCSET_DEFAULT_SIZES);
+ /* ... and switch to procedure's context */
+ _SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt);
+
+ /*
+ * Reset API global variables so that current caller cannot accidentally
+ * depend on state of an outer caller.
+ */
+ SPI_processed = 0;
+ SPI_tuptable = NULL;
+ SPI_result = 0;
+
+ return SPI_OK_CONNECT;
+}
+
+int
+SPI_finish(void)
+{
+ int res;
+
+ res = _SPI_begin_call(false); /* just check we're connected */
+ if (res < 0)
+ return res;
+
+ /* Restore memory context as it was before procedure call */
+ MemoryContextSwitchTo(_SPI_current->savedcxt);
+
+ /* Release memory used in procedure call (including tuptables) */
+ MemoryContextDelete(_SPI_current->execCxt);
+ _SPI_current->execCxt = NULL;
+ MemoryContextDelete(_SPI_current->procCxt);
+ _SPI_current->procCxt = NULL;
+
+ /*
+ * Restore outer API variables, especially SPI_tuptable which is probably
+ * pointing at a just-deleted tuptable
+ */
+ SPI_processed = _SPI_current->outer_processed;
+ SPI_tuptable = _SPI_current->outer_tuptable;
+ SPI_result = _SPI_current->outer_result;
+
+ /* Exit stack level */
+ _SPI_connected--;
+ if (_SPI_connected < 0)
+ _SPI_current = NULL;
+ else
+ _SPI_current = &(_SPI_stack[_SPI_connected]);
+
+ return SPI_OK_FINISH;
+}
+
+/*
+ * SPI_start_transaction is a no-op, kept for backwards compatibility.
+ * SPI callers are *always* inside a transaction.
+ */
+void
+SPI_start_transaction(void)
+{
+}
+
+static void
+_SPI_commit(bool chain)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+ SavedTransactionCharacteristics savetc;
+
+ /*
+ * Complain if we are in a context that doesn't permit transaction
+ * termination. (Note: here and _SPI_rollback should be the only places
+ * that throw ERRCODE_INVALID_TRANSACTION_TERMINATION, so that callers can
+ * test for that with security that they know what happened.)
+ */
+ if (_SPI_current->atomic)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("invalid transaction termination")));
+
+ /*
+ * This restriction is required by PLs implemented on top of SPI. They
+ * use subtransactions to establish exception blocks that are supposed to
+ * be rolled back together if there is an error. Terminating the
+ * top-level transaction in such a block violates that idea. A future PL
+ * implementation might have different ideas about this, in which case
+ * this restriction would have to be refined or the check possibly be
+ * moved out of SPI into the PLs. Note however that the code below relies
+ * on not being within a subtransaction.
+ */
+ if (IsSubTransaction())
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("cannot commit while a subtransaction is active")));
+
+ if (chain)
+ SaveTransactionCharacteristics(&savetc);
+
+ /* Catch any error occurring during the COMMIT */
+ PG_TRY();
+ {
+ /* Protect current SPI stack entry against deletion */
+ _SPI_current->internal_xact = true;
+
+ /*
+ * Hold any pinned portals that any PLs might be using. We have to do
+ * this before changing transaction state, since this will run
+ * user-defined code that might throw an error.
+ */
+ HoldPinnedPortals();
+
+ /* Release snapshots associated with portals */
+ ForgetPortalSnapshots();
+
+ /* Do the deed */
+ CommitTransactionCommand();
+
+ /* Immediately start a new transaction */
+ StartTransactionCommand();
+ if (chain)
+ RestoreTransactionCharacteristics(&savetc);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ _SPI_current->internal_xact = false;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info in caller's context */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /*
+ * Abort the failed transaction. If this fails too, we'll just
+ * propagate the error out ... there's not that much we can do.
+ */
+ AbortCurrentTransaction();
+
+ /* ... and start a new one */
+ StartTransactionCommand();
+ if (chain)
+ RestoreTransactionCharacteristics(&savetc);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ _SPI_current->internal_xact = false;
+
+ /* Now that we've cleaned up the transaction, re-throw the error */
+ ReThrowError(edata);
+ }
+ PG_END_TRY();
+}
+
+void
+SPI_commit(void)
+{
+ _SPI_commit(false);
+}
+
+void
+SPI_commit_and_chain(void)
+{
+ _SPI_commit(true);
+}
+
+static void
+_SPI_rollback(bool chain)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+ SavedTransactionCharacteristics savetc;
+
+ /* see under SPI_commit() */
+ if (_SPI_current->atomic)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("invalid transaction termination")));
+
+ /* see under SPI_commit() */
+ if (IsSubTransaction())
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("cannot roll back while a subtransaction is active")));
+
+ if (chain)
+ SaveTransactionCharacteristics(&savetc);
+
+ /* Catch any error occurring during the ROLLBACK */
+ PG_TRY();
+ {
+ /* Protect current SPI stack entry against deletion */
+ _SPI_current->internal_xact = true;
+
+ /*
+ * Hold any pinned portals that any PLs might be using. We have to do
+ * this before changing transaction state, since this will run
+ * user-defined code that might throw an error, and in any case
+ * couldn't be run in an already-aborted transaction.
+ */
+ HoldPinnedPortals();
+
+ /* Release snapshots associated with portals */
+ ForgetPortalSnapshots();
+
+ /* Do the deed */
+ AbortCurrentTransaction();
+
+ /* Immediately start a new transaction */
+ StartTransactionCommand();
+ if (chain)
+ RestoreTransactionCharacteristics(&savetc);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ _SPI_current->internal_xact = false;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info in caller's context */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /*
+ * Try again to abort the failed transaction. If this fails too,
+ * we'll just propagate the error out ... there's not that much we can
+ * do.
+ */
+ AbortCurrentTransaction();
+
+ /* ... and start a new one */
+ StartTransactionCommand();
+ if (chain)
+ RestoreTransactionCharacteristics(&savetc);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ _SPI_current->internal_xact = false;
+
+ /* Now that we've cleaned up the transaction, re-throw the error */
+ ReThrowError(edata);
+ }
+ PG_END_TRY();
+}
+
+void
+SPI_rollback(void)
+{
+ _SPI_rollback(false);
+}
+
+void
+SPI_rollback_and_chain(void)
+{
+ _SPI_rollback(true);
+}
+
+/*
+ * Clean up SPI state at transaction commit or abort.
+ */
+void
+AtEOXact_SPI(bool isCommit)
+{
+ bool found = false;
+
+ /*
+ * Pop stack entries, stopping if we find one marked internal_xact (that
+ * one belongs to the caller of SPI_commit or SPI_abort).
+ */
+ while (_SPI_connected >= 0)
+ {
+ _SPI_connection *connection = &(_SPI_stack[_SPI_connected]);
+
+ if (connection->internal_xact)
+ break;
+
+ found = true;
+
+ /*
+ * We need not release the procedure's memory contexts explicitly, as
+ * they'll go away automatically when their parent context does; see
+ * notes in SPI_connect_ext.
+ */
+
+ /*
+ * Restore outer global variables and pop the stack entry. Unlike
+ * SPI_finish(), we don't risk switching to memory contexts that might
+ * be already gone.
+ */
+ SPI_processed = connection->outer_processed;
+ SPI_tuptable = connection->outer_tuptable;
+ SPI_result = connection->outer_result;
+
+ _SPI_connected--;
+ if (_SPI_connected < 0)
+ _SPI_current = NULL;
+ else
+ _SPI_current = &(_SPI_stack[_SPI_connected]);
+ }
+
+ /* We should only find entries to pop during an ABORT. */
+ if (found && isCommit)
+ ereport(WARNING,
+ (errcode(ERRCODE_WARNING),
+ errmsg("transaction left non-empty SPI stack"),
+ errhint("Check for missing \"SPI_finish\" calls.")));
+}
+
+/*
+ * Clean up SPI state at subtransaction commit or abort.
+ *
+ * During commit, there shouldn't be any unclosed entries remaining from
+ * the current subtransaction; we emit a warning if any are found.
+ */
+void
+AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
+{
+ bool found = false;
+
+ while (_SPI_connected >= 0)
+ {
+ _SPI_connection *connection = &(_SPI_stack[_SPI_connected]);
+
+ if (connection->connectSubid != mySubid)
+ break; /* couldn't be any underneath it either */
+
+ if (connection->internal_xact)
+ break;
+
+ 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;
+ }
+
+ /*
+ * Restore outer global variables and pop the stack entry. Unlike
+ * SPI_finish(), we don't risk switching to memory contexts that might
+ * be already gone.
+ */
+ SPI_processed = connection->outer_processed;
+ SPI_tuptable = connection->outer_tuptable;
+ SPI_result = connection->outer_result;
+
+ _SPI_connected--;
+ if (_SPI_connected < 0)
+ _SPI_current = NULL;
+ else
+ _SPI_current = &(_SPI_stack[_SPI_connected]);
+ }
+
+ if (found && isCommit)
+ ereport(WARNING,
+ (errcode(ERRCODE_WARNING),
+ errmsg("subtransaction left non-empty SPI stack"),
+ errhint("Check for missing \"SPI_finish\" calls.")));
+
+ /*
+ * If we are aborting a subtransaction and there is an open SPI context
+ * surrounding the subxact, clean up to prevent memory leakage.
+ */
+ if (_SPI_current && !isCommit)
+ {
+ slist_mutable_iter siter;
+
+ /*
+ * Throw away executor state if current executor operation was started
+ * within current subxact (essentially, force a _SPI_end_call(true)).
+ */
+ if (_SPI_current->execSubid >= mySubid)
+ {
+ _SPI_current->execSubid = InvalidSubTransactionId;
+ MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
+ }
+
+ /* throw away any tuple tables created within current subxact */
+ slist_foreach_modify(siter, &_SPI_current->tuptables)
+ {
+ SPITupleTable *tuptable;
+
+ tuptable = slist_container(SPITupleTable, next, siter.cur);
+ if (tuptable->subid >= mySubid)
+ {
+ /*
+ * If we used SPI_freetuptable() here, its internal search of
+ * the tuptables list would make this operation O(N^2).
+ * Instead, just free the tuptable manually. This should
+ * match what SPI_freetuptable() does.
+ */
+ slist_delete_current(&siter);
+ if (tuptable == _SPI_current->tuptable)
+ _SPI_current->tuptable = NULL;
+ if (tuptable == SPI_tuptable)
+ SPI_tuptable = NULL;
+ MemoryContextDelete(tuptable->tuptabcxt);
+ }
+ }
+ }
+}
+
+/*
+ * Are we executing inside a procedure (that is, a nonatomic SPI context)?
+ */
+bool
+SPI_inside_nonatomic_context(void)
+{
+ if (_SPI_current == NULL)
+ return false; /* not in any SPI context at all */
+ if (_SPI_current->atomic)
+ return false; /* it's atomic (ie function not procedure) */
+ return true;
+}
+
+
+/* Parse, plan, and execute a query string */
+int
+SPI_execute(const char *src, bool read_only, long tcount)
+{
+ _SPI_plan plan;
+ SPIExecuteOptions options;
+ int res;
+
+ if (src == NULL || tcount < 0)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
+
+ _SPI_prepare_oneshot_plan(src, &plan);
+
+ memset(&options, 0, sizeof(options));
+ options.read_only = read_only;
+ options.tcount = tcount;
+
+ res = _SPI_execute_plan(&plan, &options,
+ InvalidSnapshot, InvalidSnapshot,
+ true);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+/* Obsolete version of SPI_execute */
+int
+SPI_exec(const char *src, long tcount)
+{
+ return SPI_execute(src, false, tcount);
+}
+
+/* Parse, plan, and execute a query string, with extensible options */
+int
+SPI_execute_extended(const char *src,
+ const SPIExecuteOptions *options)
+{
+ int res;
+ _SPI_plan plan;
+
+ if (src == NULL || options == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
+ if (options->params)
+ {
+ plan.parserSetup = options->params->parserSetup;
+ plan.parserSetupArg = options->params->parserSetupArg;
+ }
+
+ _SPI_prepare_oneshot_plan(src, &plan);
+
+ res = _SPI_execute_plan(&plan, options,
+ InvalidSnapshot, InvalidSnapshot,
+ true);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+/* Execute a previously prepared plan */
+int
+SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
+ bool read_only, long tcount)
+{
+ SPIExecuteOptions options;
+ int res;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
+ return SPI_ERROR_ARGUMENT;
+
+ if (plan->nargs > 0 && Values == NULL)
+ return SPI_ERROR_PARAM;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ memset(&options, 0, sizeof(options));
+ options.params = _SPI_convert_params(plan->nargs, plan->argtypes,
+ Values, Nulls);
+ options.read_only = read_only;
+ options.tcount = tcount;
+
+ res = _SPI_execute_plan(plan, &options,
+ InvalidSnapshot, InvalidSnapshot,
+ true);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+/* Obsolete version of SPI_execute_plan */
+int
+SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
+{
+ return SPI_execute_plan(plan, Values, Nulls, false, tcount);
+}
+
+/* Execute a previously prepared plan */
+int
+SPI_execute_plan_extended(SPIPlanPtr plan,
+ const SPIExecuteOptions *options)
+{
+ int res;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || options == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ res = _SPI_execute_plan(plan, options,
+ InvalidSnapshot, InvalidSnapshot,
+ true);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+/* Execute a previously prepared plan */
+int
+SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
+ bool read_only, long tcount)
+{
+ SPIExecuteOptions options;
+ int res;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ memset(&options, 0, sizeof(options));
+ options.params = params;
+ options.read_only = read_only;
+ options.tcount = tcount;
+
+ res = _SPI_execute_plan(plan, &options,
+ InvalidSnapshot, InvalidSnapshot,
+ true);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+/*
+ * SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow
+ * the caller to specify exactly which snapshots to use, which will be
+ * registered here. Also, the caller may specify that AFTER triggers should be
+ * queued as part of the outer query rather than being fired immediately at the
+ * end of the command.
+ *
+ * This is currently not documented in spi.sgml because it is only intended
+ * for use by RI triggers.
+ *
+ * Passing snapshot == InvalidSnapshot will select the normal behavior of
+ * fetching a new snapshot for each query.
+ */
+int
+SPI_execute_snapshot(SPIPlanPtr plan,
+ Datum *Values, const char *Nulls,
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool read_only, bool fire_triggers, long tcount)
+{
+ SPIExecuteOptions options;
+ int res;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
+ return SPI_ERROR_ARGUMENT;
+
+ if (plan->nargs > 0 && Values == NULL)
+ return SPI_ERROR_PARAM;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ memset(&options, 0, sizeof(options));
+ options.params = _SPI_convert_params(plan->nargs, plan->argtypes,
+ Values, Nulls);
+ options.read_only = read_only;
+ options.tcount = tcount;
+
+ res = _SPI_execute_plan(plan, &options,
+ snapshot, crosscheck_snapshot,
+ fire_triggers);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+/*
+ * SPI_execute_with_args -- plan and execute a query with supplied arguments
+ *
+ * This is functionally equivalent to SPI_prepare followed by
+ * SPI_execute_plan.
+ */
+int
+SPI_execute_with_args(const char *src,
+ int nargs, Oid *argtypes,
+ Datum *Values, const char *Nulls,
+ bool read_only, long tcount)
+{
+ int res;
+ _SPI_plan plan;
+ ParamListInfo paramLI;
+ SPIExecuteOptions options;
+
+ if (src == NULL || nargs < 0 || tcount < 0)
+ return SPI_ERROR_ARGUMENT;
+
+ if (nargs > 0 && (argtypes == NULL || Values == NULL))
+ return SPI_ERROR_PARAM;
+
+ res = _SPI_begin_call(true);
+ if (res < 0)
+ return res;
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
+ plan.nargs = nargs;
+ plan.argtypes = argtypes;
+ plan.parserSetup = NULL;
+ plan.parserSetupArg = NULL;
+
+ paramLI = _SPI_convert_params(nargs, argtypes,
+ Values, Nulls);
+
+ _SPI_prepare_oneshot_plan(src, &plan);
+
+ memset(&options, 0, sizeof(options));
+ options.params = paramLI;
+ options.read_only = read_only;
+ options.tcount = tcount;
+
+ res = _SPI_execute_plan(&plan, &options,
+ InvalidSnapshot, InvalidSnapshot,
+ true);
+
+ _SPI_end_call(true);
+ return res;
+}
+
+SPIPlanPtr
+SPI_prepare(const char *src, int nargs, Oid *argtypes)
+{
+ return SPI_prepare_cursor(src, nargs, argtypes, 0);
+}
+
+SPIPlanPtr
+SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
+ int cursorOptions)
+{
+ _SPI_plan plan;
+ SPIPlanPtr result;
+
+ if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL))
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ SPI_result = _SPI_begin_call(true);
+ if (SPI_result < 0)
+ return NULL;
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = cursorOptions;
+ plan.nargs = nargs;
+ plan.argtypes = argtypes;
+ plan.parserSetup = NULL;
+ plan.parserSetupArg = NULL;
+
+ _SPI_prepare_plan(src, &plan);
+
+ /* copy plan to procedure context */
+ result = _SPI_make_plan_non_temp(&plan);
+
+ _SPI_end_call(true);
+
+ return result;
+}
+
+SPIPlanPtr
+SPI_prepare_extended(const char *src,
+ const SPIPrepareOptions *options)
+{
+ _SPI_plan plan;
+ SPIPlanPtr result;
+
+ if (src == NULL || options == NULL)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ SPI_result = _SPI_begin_call(true);
+ if (SPI_result < 0)
+ return NULL;
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = options->parseMode;
+ plan.cursor_options = options->cursorOptions;
+ plan.nargs = 0;
+ plan.argtypes = NULL;
+ plan.parserSetup = options->parserSetup;
+ plan.parserSetupArg = options->parserSetupArg;
+
+ _SPI_prepare_plan(src, &plan);
+
+ /* copy plan to procedure context */
+ result = _SPI_make_plan_non_temp(&plan);
+
+ _SPI_end_call(true);
+
+ return result;
+}
+
+SPIPlanPtr
+SPI_prepare_params(const char *src,
+ ParserSetupHook parserSetup,
+ void *parserSetupArg,
+ int cursorOptions)
+{
+ _SPI_plan plan;
+ SPIPlanPtr result;
+
+ if (src == NULL)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ SPI_result = _SPI_begin_call(true);
+ if (SPI_result < 0)
+ return NULL;
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = cursorOptions;
+ plan.nargs = 0;
+ plan.argtypes = NULL;
+ plan.parserSetup = parserSetup;
+ plan.parserSetupArg = parserSetupArg;
+
+ _SPI_prepare_plan(src, &plan);
+
+ /* copy plan to procedure context */
+ result = _SPI_make_plan_non_temp(&plan);
+
+ _SPI_end_call(true);
+
+ return result;
+}
+
+int
+SPI_keepplan(SPIPlanPtr plan)
+{
+ ListCell *lc;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC ||
+ plan->saved || plan->oneshot)
+ return SPI_ERROR_ARGUMENT;
+
+ /*
+ * Mark it saved, reparent it under CacheMemoryContext, and mark all the
+ * component CachedPlanSources as saved. This sequence cannot fail
+ * partway through, so there's no risk of long-term memory leakage.
+ */
+ plan->saved = true;
+ MemoryContextSetParent(plan->plancxt, CacheMemoryContext);
+
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ SaveCachedPlan(plansource);
+ }
+
+ return 0;
+}
+
+SPIPlanPtr
+SPI_saveplan(SPIPlanPtr plan)
+{
+ SPIPlanPtr newplan;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ SPI_result = _SPI_begin_call(false); /* don't change context */
+ if (SPI_result < 0)
+ return NULL;
+
+ newplan = _SPI_save_plan(plan);
+
+ SPI_result = _SPI_end_call(false);
+
+ return newplan;
+}
+
+int
+SPI_freeplan(SPIPlanPtr plan)
+{
+ ListCell *lc;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+ return SPI_ERROR_ARGUMENT;
+
+ /* Release the plancache entries */
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ DropCachedPlan(plansource);
+ }
+
+ /* Now get rid of the _SPI_plan and subsidiary data in its plancxt */
+ MemoryContextDelete(plan->plancxt);
+
+ return 0;
+}
+
+HeapTuple
+SPI_copytuple(HeapTuple tuple)
+{
+ MemoryContext oldcxt;
+ HeapTuple ctuple;
+
+ if (tuple == NULL)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ if (_SPI_current == NULL)
+ {
+ SPI_result = SPI_ERROR_UNCONNECTED;
+ return NULL;
+ }
+
+ oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
+
+ ctuple = heap_copytuple(tuple);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return ctuple;
+}
+
+HeapTupleHeader
+SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
+{
+ MemoryContext oldcxt;
+ HeapTupleHeader dtup;
+
+ if (tuple == NULL || tupdesc == NULL)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ if (_SPI_current == NULL)
+ {
+ SPI_result = SPI_ERROR_UNCONNECTED;
+ return NULL;
+ }
+
+ /* For RECORD results, make sure a typmod has been assigned */
+ if (tupdesc->tdtypeid == RECORDOID &&
+ tupdesc->tdtypmod < 0)
+ assign_record_type_typmod(tupdesc);
+
+ oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
+
+ dtup = DatumGetHeapTupleHeader(heap_copy_tuple_as_datum(tuple, tupdesc));
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return dtup;
+}
+
+HeapTuple
+SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
+ Datum *Values, const char *Nulls)
+{
+ MemoryContext oldcxt;
+ HeapTuple mtuple;
+ int numberOfAttributes;
+ Datum *v;
+ bool *n;
+ int i;
+
+ if (rel == NULL || tuple == NULL || natts < 0 || attnum == NULL || Values == NULL)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return NULL;
+ }
+
+ if (_SPI_current == NULL)
+ {
+ SPI_result = SPI_ERROR_UNCONNECTED;
+ return NULL;
+ }
+
+ oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
+
+ SPI_result = 0;
+
+ numberOfAttributes = rel->rd_att->natts;
+ v = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
+ n = (bool *) palloc(numberOfAttributes * sizeof(bool));
+
+ /* fetch old values and nulls */
+ heap_deform_tuple(tuple, rel->rd_att, v, n);
+
+ /* replace values and nulls */
+ for (i = 0; i < natts; i++)
+ {
+ if (attnum[i] <= 0 || attnum[i] > numberOfAttributes)
+ break;
+ v[attnum[i] - 1] = Values[i];
+ n[attnum[i] - 1] = (Nulls && Nulls[i] == 'n');
+ }
+
+ if (i == natts) /* no errors in *attnum */
+ {
+ mtuple = heap_form_tuple(rel->rd_att, v, n);
+
+ /*
+ * 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;
+ mtuple->t_tableOid = tuple->t_tableOid;
+ }
+ else
+ {
+ mtuple = NULL;
+ SPI_result = SPI_ERROR_NOATTRIBUTE;
+ }
+
+ pfree(v);
+ pfree(n);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return mtuple;
+}
+
+int
+SPI_fnumber(TupleDesc tupdesc, const char *fname)
+{
+ int res;
+ const FormData_pg_attribute *sysatt;
+
+ for (res = 0; res < tupdesc->natts; res++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, res);
+
+ if (namestrcmp(&attr->attname, fname) == 0 &&
+ !attr->attisdropped)
+ return res + 1;
+ }
+
+ sysatt = SystemAttributeByName(fname);
+ if (sysatt != NULL)
+ return sysatt->attnum;
+
+ /* SPI_ERROR_NOATTRIBUTE is different from all sys column numbers */
+ return SPI_ERROR_NOATTRIBUTE;
+}
+
+char *
+SPI_fname(TupleDesc tupdesc, int fnumber)
+{
+ const FormData_pg_attribute *att;
+
+ SPI_result = 0;
+
+ if (fnumber > tupdesc->natts || fnumber == 0 ||
+ fnumber <= FirstLowInvalidHeapAttributeNumber)
+ {
+ SPI_result = SPI_ERROR_NOATTRIBUTE;
+ return NULL;
+ }
+
+ if (fnumber > 0)
+ att = TupleDescAttr(tupdesc, fnumber - 1);
+ else
+ att = SystemAttributeDefinition(fnumber);
+
+ return pstrdup(NameStr(att->attname));
+}
+
+char *
+SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
+{
+ Datum val;
+ bool isnull;
+ Oid typoid,
+ foutoid;
+ bool typisvarlena;
+
+ SPI_result = 0;
+
+ if (fnumber > tupdesc->natts || fnumber == 0 ||
+ fnumber <= FirstLowInvalidHeapAttributeNumber)
+ {
+ SPI_result = SPI_ERROR_NOATTRIBUTE;
+ return NULL;
+ }
+
+ val = heap_getattr(tuple, fnumber, tupdesc, &isnull);
+ if (isnull)
+ return NULL;
+
+ if (fnumber > 0)
+ typoid = TupleDescAttr(tupdesc, fnumber - 1)->atttypid;
+ else
+ typoid = (SystemAttributeDefinition(fnumber))->atttypid;
+
+ getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
+
+ return OidOutputFunctionCall(foutoid, val);
+}
+
+Datum
+SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull)
+{
+ SPI_result = 0;
+
+ if (fnumber > tupdesc->natts || fnumber == 0 ||
+ fnumber <= FirstLowInvalidHeapAttributeNumber)
+ {
+ SPI_result = SPI_ERROR_NOATTRIBUTE;
+ *isnull = true;
+ return (Datum) NULL;
+ }
+
+ return heap_getattr(tuple, fnumber, tupdesc, isnull);
+}
+
+char *
+SPI_gettype(TupleDesc tupdesc, int fnumber)
+{
+ Oid typoid;
+ HeapTuple typeTuple;
+ char *result;
+
+ SPI_result = 0;
+
+ if (fnumber > tupdesc->natts || fnumber == 0 ||
+ fnumber <= FirstLowInvalidHeapAttributeNumber)
+ {
+ SPI_result = SPI_ERROR_NOATTRIBUTE;
+ return NULL;
+ }
+
+ if (fnumber > 0)
+ typoid = TupleDescAttr(tupdesc, fnumber - 1)->atttypid;
+ else
+ typoid = (SystemAttributeDefinition(fnumber))->atttypid;
+
+ typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typoid));
+
+ if (!HeapTupleIsValid(typeTuple))
+ {
+ SPI_result = SPI_ERROR_TYPUNKNOWN;
+ return NULL;
+ }
+
+ result = pstrdup(NameStr(((Form_pg_type) GETSTRUCT(typeTuple))->typname));
+ ReleaseSysCache(typeTuple);
+ return result;
+}
+
+/*
+ * Get the data type OID for a column.
+ *
+ * There's nothing similar for typmod and typcollation. The rare consumers
+ * thereof should inspect the TupleDesc directly.
+ */
+Oid
+SPI_gettypeid(TupleDesc tupdesc, int fnumber)
+{
+ SPI_result = 0;
+
+ if (fnumber > tupdesc->natts || fnumber == 0 ||
+ fnumber <= FirstLowInvalidHeapAttributeNumber)
+ {
+ SPI_result = SPI_ERROR_NOATTRIBUTE;
+ return InvalidOid;
+ }
+
+ if (fnumber > 0)
+ return TupleDescAttr(tupdesc, fnumber - 1)->atttypid;
+ else
+ return (SystemAttributeDefinition(fnumber))->atttypid;
+}
+
+char *
+SPI_getrelname(Relation rel)
+{
+ return pstrdup(RelationGetRelationName(rel));
+}
+
+char *
+SPI_getnspname(Relation rel)
+{
+ return get_namespace_name(RelationGetNamespace(rel));
+}
+
+void *
+SPI_palloc(Size size)
+{
+ if (_SPI_current == NULL)
+ elog(ERROR, "SPI_palloc called while not connected to SPI");
+
+ return MemoryContextAlloc(_SPI_current->savedcxt, size);
+}
+
+void *
+SPI_repalloc(void *pointer, Size size)
+{
+ /* No longer need to worry which context chunk was in... */
+ return repalloc(pointer, size);
+}
+
+void
+SPI_pfree(void *pointer)
+{
+ /* No longer need to worry which context chunk was in... */
+ pfree(pointer);
+}
+
+Datum
+SPI_datumTransfer(Datum value, bool typByVal, int typLen)
+{
+ MemoryContext oldcxt;
+ Datum result;
+
+ if (_SPI_current == NULL)
+ elog(ERROR, "SPI_datumTransfer called while not connected to SPI");
+
+ oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
+
+ result = datumTransfer(value, typByVal, typLen);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+void
+SPI_freetuple(HeapTuple tuple)
+{
+ /* No longer need to worry which context tuple was in... */
+ heap_freetuple(tuple);
+}
+
+void
+SPI_freetuptable(SPITupleTable *tuptable)
+{
+ bool found = false;
+
+ /* ignore call if NULL pointer */
+ if (tuptable == NULL)
+ return;
+
+ /*
+ * Search only the topmost SPI context for a matching tuple table.
+ */
+ if (_SPI_current != NULL)
+ {
+ slist_mutable_iter siter;
+
+ /* find tuptable in active list, then remove it */
+ slist_foreach_modify(siter, &_SPI_current->tuptables)
+ {
+ SPITupleTable *tt;
+
+ tt = slist_container(SPITupleTable, next, siter.cur);
+ if (tt == tuptable)
+ {
+ slist_delete_current(&siter);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Refuse the deletion if we didn't find it in the topmost SPI context.
+ * This is primarily a guard against double deletion, but might prevent
+ * other errors as well. Since the worst consequence of not deleting a
+ * tuptable would be a transient memory leak, this is just a WARNING.
+ */
+ if (!found)
+ {
+ elog(WARNING, "attempt to delete invalid SPITupleTable %p", tuptable);
+ return;
+ }
+
+ /* for safety, reset global variables that might point at tuptable */
+ if (tuptable == _SPI_current->tuptable)
+ _SPI_current->tuptable = NULL;
+ if (tuptable == SPI_tuptable)
+ SPI_tuptable = NULL;
+
+ /* release all memory belonging to tuptable */
+ MemoryContextDelete(tuptable->tuptabcxt);
+}
+
+
+/*
+ * SPI_cursor_open()
+ *
+ * Open a prepared SPI plan as a portal
+ */
+Portal
+SPI_cursor_open(const char *name, SPIPlanPtr plan,
+ Datum *Values, const char *Nulls,
+ bool read_only)
+{
+ Portal portal;
+ ParamListInfo paramLI;
+
+ /* build transient ParamListInfo in caller's context */
+ paramLI = _SPI_convert_params(plan->nargs, plan->argtypes,
+ Values, Nulls);
+
+ portal = SPI_cursor_open_internal(name, plan, paramLI, read_only);
+
+ /* done with the transient ParamListInfo */
+ if (paramLI)
+ pfree(paramLI);
+
+ return portal;
+}
+
+
+/*
+ * SPI_cursor_open_with_args()
+ *
+ * Parse and plan a query and open it as a portal.
+ */
+Portal
+SPI_cursor_open_with_args(const char *name,
+ const char *src,
+ int nargs, Oid *argtypes,
+ Datum *Values, const char *Nulls,
+ bool read_only, int cursorOptions)
+{
+ Portal result;
+ _SPI_plan plan;
+ ParamListInfo paramLI;
+
+ if (src == NULL || nargs < 0)
+ elog(ERROR, "SPI_cursor_open_with_args called with invalid arguments");
+
+ if (nargs > 0 && (argtypes == NULL || Values == NULL))
+ elog(ERROR, "SPI_cursor_open_with_args called with missing parameters");
+
+ SPI_result = _SPI_begin_call(true);
+ if (SPI_result < 0)
+ elog(ERROR, "SPI_cursor_open_with_args called while not connected");
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = cursorOptions;
+ plan.nargs = nargs;
+ plan.argtypes = argtypes;
+ plan.parserSetup = NULL;
+ plan.parserSetupArg = NULL;
+
+ /* build transient ParamListInfo in executor context */
+ paramLI = _SPI_convert_params(nargs, argtypes,
+ Values, Nulls);
+
+ _SPI_prepare_plan(src, &plan);
+
+ /* We needn't copy the plan; SPI_cursor_open_internal will do so */
+
+ result = SPI_cursor_open_internal(name, &plan, paramLI, read_only);
+
+ /* And clean up */
+ _SPI_end_call(true);
+
+ return result;
+}
+
+
+/*
+ * SPI_cursor_open_with_paramlist()
+ *
+ * Same as SPI_cursor_open except that parameters (if any) are passed
+ * as a ParamListInfo, which supports dynamic parameter set determination
+ */
+Portal
+SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan,
+ ParamListInfo params, bool read_only)
+{
+ return SPI_cursor_open_internal(name, plan, params, read_only);
+}
+
+/* Parse a query and open it as a cursor */
+Portal
+SPI_cursor_parse_open(const char *name,
+ const char *src,
+ const SPIParseOpenOptions *options)
+{
+ Portal result;
+ _SPI_plan plan;
+
+ if (src == NULL || options == NULL)
+ elog(ERROR, "SPI_cursor_parse_open called with invalid arguments");
+
+ SPI_result = _SPI_begin_call(true);
+ if (SPI_result < 0)
+ elog(ERROR, "SPI_cursor_parse_open called while not connected");
+
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
+ plan.parse_mode = RAW_PARSE_DEFAULT;
+ plan.cursor_options = options->cursorOptions;
+ if (options->params)
+ {
+ plan.parserSetup = options->params->parserSetup;
+ plan.parserSetupArg = options->params->parserSetupArg;
+ }
+
+ _SPI_prepare_plan(src, &plan);
+
+ /* We needn't copy the plan; SPI_cursor_open_internal will do so */
+
+ result = SPI_cursor_open_internal(name, &plan,
+ options->params, options->read_only);
+
+ /* And clean up */
+ _SPI_end_call(true);
+
+ return result;
+}
+
+
+/*
+ * SPI_cursor_open_internal()
+ *
+ * Common code for SPI_cursor_open variants
+ */
+static Portal
+SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
+ ParamListInfo paramLI, bool read_only)
+{
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
+ List *stmt_list;
+ char *query_string;
+ Snapshot snapshot;
+ MemoryContext oldcontext;
+ Portal portal;
+ SPICallbackArg spicallbackarg;
+ ErrorContextCallback spierrcontext;
+
+ /*
+ * Check that the plan is something the Portal code will special-case as
+ * returning one tupleset.
+ */
+ if (!SPI_is_cursor_plan(plan))
+ {
+ /* try to give a good error message */
+ const char *cmdtag;
+
+ if (list_length(plan->plancache_list) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cannot open multi-query plan as cursor")));
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+ /* A SELECT that fails SPI_is_cursor_plan() must be SELECT INTO */
+ if (plansource->commandTag == CMDTAG_SELECT)
+ cmdtag = "SELECT INTO";
+ else
+ cmdtag = GetCommandTagName(plansource->commandTag);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ /* translator: %s is name of a SQL command, eg INSERT */
+ errmsg("cannot open %s query as cursor", cmdtag)));
+ }
+
+ Assert(list_length(plan->plancache_list) == 1);
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+
+ /* Push the SPI stack */
+ if (_SPI_begin_call(true) < 0)
+ elog(ERROR, "SPI_cursor_open called while not connected");
+
+ /* Reset SPI result (note we deliberately don't touch lastoid) */
+ SPI_processed = 0;
+ SPI_tuptable = NULL;
+ _SPI_current->processed = 0;
+ _SPI_current->tuptable = NULL;
+
+ /* Create the portal */
+ if (name == NULL || name[0] == '\0')
+ {
+ /* Use a random nonconflicting name */
+ portal = CreateNewPortal();
+ }
+ else
+ {
+ /* In this path, error if portal of same name already exists */
+ portal = CreatePortal(name, false, false);
+ }
+
+ /* Copy the plan's query string into the portal */
+ query_string = MemoryContextStrdup(portal->portalContext,
+ plansource->query_string);
+
+ /*
+ * Setup error traceback support for ereport(), in case GetCachedPlan
+ * throws an error.
+ */
+ spicallbackarg.query = plansource->query_string;
+ spicallbackarg.mode = plan->parse_mode;
+ spierrcontext.callback = _SPI_error_callback;
+ spierrcontext.arg = &spicallbackarg;
+ spierrcontext.previous = error_context_stack;
+ error_context_stack = &spierrcontext;
+
+ /*
+ * Note: for a saved plan, we mustn't have any failure occur between
+ * GetCachedPlan and PortalDefineQuery; that would result in leaking our
+ * plancache refcount.
+ */
+
+ /* Replan if needed, and increment plan refcount for portal */
+ cplan = GetCachedPlan(plansource, paramLI, NULL, _SPI_current->queryEnv);
+ stmt_list = cplan->stmt_list;
+
+ if (!plan->saved)
+ {
+ /*
+ * We don't want the portal to depend on an unsaved CachedPlanSource,
+ * so must copy the plan into the portal's context. An error here
+ * will result in leaking our refcount on the plan, but it doesn't
+ * matter because the plan is unsaved and hence transient anyway.
+ */
+ oldcontext = MemoryContextSwitchTo(portal->portalContext);
+ stmt_list = copyObject(stmt_list);
+ MemoryContextSwitchTo(oldcontext);
+ ReleaseCachedPlan(cplan, NULL);
+ cplan = NULL; /* portal shouldn't depend on cplan */
+ }
+
+ /*
+ * Set up the portal.
+ */
+ PortalDefineQuery(portal,
+ NULL, /* no statement name */
+ query_string,
+ plansource->commandTag,
+ stmt_list,
+ cplan);
+
+ /*
+ * Set up options for portal. Default SCROLL type is chosen the same way
+ * as PerformCursorOpen does it.
+ */
+ portal->cursorOptions = plan->cursor_options;
+ if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
+ {
+ if (list_length(stmt_list) == 1 &&
+ linitial_node(PlannedStmt, stmt_list)->commandType != CMD_UTILITY &&
+ linitial_node(PlannedStmt, stmt_list)->rowMarks == NIL &&
+ ExecSupportsBackwardScan(linitial_node(PlannedStmt, stmt_list)->planTree))
+ portal->cursorOptions |= CURSOR_OPT_SCROLL;
+ else
+ portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
+ }
+
+ /*
+ * Disallow SCROLL with SELECT FOR UPDATE. This is not redundant with the
+ * check in transformDeclareCursorStmt because the cursor options might
+ * not have come through there.
+ */
+ if (portal->cursorOptions & CURSOR_OPT_SCROLL)
+ {
+ if (list_length(stmt_list) == 1 &&
+ linitial_node(PlannedStmt, stmt_list)->commandType != CMD_UTILITY &&
+ linitial_node(PlannedStmt, stmt_list)->rowMarks != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported"),
+ errdetail("Scrollable cursors must be READ ONLY.")));
+ }
+
+ /* Make current query environment available to portal at execution time. */
+ portal->queryEnv = _SPI_current->queryEnv;
+
+ /*
+ * If told to be read-only, we'd better check for read-only queries. This
+ * can't be done earlier because we need to look at the finished, planned
+ * queries. (In particular, we don't want to do it between GetCachedPlan
+ * and PortalDefineQuery, because throwing an error between those steps
+ * would result in leaking our plancache refcount.)
+ */
+ if (read_only)
+ {
+ ListCell *lc;
+
+ foreach(lc, stmt_list)
+ {
+ PlannedStmt *pstmt = lfirst_node(PlannedStmt, lc);
+
+ if (!CommandIsReadOnly(pstmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a non-volatile function",
+ CreateCommandName((Node *) pstmt))));
+ }
+ }
+
+ /* Set up the snapshot to use. */
+ if (read_only)
+ snapshot = GetActiveSnapshot();
+ else
+ {
+ CommandCounterIncrement();
+ snapshot = GetTransactionSnapshot();
+ }
+
+ /*
+ * If the plan has parameters, copy them into the portal. Note that this
+ * must be done after revalidating the plan, because in dynamic parameter
+ * cases the set of parameters could have changed during re-parsing.
+ */
+ if (paramLI)
+ {
+ oldcontext = MemoryContextSwitchTo(portal->portalContext);
+ paramLI = copyParamList(paramLI);
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * Start portal execution.
+ */
+ PortalStart(portal, paramLI, 0, snapshot);
+
+ Assert(portal->strategy != PORTAL_MULTI_QUERY);
+
+ /* Pop the error context stack */
+ error_context_stack = spierrcontext.previous;
+
+ /* Pop the SPI stack */
+ _SPI_end_call(true);
+
+ /* Return the created portal */
+ return portal;
+}
+
+
+/*
+ * SPI_cursor_find()
+ *
+ * Find the portal of an existing open cursor
+ */
+Portal
+SPI_cursor_find(const char *name)
+{
+ return GetPortalByName(name);
+}
+
+
+/*
+ * SPI_cursor_fetch()
+ *
+ * Fetch rows in a cursor
+ */
+void
+SPI_cursor_fetch(Portal portal, bool forward, long count)
+{
+ _SPI_cursor_operation(portal,
+ forward ? FETCH_FORWARD : FETCH_BACKWARD, count,
+ CreateDestReceiver(DestSPI));
+ /* we know that the DestSPI receiver doesn't need a destroy call */
+}
+
+
+/*
+ * SPI_cursor_move()
+ *
+ * Move in a cursor
+ */
+void
+SPI_cursor_move(Portal portal, bool forward, long count)
+{
+ _SPI_cursor_operation(portal,
+ forward ? FETCH_FORWARD : FETCH_BACKWARD, count,
+ None_Receiver);
+}
+
+
+/*
+ * SPI_scroll_cursor_fetch()
+ *
+ * Fetch rows in a scrollable cursor
+ */
+void
+SPI_scroll_cursor_fetch(Portal portal, FetchDirection direction, long count)
+{
+ _SPI_cursor_operation(portal,
+ direction, count,
+ CreateDestReceiver(DestSPI));
+ /* we know that the DestSPI receiver doesn't need a destroy call */
+}
+
+
+/*
+ * SPI_scroll_cursor_move()
+ *
+ * Move in a scrollable cursor
+ */
+void
+SPI_scroll_cursor_move(Portal portal, FetchDirection direction, long count)
+{
+ _SPI_cursor_operation(portal, direction, count, None_Receiver);
+}
+
+
+/*
+ * SPI_cursor_close()
+ *
+ * Close a cursor
+ */
+void
+SPI_cursor_close(Portal portal)
+{
+ if (!PortalIsValid(portal))
+ elog(ERROR, "invalid portal in SPI cursor operation");
+
+ PortalDrop(portal, false);
+}
+
+/*
+ * Returns the Oid representing the type id for argument at argIndex. First
+ * parameter is at index zero.
+ */
+Oid
+SPI_getargtypeid(SPIPlanPtr plan, int argIndex)
+{
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC ||
+ argIndex < 0 || argIndex >= plan->nargs)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return InvalidOid;
+ }
+ return plan->argtypes[argIndex];
+}
+
+/*
+ * Returns the number of arguments for the prepared plan.
+ */
+int
+SPI_getargcount(SPIPlanPtr plan)
+{
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return -1;
+ }
+ return plan->nargs;
+}
+
+/*
+ * Returns true if the plan contains exactly one command
+ * and that command returns tuples to the caller (eg, SELECT or
+ * INSERT ... RETURNING, but not SELECT ... INTO). In essence,
+ * the result indicates if the command can be used with SPI_cursor_open
+ *
+ * Parameters
+ * plan: A plan previously prepared using SPI_prepare
+ */
+bool
+SPI_is_cursor_plan(SPIPlanPtr plan)
+{
+ CachedPlanSource *plansource;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+ {
+ SPI_result = SPI_ERROR_ARGUMENT;
+ return false;
+ }
+
+ if (list_length(plan->plancache_list) != 1)
+ {
+ SPI_result = 0;
+ return false; /* not exactly 1 pre-rewrite command */
+ }
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+
+ /*
+ * We used to force revalidation of the cached plan here, but that seems
+ * unnecessary: invalidation could mean a change in the rowtype of the
+ * tuples returned by a plan, but not whether it returns tuples at all.
+ */
+ SPI_result = 0;
+
+ /* Does it return tuples? */
+ if (plansource->resultDesc)
+ return true;
+
+ return false;
+}
+
+/*
+ * SPI_plan_is_valid --- test whether a SPI plan is currently valid
+ * (that is, not marked as being in need of revalidation).
+ *
+ * See notes for CachedPlanIsValid before using this.
+ */
+bool
+SPI_plan_is_valid(SPIPlanPtr plan)
+{
+ ListCell *lc;
+
+ Assert(plan->magic == _SPI_PLAN_MAGIC);
+
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ if (!CachedPlanIsValid(plansource))
+ return false;
+ }
+ return true;
+}
+
+/*
+ * SPI_result_code_string --- convert any SPI return code to a string
+ *
+ * This is often useful in error messages. Most callers will probably
+ * only pass negative (error-case) codes, but for generality we recognize
+ * the success codes too.
+ */
+const char *
+SPI_result_code_string(int code)
+{
+ static char buf[64];
+
+ switch (code)
+ {
+ case SPI_ERROR_CONNECT:
+ return "SPI_ERROR_CONNECT";
+ case SPI_ERROR_COPY:
+ return "SPI_ERROR_COPY";
+ case SPI_ERROR_OPUNKNOWN:
+ return "SPI_ERROR_OPUNKNOWN";
+ case SPI_ERROR_UNCONNECTED:
+ return "SPI_ERROR_UNCONNECTED";
+ case SPI_ERROR_ARGUMENT:
+ return "SPI_ERROR_ARGUMENT";
+ case SPI_ERROR_PARAM:
+ return "SPI_ERROR_PARAM";
+ case SPI_ERROR_TRANSACTION:
+ return "SPI_ERROR_TRANSACTION";
+ case SPI_ERROR_NOATTRIBUTE:
+ return "SPI_ERROR_NOATTRIBUTE";
+ case SPI_ERROR_NOOUTFUNC:
+ return "SPI_ERROR_NOOUTFUNC";
+ case SPI_ERROR_TYPUNKNOWN:
+ return "SPI_ERROR_TYPUNKNOWN";
+ case SPI_ERROR_REL_DUPLICATE:
+ return "SPI_ERROR_REL_DUPLICATE";
+ case SPI_ERROR_REL_NOT_FOUND:
+ return "SPI_ERROR_REL_NOT_FOUND";
+ case SPI_OK_CONNECT:
+ return "SPI_OK_CONNECT";
+ case SPI_OK_FINISH:
+ return "SPI_OK_FINISH";
+ case SPI_OK_FETCH:
+ return "SPI_OK_FETCH";
+ case SPI_OK_UTILITY:
+ return "SPI_OK_UTILITY";
+ case SPI_OK_SELECT:
+ return "SPI_OK_SELECT";
+ case SPI_OK_SELINTO:
+ return "SPI_OK_SELINTO";
+ case SPI_OK_INSERT:
+ return "SPI_OK_INSERT";
+ case SPI_OK_DELETE:
+ return "SPI_OK_DELETE";
+ case SPI_OK_UPDATE:
+ return "SPI_OK_UPDATE";
+ case SPI_OK_CURSOR:
+ return "SPI_OK_CURSOR";
+ case SPI_OK_INSERT_RETURNING:
+ return "SPI_OK_INSERT_RETURNING";
+ case SPI_OK_DELETE_RETURNING:
+ return "SPI_OK_DELETE_RETURNING";
+ case SPI_OK_UPDATE_RETURNING:
+ return "SPI_OK_UPDATE_RETURNING";
+ case SPI_OK_REWRITTEN:
+ return "SPI_OK_REWRITTEN";
+ case SPI_OK_REL_REGISTER:
+ return "SPI_OK_REL_REGISTER";
+ case SPI_OK_REL_UNREGISTER:
+ return "SPI_OK_REL_UNREGISTER";
+ case SPI_OK_TD_REGISTER:
+ return "SPI_OK_TD_REGISTER";
+ case SPI_OK_MERGE:
+ return "SPI_OK_MERGE";
+ }
+ /* Unrecognized code ... return something useful ... */
+ sprintf(buf, "Unrecognized SPI code %d", code);
+ return buf;
+}
+
+/*
+ * SPI_plan_get_plan_sources --- get a SPI plan's underlying list of
+ * CachedPlanSources.
+ *
+ * This is exported so that PL/pgSQL can use it (this beats letting PL/pgSQL
+ * look directly into the SPIPlan for itself). It's not documented in
+ * spi.sgml because we'd just as soon not have too many places using this.
+ */
+List *
+SPI_plan_get_plan_sources(SPIPlanPtr plan)
+{
+ Assert(plan->magic == _SPI_PLAN_MAGIC);
+ return plan->plancache_list;
+}
+
+/*
+ * SPI_plan_get_cached_plan --- get a SPI plan's generic CachedPlan,
+ * if the SPI plan contains exactly one CachedPlanSource. If not,
+ * return NULL.
+ *
+ * The plan's refcount is incremented (and logged in CurrentResourceOwner,
+ * if it's a saved plan). Caller is responsible for doing ReleaseCachedPlan.
+ *
+ * This is exported so that PL/pgSQL can use it (this beats letting PL/pgSQL
+ * look directly into the SPIPlan for itself). It's not documented in
+ * spi.sgml because we'd just as soon not have too many places using this.
+ */
+CachedPlan *
+SPI_plan_get_cached_plan(SPIPlanPtr plan)
+{
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
+ SPICallbackArg spicallbackarg;
+ ErrorContextCallback spierrcontext;
+
+ Assert(plan->magic == _SPI_PLAN_MAGIC);
+
+ /* Can't support one-shot plans here */
+ if (plan->oneshot)
+ return NULL;
+
+ /* Must have exactly one CachedPlanSource */
+ if (list_length(plan->plancache_list) != 1)
+ return NULL;
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+
+ /* Setup error traceback support for ereport() */
+ spicallbackarg.query = plansource->query_string;
+ spicallbackarg.mode = plan->parse_mode;
+ spierrcontext.callback = _SPI_error_callback;
+ spierrcontext.arg = &spicallbackarg;
+ spierrcontext.previous = error_context_stack;
+ error_context_stack = &spierrcontext;
+
+ /* Get the generic plan for the query */
+ cplan = GetCachedPlan(plansource, NULL,
+ plan->saved ? CurrentResourceOwner : NULL,
+ _SPI_current->queryEnv);
+ Assert(cplan == plansource->gplan);
+
+ /* Pop the error context stack */
+ error_context_stack = spierrcontext.previous;
+
+ return cplan;
+}
+
+
+/* =================== private functions =================== */
+
+/*
+ * spi_dest_startup
+ * Initialize to receive tuples from Executor into SPITupleTable
+ * of current SPI procedure
+ */
+void
+spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ SPITupleTable *tuptable;
+ MemoryContext oldcxt;
+ MemoryContext tuptabcxt;
+
+ if (_SPI_current == NULL)
+ elog(ERROR, "spi_dest_startup called while not connected to SPI");
+
+ if (_SPI_current->tuptable != NULL)
+ elog(ERROR, "improper call to spi_dest_startup");
+
+ /* We create the tuple table context as a child of procCxt */
+
+ oldcxt = _SPI_procmem(); /* switch to procedure memory context */
+
+ tuptabcxt = AllocSetContextCreate(CurrentMemoryContext,
+ "SPI TupTable",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(tuptabcxt);
+
+ _SPI_current->tuptable = tuptable = (SPITupleTable *)
+ palloc0(sizeof(SPITupleTable));
+ tuptable->tuptabcxt = tuptabcxt;
+ tuptable->subid = GetCurrentSubTransactionId();
+
+ /*
+ * The tuptable is now valid enough to be freed by AtEOSubXact_SPI, so put
+ * it onto the SPI context's tuptables list. This will ensure it's not
+ * leaked even in the unlikely event the following few lines fail.
+ */
+ slist_push_head(&_SPI_current->tuptables, &tuptable->next);
+
+ /* set up initial allocations */
+ tuptable->alloced = 128;
+ tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
+ tuptable->numvals = 0;
+ tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * spi_printtup
+ * store tuple retrieved by Executor into SPITupleTable
+ * of current SPI procedure
+ */
+bool
+spi_printtup(TupleTableSlot *slot, DestReceiver *self)
+{
+ SPITupleTable *tuptable;
+ MemoryContext oldcxt;
+
+ if (_SPI_current == NULL)
+ elog(ERROR, "spi_printtup called while not connected to SPI");
+
+ tuptable = _SPI_current->tuptable;
+ if (tuptable == NULL)
+ elog(ERROR, "improper call to spi_printtup");
+
+ oldcxt = MemoryContextSwitchTo(tuptable->tuptabcxt);
+
+ if (tuptable->numvals >= tuptable->alloced)
+ {
+ /* Double the size of the pointer array */
+ uint64 newalloced = tuptable->alloced * 2;
+
+ tuptable->vals = (HeapTuple *) repalloc_huge(tuptable->vals,
+ newalloced * sizeof(HeapTuple));
+ tuptable->alloced = newalloced;
+ }
+
+ tuptable->vals[tuptable->numvals] = ExecCopySlotHeapTuple(slot);
+ (tuptable->numvals)++;
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return true;
+}
+
+/*
+ * Static functions
+ */
+
+/*
+ * Parse and analyze a querystring.
+ *
+ * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
+ * and plan->parserSetupArg) must be valid, as must plan->parse_mode and
+ * plan->cursor_options.
+ *
+ * Results are stored into *plan (specifically, plan->plancache_list).
+ * Note that the result data is all in CurrentMemoryContext or child contexts
+ * thereof; in practice this means it is in the SPI executor context, and
+ * what we are creating is a "temporary" SPIPlan. Cruft generated during
+ * parsing is also left in CurrentMemoryContext.
+ */
+static void
+_SPI_prepare_plan(const char *src, SPIPlanPtr plan)
+{
+ List *raw_parsetree_list;
+ List *plancache_list;
+ ListCell *list_item;
+ SPICallbackArg spicallbackarg;
+ ErrorContextCallback spierrcontext;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ spicallbackarg.query = src;
+ spicallbackarg.mode = plan->parse_mode;
+ spierrcontext.callback = _SPI_error_callback;
+ spierrcontext.arg = &spicallbackarg;
+ spierrcontext.previous = error_context_stack;
+ error_context_stack = &spierrcontext;
+
+ /*
+ * Parse the request string into a list of raw parse trees.
+ */
+ raw_parsetree_list = raw_parser(src, plan->parse_mode);
+
+ /*
+ * Do parse analysis and rule rewrite for each raw parsetree, storing the
+ * results into unsaved plancache entries.
+ */
+ plancache_list = NIL;
+
+ foreach(list_item, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, list_item);
+ List *stmt_list;
+ CachedPlanSource *plansource;
+
+ /*
+ * Create the CachedPlanSource before we do parse analysis, since it
+ * needs to see the unmodified raw parse tree.
+ */
+ plansource = CreateCachedPlan(parsetree,
+ src,
+ CreateCommandTag(parsetree->stmt));
+
+ /*
+ * Parameter datatypes are driven by parserSetup hook if provided,
+ * otherwise we use the fixed parameter list.
+ */
+ if (plan->parserSetup != NULL)
+ {
+ Assert(plan->nargs == 0);
+ stmt_list = pg_analyze_and_rewrite_withcb(parsetree,
+ src,
+ plan->parserSetup,
+ plan->parserSetupArg,
+ _SPI_current->queryEnv);
+ }
+ else
+ {
+ stmt_list = pg_analyze_and_rewrite_fixedparams(parsetree,
+ src,
+ plan->argtypes,
+ plan->nargs,
+ _SPI_current->queryEnv);
+ }
+
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(plansource,
+ stmt_list,
+ NULL,
+ plan->argtypes,
+ plan->nargs,
+ plan->parserSetup,
+ plan->parserSetupArg,
+ plan->cursor_options,
+ false); /* not fixed result */
+
+ plancache_list = lappend(plancache_list, plansource);
+ }
+
+ plan->plancache_list = plancache_list;
+ plan->oneshot = false;
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = spierrcontext.previous;
+}
+
+/*
+ * Parse, but don't analyze, a querystring.
+ *
+ * This is a stripped-down version of _SPI_prepare_plan that only does the
+ * initial raw parsing. It creates "one shot" CachedPlanSources
+ * that still require parse analysis before execution is possible.
+ *
+ * The advantage of using the "one shot" form of CachedPlanSource is that
+ * we eliminate data copying and invalidation overhead. Postponing parse
+ * analysis also prevents issues if some of the raw parsetrees are DDL
+ * commands that affect validity of later parsetrees. Both of these
+ * attributes are good things for SPI_execute() and similar cases.
+ *
+ * Results are stored into *plan (specifically, plan->plancache_list).
+ * Note that the result data is all in CurrentMemoryContext or child contexts
+ * thereof; in practice this means it is in the SPI executor context, and
+ * what we are creating is a "temporary" SPIPlan. Cruft generated during
+ * parsing is also left in CurrentMemoryContext.
+ */
+static void
+_SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
+{
+ List *raw_parsetree_list;
+ List *plancache_list;
+ ListCell *list_item;
+ SPICallbackArg spicallbackarg;
+ ErrorContextCallback spierrcontext;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ spicallbackarg.query = src;
+ spicallbackarg.mode = plan->parse_mode;
+ spierrcontext.callback = _SPI_error_callback;
+ spierrcontext.arg = &spicallbackarg;
+ spierrcontext.previous = error_context_stack;
+ error_context_stack = &spierrcontext;
+
+ /*
+ * Parse the request string into a list of raw parse trees.
+ */
+ raw_parsetree_list = raw_parser(src, plan->parse_mode);
+
+ /*
+ * Construct plancache entries, but don't do parse analysis yet.
+ */
+ plancache_list = NIL;
+
+ foreach(list_item, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, list_item);
+ CachedPlanSource *plansource;
+
+ plansource = CreateOneShotCachedPlan(parsetree,
+ src,
+ CreateCommandTag(parsetree->stmt));
+
+ plancache_list = lappend(plancache_list, plansource);
+ }
+
+ plan->plancache_list = plancache_list;
+ plan->oneshot = true;
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = spierrcontext.previous;
+}
+
+/*
+ * _SPI_execute_plan: execute the given plan with the given options
+ *
+ * options contains options accessible from outside SPI:
+ * params: parameter values to pass to query
+ * read_only: true for read-only execution (no CommandCounterIncrement)
+ * allow_nonatomic: true to allow nonatomic CALL/DO execution
+ * must_return_tuples: throw error if query doesn't return tuples
+ * tcount: execution tuple-count limit, or 0 for none
+ * dest: DestReceiver to receive output, or NULL for normal SPI output
+ * owner: ResourceOwner that will be used to hold refcount on plan;
+ * if NULL, CurrentResourceOwner is used (ignored for non-saved plan)
+ *
+ * Additional, only-internally-accessible options:
+ * snapshot: query snapshot to use, or InvalidSnapshot for the normal
+ * behavior of taking a new snapshot for each query.
+ * crosscheck_snapshot: for RI use, all others pass InvalidSnapshot
+ * fire_triggers: true to fire AFTER triggers at end of query (normal case);
+ * false means any AFTER triggers are postponed to end of outer query
+ */
+static int
+_SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool fire_triggers)
+{
+ int my_res = 0;
+ uint64 my_processed = 0;
+ SPITupleTable *my_tuptable = NULL;
+ int res = 0;
+ bool pushed_active_snap = false;
+ ResourceOwner plan_owner = options->owner;
+ SPICallbackArg spicallbackarg;
+ ErrorContextCallback spierrcontext;
+ CachedPlan *cplan = NULL;
+ ListCell *lc1;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ spicallbackarg.query = NULL; /* we'll fill this below */
+ spicallbackarg.mode = plan->parse_mode;
+ spierrcontext.callback = _SPI_error_callback;
+ spierrcontext.arg = &spicallbackarg;
+ spierrcontext.previous = error_context_stack;
+ error_context_stack = &spierrcontext;
+
+ /*
+ * We support four distinct snapshot management behaviors:
+ *
+ * snapshot != InvalidSnapshot, read_only = true: use exactly the given
+ * snapshot.
+ *
+ * snapshot != InvalidSnapshot, read_only = false: use the given snapshot,
+ * modified by advancing its command ID before each querytree.
+ *
+ * snapshot == InvalidSnapshot, read_only = true: use the entry-time
+ * ActiveSnapshot, if any (if there isn't one, we run with no snapshot).
+ *
+ * snapshot == InvalidSnapshot, read_only = false: take a full new
+ * snapshot for each user command, and advance its command ID before each
+ * querytree within the command.
+ *
+ * In the first two cases, we can just push the snap onto the stack once
+ * for the whole plan list.
+ *
+ * Note that snapshot != InvalidSnapshot implies an atomic execution
+ * context.
+ */
+ if (snapshot != InvalidSnapshot)
+ {
+ Assert(!options->allow_nonatomic);
+ if (options->read_only)
+ {
+ PushActiveSnapshot(snapshot);
+ pushed_active_snap = true;
+ }
+ else
+ {
+ /* Make sure we have a private copy of the snapshot to modify */
+ PushCopiedSnapshot(snapshot);
+ pushed_active_snap = true;
+ }
+ }
+
+ /*
+ * Ensure that we have a resource owner if plan is saved, and not if it
+ * isn't.
+ */
+ if (!plan->saved)
+ plan_owner = NULL;
+ else if (plan_owner == NULL)
+ plan_owner = CurrentResourceOwner;
+
+ /*
+ * We interpret must_return_tuples as "there must be at least one query,
+ * and all of them must return tuples". This is a bit laxer than
+ * SPI_is_cursor_plan's check, but there seems no reason to enforce that
+ * there be only one query.
+ */
+ if (options->must_return_tuples && plan->plancache_list == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("empty query does not return tuples")));
+
+ foreach(lc1, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
+ List *stmt_list;
+ ListCell *lc2;
+
+ spicallbackarg.query = plansource->query_string;
+
+ /*
+ * If this is a one-shot plan, we still need to do parse analysis.
+ */
+ if (plan->oneshot)
+ {
+ RawStmt *parsetree = plansource->raw_parse_tree;
+ const char *src = plansource->query_string;
+ List *stmt_list;
+
+ /*
+ * Parameter datatypes are driven by parserSetup hook if provided,
+ * otherwise we use the fixed parameter list.
+ */
+ if (parsetree == NULL)
+ stmt_list = NIL;
+ else if (plan->parserSetup != NULL)
+ {
+ Assert(plan->nargs == 0);
+ stmt_list = pg_analyze_and_rewrite_withcb(parsetree,
+ src,
+ plan->parserSetup,
+ plan->parserSetupArg,
+ _SPI_current->queryEnv);
+ }
+ else
+ {
+ stmt_list = pg_analyze_and_rewrite_fixedparams(parsetree,
+ src,
+ plan->argtypes,
+ plan->nargs,
+ _SPI_current->queryEnv);
+ }
+
+ /* Finish filling in the CachedPlanSource */
+ CompleteCachedPlan(plansource,
+ stmt_list,
+ NULL,
+ plan->argtypes,
+ plan->nargs,
+ plan->parserSetup,
+ plan->parserSetupArg,
+ plan->cursor_options,
+ false); /* not fixed result */
+ }
+
+ /*
+ * If asked to, complain when query does not return tuples.
+ * (Replanning can't change this, so we can check it before that.
+ * However, we can't check it till after parse analysis, so in the
+ * case of a one-shot plan this is the earliest we could check.)
+ */
+ if (options->must_return_tuples && !plansource->resultDesc)
+ {
+ /* try to give a good error message */
+ const char *cmdtag;
+
+ /* A SELECT without resultDesc must be SELECT INTO */
+ if (plansource->commandTag == CMDTAG_SELECT)
+ cmdtag = "SELECT INTO";
+ else
+ cmdtag = GetCommandTagName(plansource->commandTag);
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: %s is name of a SQL command, eg INSERT */
+ errmsg("%s query does not return tuples", cmdtag)));
+ }
+
+ /*
+ * Replan if needed, and increment plan refcount. If it's a saved
+ * plan, the refcount must be backed by the plan_owner.
+ */
+ cplan = GetCachedPlan(plansource, options->params,
+ plan_owner, _SPI_current->queryEnv);
+
+ stmt_list = cplan->stmt_list;
+
+ /*
+ * If we weren't given a specific snapshot to use, and the statement
+ * list requires a snapshot, set that up.
+ */
+ if (snapshot == InvalidSnapshot &&
+ (list_length(stmt_list) > 1 ||
+ (list_length(stmt_list) == 1 &&
+ PlannedStmtRequiresSnapshot(linitial_node(PlannedStmt,
+ stmt_list)))))
+ {
+ /*
+ * First, ensure there's a Portal-level snapshot. This back-fills
+ * the snapshot stack in case the previous operation was a COMMIT
+ * or ROLLBACK inside a procedure or DO block. (We can't put back
+ * the Portal snapshot any sooner, or we'd break cases like doing
+ * SET or LOCK just after COMMIT.) It's enough to check once per
+ * statement list, since COMMIT/ROLLBACK/CALL/DO can't appear
+ * within a multi-statement list.
+ */
+ EnsurePortalSnapshotExists();
+
+ /*
+ * In the default non-read-only case, get a new per-statement-list
+ * snapshot, replacing any that we pushed in a previous cycle.
+ * Skip it when doing non-atomic execution, though (we rely
+ * entirely on the Portal snapshot in that case).
+ */
+ if (!options->read_only && !options->allow_nonatomic)
+ {
+ if (pushed_active_snap)
+ PopActiveSnapshot();
+ PushActiveSnapshot(GetTransactionSnapshot());
+ pushed_active_snap = true;
+ }
+ }
+
+ foreach(lc2, stmt_list)
+ {
+ PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
+ bool canSetTag = stmt->canSetTag;
+ DestReceiver *dest;
+
+ /*
+ * Reset output state. (Note that if a non-SPI receiver is used,
+ * _SPI_current->processed will stay zero, and that's what we'll
+ * report to the caller. It's the receiver's job to count tuples
+ * in that case.)
+ */
+ _SPI_current->processed = 0;
+ _SPI_current->tuptable = NULL;
+
+ /* Check for unsupported cases. */
+ if (stmt->utilityStmt)
+ {
+ if (IsA(stmt->utilityStmt, CopyStmt))
+ {
+ CopyStmt *cstmt = (CopyStmt *) stmt->utilityStmt;
+
+ if (cstmt->filename == NULL)
+ {
+ my_res = SPI_ERROR_COPY;
+ goto fail;
+ }
+ }
+ else if (IsA(stmt->utilityStmt, TransactionStmt))
+ {
+ my_res = SPI_ERROR_TRANSACTION;
+ goto fail;
+ }
+ }
+
+ if (options->read_only && !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",
+ CreateCommandName((Node *) stmt))));
+
+ /*
+ * If not read-only mode, advance the command counter before each
+ * command and update the snapshot. (But skip it if the snapshot
+ * isn't under our control.)
+ */
+ if (!options->read_only && pushed_active_snap)
+ {
+ CommandCounterIncrement();
+ UpdateActiveSnapshotCommandId();
+ }
+
+ /*
+ * Select appropriate tuple receiver. Output from non-canSetTag
+ * subqueries always goes to the bit bucket.
+ */
+ if (!canSetTag)
+ dest = CreateDestReceiver(DestNone);
+ else if (options->dest)
+ dest = options->dest;
+ else
+ dest = CreateDestReceiver(DestSPI);
+
+ if (stmt->utilityStmt == NULL)
+ {
+ QueryDesc *qdesc;
+ Snapshot snap;
+
+ if (ActiveSnapshotSet())
+ snap = GetActiveSnapshot();
+ else
+ snap = InvalidSnapshot;
+
+ qdesc = CreateQueryDesc(stmt,
+ plansource->query_string,
+ snap, crosscheck_snapshot,
+ dest,
+ options->params,
+ _SPI_current->queryEnv,
+ 0);
+ res = _SPI_pquery(qdesc, fire_triggers,
+ canSetTag ? options->tcount : 0);
+ FreeQueryDesc(qdesc);
+ }
+ else
+ {
+ ProcessUtilityContext context;
+ QueryCompletion qc;
+
+ /*
+ * If the SPI context is atomic, or we were not told to allow
+ * nonatomic operations, tell ProcessUtility this is an atomic
+ * execution context.
+ */
+ if (_SPI_current->atomic || !options->allow_nonatomic)
+ context = PROCESS_UTILITY_QUERY;
+ else
+ context = PROCESS_UTILITY_QUERY_NONATOMIC;
+
+ InitializeQueryCompletion(&qc);
+ ProcessUtility(stmt,
+ plansource->query_string,
+ true, /* protect plancache's node tree */
+ context,
+ options->params,
+ _SPI_current->queryEnv,
+ dest,
+ &qc);
+
+ /* Update "processed" if stmt returned tuples */
+ if (_SPI_current->tuptable)
+ _SPI_current->processed = _SPI_current->tuptable->numvals;
+
+ res = SPI_OK_UTILITY;
+
+ /*
+ * Some utility statements return a row count, even though the
+ * tuples are not returned to the caller.
+ */
+ if (IsA(stmt->utilityStmt, CreateTableAsStmt))
+ {
+ CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt->utilityStmt;
+
+ if (qc.commandTag == CMDTAG_SELECT)
+ _SPI_current->processed = qc.nprocessed;
+ else
+ {
+ /*
+ * Must be an IF NOT EXISTS that did nothing, or a
+ * CREATE ... WITH NO DATA.
+ */
+ Assert(ctastmt->if_not_exists ||
+ ctastmt->into->skipData);
+ _SPI_current->processed = 0;
+ }
+
+ /*
+ * For historical reasons, if CREATE TABLE AS was spelled
+ * as SELECT INTO, return a special return code.
+ */
+ if (ctastmt->is_select_into)
+ res = SPI_OK_SELINTO;
+ }
+ else if (IsA(stmt->utilityStmt, CopyStmt))
+ {
+ Assert(qc.commandTag == CMDTAG_COPY);
+ _SPI_current->processed = qc.nprocessed;
+ }
+ }
+
+ /*
+ * The last canSetTag query sets the status values returned to the
+ * caller. Be careful to free any tuptables not returned, to
+ * avoid intra-transaction memory leak.
+ */
+ if (canSetTag)
+ {
+ my_processed = _SPI_current->processed;
+ SPI_freetuptable(my_tuptable);
+ my_tuptable = _SPI_current->tuptable;
+ my_res = res;
+ }
+ else
+ {
+ SPI_freetuptable(_SPI_current->tuptable);
+ _SPI_current->tuptable = NULL;
+ }
+
+ /*
+ * We don't issue a destroy call to the receiver. The SPI and
+ * None receivers would ignore it anyway, while if the caller
+ * supplied a receiver, it's not our job to destroy it.
+ */
+
+ if (res < 0)
+ {
+ my_res = res;
+ goto fail;
+ }
+ }
+
+ /* Done with this plan, so release refcount */
+ ReleaseCachedPlan(cplan, plan_owner);
+ cplan = NULL;
+
+ /*
+ * If not read-only mode, advance the command counter after the last
+ * command. This ensures that its effects are visible, in case it was
+ * DDL that would affect the next CachedPlanSource.
+ */
+ if (!options->read_only)
+ CommandCounterIncrement();
+ }
+
+fail:
+
+ /* Pop the snapshot off the stack if we pushed one */
+ if (pushed_active_snap)
+ PopActiveSnapshot();
+
+ /* We no longer need the cached plan refcount, if any */
+ if (cplan)
+ ReleaseCachedPlan(cplan, plan_owner);
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = spierrcontext.previous;
+
+ /* Save results for caller */
+ SPI_processed = my_processed;
+ SPI_tuptable = my_tuptable;
+
+ /* tuptable now is caller's responsibility, not SPI's */
+ _SPI_current->tuptable = NULL;
+
+ /*
+ * If none of the queries had canSetTag, return SPI_OK_REWRITTEN. Prior to
+ * 8.4, we used return the last query's result code, but not its auxiliary
+ * results, but that's confusing.
+ */
+ if (my_res == 0)
+ my_res = SPI_OK_REWRITTEN;
+
+ return my_res;
+}
+
+/*
+ * Convert arrays of query parameters to form wanted by planner and executor
+ */
+static ParamListInfo
+_SPI_convert_params(int nargs, Oid *argtypes,
+ Datum *Values, const char *Nulls)
+{
+ ParamListInfo paramLI;
+
+ if (nargs > 0)
+ {
+ paramLI = makeParamList(nargs);
+
+ for (int i = 0; i < nargs; i++)
+ {
+ ParamExternData *prm = &paramLI->params[i];
+
+ prm->value = Values[i];
+ prm->isnull = (Nulls && Nulls[i] == 'n');
+ prm->pflags = PARAM_FLAG_CONST;
+ prm->ptype = argtypes[i];
+ }
+ }
+ else
+ paramLI = NULL;
+ return paramLI;
+}
+
+static int
+_SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
+{
+ int operation = queryDesc->operation;
+ int eflags;
+ int res;
+
+ switch (operation)
+ {
+ case CMD_SELECT:
+ if (queryDesc->dest->mydest == DestNone)
+ {
+ /* Don't return SPI_OK_SELECT if we're discarding result */
+ res = SPI_OK_UTILITY;
+ }
+ else
+ res = SPI_OK_SELECT;
+ break;
+ case CMD_INSERT:
+ if (queryDesc->plannedstmt->hasReturning)
+ res = SPI_OK_INSERT_RETURNING;
+ else
+ res = SPI_OK_INSERT;
+ break;
+ case CMD_DELETE:
+ if (queryDesc->plannedstmt->hasReturning)
+ res = SPI_OK_DELETE_RETURNING;
+ else
+ res = SPI_OK_DELETE;
+ break;
+ case CMD_UPDATE:
+ if (queryDesc->plannedstmt->hasReturning)
+ res = SPI_OK_UPDATE_RETURNING;
+ else
+ res = SPI_OK_UPDATE;
+ break;
+ case CMD_MERGE:
+ res = SPI_OK_MERGE;
+ break;
+ default:
+ return SPI_ERROR_OPUNKNOWN;
+ }
+
+#ifdef SPI_EXECUTOR_STATS
+ if (ShowExecutorStats)
+ ResetUsage();
+#endif
+
+ /* Select execution options */
+ if (fire_triggers)
+ eflags = 0; /* default run-to-completion flags */
+ else
+ eflags = EXEC_FLAG_SKIP_TRIGGERS;
+
+ ExecutorStart(queryDesc, eflags);
+
+ ExecutorRun(queryDesc, ForwardScanDirection, tcount, true);
+
+ _SPI_current->processed = queryDesc->estate->es_processed;
+
+ if ((res == SPI_OK_SELECT || queryDesc->plannedstmt->hasReturning) &&
+ queryDesc->dest->mydest == DestSPI)
+ {
+ if (_SPI_checktuples())
+ elog(ERROR, "consistency check on SPI tuple count failed");
+ }
+
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+ /* FreeQueryDesc is done by the caller */
+
+#ifdef SPI_EXECUTOR_STATS
+ if (ShowExecutorStats)
+ ShowUsage("SPI EXECUTOR STATS");
+#endif
+
+ return res;
+}
+
+/*
+ * _SPI_error_callback
+ *
+ * Add context information when a query invoked via SPI fails
+ */
+static void
+_SPI_error_callback(void *arg)
+{
+ SPICallbackArg *carg = (SPICallbackArg *) arg;
+ const char *query = carg->query;
+ int syntaxerrposition;
+
+ if (query == NULL) /* in case arg wasn't set yet */
+ return;
+
+ /*
+ * 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)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(query);
+ }
+ else
+ {
+ /* Use the parse mode to decide how to describe the query */
+ switch (carg->mode)
+ {
+ case RAW_PARSE_PLPGSQL_EXPR:
+ errcontext("SQL expression \"%s\"", query);
+ break;
+ case RAW_PARSE_PLPGSQL_ASSIGN1:
+ case RAW_PARSE_PLPGSQL_ASSIGN2:
+ case RAW_PARSE_PLPGSQL_ASSIGN3:
+ errcontext("PL/pgSQL assignment \"%s\"", query);
+ break;
+ default:
+ errcontext("SQL statement \"%s\"", query);
+ break;
+ }
+ }
+}
+
+/*
+ * _SPI_cursor_operation()
+ *
+ * Do a FETCH or MOVE in a cursor
+ */
+static void
+_SPI_cursor_operation(Portal portal, FetchDirection direction, long count,
+ DestReceiver *dest)
+{
+ uint64 nfetched;
+
+ /* Check that the portal is valid */
+ if (!PortalIsValid(portal))
+ elog(ERROR, "invalid portal in SPI cursor operation");
+
+ /* Push the SPI stack */
+ if (_SPI_begin_call(true) < 0)
+ elog(ERROR, "SPI cursor operation called while not connected");
+
+ /* Reset the SPI result (note we deliberately don't touch lastoid) */
+ SPI_processed = 0;
+ SPI_tuptable = NULL;
+ _SPI_current->processed = 0;
+ _SPI_current->tuptable = NULL;
+
+ /* Run the cursor */
+ nfetched = PortalRunFetch(portal,
+ direction,
+ 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.
+ */
+ _SPI_current->processed = nfetched;
+
+ if (dest->mydest == DestSPI && _SPI_checktuples())
+ elog(ERROR, "consistency check on SPI tuple count failed");
+
+ /* Put the result into place for access by caller */
+ SPI_processed = _SPI_current->processed;
+ SPI_tuptable = _SPI_current->tuptable;
+
+ /* tuptable now is caller's responsibility, not SPI's */
+ _SPI_current->tuptable = NULL;
+
+ /* Pop the SPI stack */
+ _SPI_end_call(true);
+}
+
+
+static MemoryContext
+_SPI_execmem(void)
+{
+ return MemoryContextSwitchTo(_SPI_current->execCxt);
+}
+
+static MemoryContext
+_SPI_procmem(void)
+{
+ return MemoryContextSwitchTo(_SPI_current->procCxt);
+}
+
+/*
+ * _SPI_begin_call: begin a SPI operation within a connected procedure
+ *
+ * use_exec is true if we intend to make use of the procedure's execCxt
+ * during this SPI operation. We'll switch into that context, and arrange
+ * for it to be cleaned up at _SPI_end_call or if an error occurs.
+ */
+static int
+_SPI_begin_call(bool use_exec)
+{
+ if (_SPI_current == NULL)
+ return SPI_ERROR_UNCONNECTED;
+
+ if (use_exec)
+ {
+ /* remember when the Executor operation started */
+ _SPI_current->execSubid = GetCurrentSubTransactionId();
+ /* switch to the Executor memory context */
+ _SPI_execmem();
+ }
+
+ return 0;
+}
+
+/*
+ * _SPI_end_call: end a SPI operation within a connected procedure
+ *
+ * use_exec must be the same as in the previous _SPI_begin_call
+ *
+ * Note: this currently has no failure return cases, so callers don't check
+ */
+static int
+_SPI_end_call(bool use_exec)
+{
+ if (use_exec)
+ {
+ /* switch to the procedure memory context */
+ _SPI_procmem();
+ /* mark Executor context no longer in use */
+ _SPI_current->execSubid = InvalidSubTransactionId;
+ /* and free Executor memory */
+ MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
+ }
+
+ return 0;
+}
+
+static bool
+_SPI_checktuples(void)
+{
+ uint64 processed = _SPI_current->processed;
+ SPITupleTable *tuptable = _SPI_current->tuptable;
+ bool failed = false;
+
+ if (tuptable == NULL) /* spi_dest_startup was not called */
+ failed = true;
+ else if (processed != tuptable->numvals)
+ failed = true;
+
+ return failed;
+}
+
+/*
+ * Convert a "temporary" SPIPlan into an "unsaved" plan.
+ *
+ * The passed _SPI_plan struct is on the stack, and all its subsidiary data
+ * is in or under the current SPI executor context. Copy the plan into the
+ * SPI procedure context so it will survive _SPI_end_call(). To minimize
+ * data copying, this destructively modifies the input plan, by taking the
+ * plancache entries away from it and reparenting them to the new SPIPlan.
+ */
+static SPIPlanPtr
+_SPI_make_plan_non_temp(SPIPlanPtr plan)
+{
+ SPIPlanPtr newplan;
+ MemoryContext parentcxt = _SPI_current->procCxt;
+ MemoryContext plancxt;
+ MemoryContext oldcxt;
+ ListCell *lc;
+
+ /* Assert the input is a temporary SPIPlan */
+ Assert(plan->magic == _SPI_PLAN_MAGIC);
+ Assert(plan->plancxt == NULL);
+ /* One-shot plans can't be saved */
+ Assert(!plan->oneshot);
+
+ /*
+ * Create a memory context for the plan, underneath the procedure context.
+ * We don't expect the plan to be very large.
+ */
+ plancxt = AllocSetContextCreate(parentcxt,
+ "SPI Plan",
+ ALLOCSET_SMALL_SIZES);
+ oldcxt = MemoryContextSwitchTo(plancxt);
+
+ /* Copy the _SPI_plan struct and subsidiary data into the new context */
+ newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
+ newplan->magic = _SPI_PLAN_MAGIC;
+ newplan->plancxt = plancxt;
+ newplan->parse_mode = plan->parse_mode;
+ newplan->cursor_options = plan->cursor_options;
+ newplan->nargs = plan->nargs;
+ if (plan->nargs > 0)
+ {
+ newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
+ memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
+ }
+ else
+ newplan->argtypes = NULL;
+ newplan->parserSetup = plan->parserSetup;
+ newplan->parserSetupArg = plan->parserSetupArg;
+
+ /*
+ * Reparent all the CachedPlanSources into the procedure context. In
+ * theory this could fail partway through due to the pallocs, but we don't
+ * care too much since both the procedure context and the executor context
+ * would go away on error.
+ */
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ CachedPlanSetParentContext(plansource, parentcxt);
+
+ /* Build new list, with list cells in plancxt */
+ newplan->plancache_list = lappend(newplan->plancache_list, plansource);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ /* For safety, unlink the CachedPlanSources from the temporary plan */
+ plan->plancache_list = NIL;
+
+ return newplan;
+}
+
+/*
+ * Make a "saved" copy of the given plan.
+ */
+static SPIPlanPtr
+_SPI_save_plan(SPIPlanPtr plan)
+{
+ SPIPlanPtr newplan;
+ MemoryContext plancxt;
+ MemoryContext oldcxt;
+ ListCell *lc;
+
+ /* One-shot plans can't be saved */
+ Assert(!plan->oneshot);
+
+ /*
+ * Create a memory context for the plan. We don't expect the plan to be
+ * very large, so use smaller-than-default alloc parameters. It's a
+ * transient context until we finish copying everything.
+ */
+ plancxt = AllocSetContextCreate(CurrentMemoryContext,
+ "SPI Plan",
+ ALLOCSET_SMALL_SIZES);
+ oldcxt = MemoryContextSwitchTo(plancxt);
+
+ /* Copy the SPI plan into its own context */
+ newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
+ newplan->magic = _SPI_PLAN_MAGIC;
+ newplan->plancxt = plancxt;
+ newplan->parse_mode = plan->parse_mode;
+ newplan->cursor_options = plan->cursor_options;
+ newplan->nargs = plan->nargs;
+ if (plan->nargs > 0)
+ {
+ newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
+ memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
+ }
+ else
+ newplan->argtypes = NULL;
+ newplan->parserSetup = plan->parserSetup;
+ newplan->parserSetupArg = plan->parserSetupArg;
+
+ /* Copy all the plancache entries */
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+ CachedPlanSource *newsource;
+
+ newsource = CopyCachedPlan(plansource);
+ newplan->plancache_list = lappend(newplan->plancache_list, newsource);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ /*
+ * Mark it saved, reparent it under CacheMemoryContext, and mark all the
+ * component CachedPlanSources as saved. This sequence cannot fail
+ * partway through, so there's no risk of long-term memory leakage.
+ */
+ newplan->saved = true;
+ MemoryContextSetParent(newplan->plancxt, CacheMemoryContext);
+
+ foreach(lc, newplan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ SaveCachedPlan(plansource);
+ }
+
+ return newplan;
+}
+
+/*
+ * Internal lookup of ephemeral named relation by name.
+ */
+static EphemeralNamedRelation
+_SPI_find_ENR_by_name(const char *name)
+{
+ /* internal static function; any error is bug in SPI itself */
+ Assert(name != NULL);
+
+ /* fast exit if no tuplestores have been added */
+ if (_SPI_current->queryEnv == NULL)
+ return NULL;
+
+ return get_ENR(_SPI_current->queryEnv, name);
+}
+
+/*
+ * Register an ephemeral named relation for use by the planner and executor on
+ * subsequent calls using this SPI connection.
+ */
+int
+SPI_register_relation(EphemeralNamedRelation enr)
+{
+ EphemeralNamedRelation match;
+ int res;
+
+ if (enr == NULL || enr->md.name == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(false); /* keep current memory context */
+ if (res < 0)
+ return res;
+
+ match = _SPI_find_ENR_by_name(enr->md.name);
+ if (match)
+ res = SPI_ERROR_REL_DUPLICATE;
+ else
+ {
+ if (_SPI_current->queryEnv == NULL)
+ _SPI_current->queryEnv = create_queryEnv();
+
+ register_ENR(_SPI_current->queryEnv, enr);
+ res = SPI_OK_REL_REGISTER;
+ }
+
+ _SPI_end_call(false);
+
+ return res;
+}
+
+/*
+ * Unregister an ephemeral named relation by name. This will probably be a
+ * rarely used function, since SPI_finish will clear it automatically.
+ */
+int
+SPI_unregister_relation(const char *name)
+{
+ EphemeralNamedRelation match;
+ int res;
+
+ if (name == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ res = _SPI_begin_call(false); /* keep current memory context */
+ if (res < 0)
+ return res;
+
+ match = _SPI_find_ENR_by_name(name);
+ if (match)
+ {
+ unregister_ENR(_SPI_current->queryEnv, match->md.name);
+ res = SPI_OK_REL_UNREGISTER;
+ }
+ else
+ res = SPI_ERROR_REL_NOT_FOUND;
+
+ _SPI_end_call(false);
+
+ return res;
+}
+
+/*
+ * Register the transient relations from 'tdata' using this SPI connection.
+ * This should be called by PL implementations' trigger handlers after
+ * connecting, in order to make transition tables visible to any queries run
+ * in this connection.
+ */
+int
+SPI_register_trigger_data(TriggerData *tdata)
+{
+ if (tdata == NULL)
+ return SPI_ERROR_ARGUMENT;
+
+ if (tdata->tg_newtable)
+ {
+ EphemeralNamedRelation enr =
+ palloc(sizeof(EphemeralNamedRelationData));
+ int rc;
+
+ enr->md.name = tdata->tg_trigger->tgnewtable;
+ enr->md.reliddesc = tdata->tg_relation->rd_id;
+ enr->md.tupdesc = NULL;
+ enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+ enr->md.enrtuples = tuplestore_tuple_count(tdata->tg_newtable);
+ enr->reldata = tdata->tg_newtable;
+ rc = SPI_register_relation(enr);
+ if (rc != SPI_OK_REL_REGISTER)
+ return rc;
+ }
+
+ if (tdata->tg_oldtable)
+ {
+ EphemeralNamedRelation enr =
+ palloc(sizeof(EphemeralNamedRelationData));
+ int rc;
+
+ enr->md.name = tdata->tg_trigger->tgoldtable;
+ enr->md.reliddesc = tdata->tg_relation->rd_id;
+ enr->md.tupdesc = NULL;
+ enr->md.enrtype = ENR_NAMED_TUPLESTORE;
+ enr->md.enrtuples = tuplestore_tuple_count(tdata->tg_oldtable);
+ enr->reldata = tdata->tg_oldtable;
+ rc = SPI_register_relation(enr);
+ if (rc != SPI_OK_REL_REGISTER)
+ return rc;
+ }
+
+ return SPI_OK_TD_REGISTER;
+}