mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
In a similar effort to f01592f91, here we mostly rename shadowed local variables to remove the warnings produced when compiling with -Wshadow=compatible-local. This fixes 63 warnings and leaves just 5. Author: Justin Pryzby, David Rowley Reviewed-by: Justin Pryzby Discussion https://postgr.es/m/20220817145434.GC26426%40telsasoft.com
3377 lines
84 KiB
C
3377 lines
84 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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";
|
|
}
|
|
/* 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 *querytree_list;
|
|
|
|
/*
|
|
* Parameter datatypes are driven by parserSetup hook if provided,
|
|
* otherwise we use the fixed parameter list.
|
|
*/
|
|
if (parsetree == NULL)
|
|
querytree_list = NIL;
|
|
else if (plan->parserSetup != NULL)
|
|
{
|
|
Assert(plan->nargs == 0);
|
|
querytree_list = pg_analyze_and_rewrite_withcb(parsetree,
|
|
src,
|
|
plan->parserSetup,
|
|
plan->parserSetupArg,
|
|
_SPI_current->queryEnv);
|
|
}
|
|
else
|
|
{
|
|
querytree_list = pg_analyze_and_rewrite_fixedparams(parsetree,
|
|
src,
|
|
plan->argtypes,
|
|
plan->nargs,
|
|
_SPI_current->queryEnv);
|
|
}
|
|
|
|
/* Finish filling in the CachedPlanSource */
|
|
CompleteCachedPlan(plansource,
|
|
querytree_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 = ¶mLI->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;
|
|
}
|