postgresql/src/backend/executor/spi.c

1831 lines
43 KiB
C
Raw Normal View History

1997-08-29 11:05:57 +02:00
/*-------------------------------------------------------------------------
*
* spi.c
* Server Programming Interface
1997-08-29 11:05:57 +02:00
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.169 2007/01/09 22:00:59 momjian Exp $
*
1997-08-29 11:05:57 +02:00
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/printtup.h"
#include "catalog/heap.h"
#include "commands/trigger.h"
#include "executor/spi_priv.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
1997-08-29 11:05:57 +02:00
2001-03-22 05:01:46 +01:00
uint32 SPI_processed = 0;
Oid SPI_lastoid = InvalidOid;
SPITupleTable *SPI_tuptable = NULL;
2001-03-22 05:01:46 +01:00
int SPI_result;
1997-08-29 11:05:57 +02:00
static _SPI_connection *_SPI_stack = NULL;
static _SPI_connection *_SPI_current = NULL;
2004-08-29 07:07:03 +02:00
static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1;
static int _SPI_curid = -1;
static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
1997-09-06 13:23:05 +02:00
static int _SPI_execute_plan(_SPI_plan *plan,
2005-10-15 04:49:52 +02:00
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, long tcount);
2005-10-15 04:49:52 +02:00
static int _SPI_pquery(QueryDesc *queryDesc, long tcount);
static void _SPI_error_callback(void *arg);
static void _SPI_cursor_operation(Portal portal, bool forward, long count,
DestReceiver *dest);
static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
static int _SPI_begin_call(bool execmem);
static int _SPI_end_call(bool procmem);
static MemoryContext _SPI_execmem(void);
static MemoryContext _SPI_procmem(void);
1997-09-25 14:16:05 +02:00
static bool _SPI_checktuples(void);
1997-08-29 11:05:57 +02:00
/* =================== interface functions =================== */
1997-08-29 11:05:57 +02:00
int
SPI_connect(void)
1997-08-29 11:05:57 +02:00
{
2004-08-29 07:07:03 +02:00
int newdepth;
2001-03-22 05:01:46 +01:00
/*
2005-10-15 04:49:52 +02:00
* When procedure called by Executor _SPI_curid expected to be equal to
* _SPI_connected
*/
if (_SPI_curid != _SPI_connected)
1998-09-01 05:29:17 +02:00
return SPI_ERROR_CONNECT;
if (_SPI_stack == NULL)
{
if (_SPI_connected != -1 || _SPI_stack_depth != 0)
elog(ERROR, "SPI stack corrupted");
newdepth = 16;
_SPI_stack = (_SPI_connection *)
MemoryContextAlloc(TopTransactionContext,
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;
}
}
/*
* We're entering procedure where _SPI_curid == _SPI_connected - 1
*/
_SPI_connected++;
Assert(_SPI_connected >= 0 && _SPI_connected < _SPI_stack_depth);
_SPI_current = &(_SPI_stack[_SPI_connected]);
_SPI_current->processed = 0;
_SPI_current->lastoid = InvalidOid;
_SPI_current->tuptable = NULL;
2005-10-15 04:49:52 +02:00
_SPI_current->procCxt = NULL; /* in case we fail to create 'em */
_SPI_current->execCxt = NULL;
_SPI_current->connectSubid = GetCurrentSubTransactionId();
/*
* Create memory contexts for this procedure
*
* XXX it would be better to use PortalContext as the parent context, but
* we may not be inside a portal (consider deferred-trigger execution).
2005-10-15 04:49:52 +02:00
* Perhaps CurTransactionContext would do? For now it doesn't matter
* because we clean up explicitly in AtEOSubXact_SPI().
*/
_SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
"SPI Proc",
2005-10-15 04:49:52 +02:00
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
_SPI_current->execCxt = AllocSetContextCreate(TopTransactionContext,
"SPI Exec",
2005-10-15 04:49:52 +02:00
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/* ... and switch to procedure's context */
_SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt);
1998-09-01 05:29:17 +02:00
return SPI_OK_CONNECT;
1997-08-29 11:05:57 +02:00
}
int
SPI_finish(void)
1997-08-29 11:05:57 +02:00
{
int res;
res = _SPI_begin_call(false); /* live in procedure memory */
if (res < 0)
1998-09-01 05:29:17 +02:00
return res;
/* Restore memory context as it was before procedure call */
MemoryContextSwitchTo(_SPI_current->savedcxt);
/* Release memory used in procedure call */
MemoryContextDelete(_SPI_current->execCxt);
_SPI_current->execCxt = NULL;
MemoryContextDelete(_SPI_current->procCxt);
_SPI_current->procCxt = NULL;
/*
* Reset result variables, especially SPI_tuptable which is probably
* pointing at a just-deleted tuptable
*/
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
/*
2005-10-15 04:49:52 +02:00
* After _SPI_begin_call _SPI_connected == _SPI_curid. Now we are closing
* connection to SPI and returning to upper Executor and so _SPI_connected
* must be equal to _SPI_curid.
*/
_SPI_connected--;
_SPI_curid--;
if (_SPI_connected == -1)
_SPI_current = NULL;
else
_SPI_current = &(_SPI_stack[_SPI_connected]);
1998-09-01 05:29:17 +02:00
return SPI_OK_FINISH;
1997-08-29 11:05:57 +02:00
}
/*
* Clean up SPI state at transaction commit or abort.
*/
void
AtEOXact_SPI(bool isCommit)
{
/*
2005-10-15 04:49:52 +02:00
* Note that memory contexts belonging to SPI stack entries will be freed
* automatically, so we can ignore them here. We just need to restore our
* static variables to initial state.
*/
if (isCommit && _SPI_connected != -1)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("transaction left non-empty SPI stack"),
2004-10-12 23:54:45 +02:00
errhint("Check for missing \"SPI_finish\" calls.")));
_SPI_current = _SPI_stack = NULL;
_SPI_stack_depth = 0;
_SPI_connected = _SPI_curid = -1;
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
}
/*
* 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)
{
2004-08-29 07:07:03 +02:00
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 */
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;
}
/*
2004-08-29 07:07:03 +02:00
* Pop the stack entry and reset global variables. Unlike
2005-10-15 04:49:52 +02:00
* SPI_finish(), we don't risk switching to memory contexts that might
* be already gone.
*/
_SPI_connected--;
_SPI_curid = _SPI_connected;
if (_SPI_connected == -1)
_SPI_current = NULL;
else
_SPI_current = &(_SPI_stack[_SPI_connected]);
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
}
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)
{
/* free Executor memory the same as _SPI_end_call would do */
MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
/* throw away any partially created tuple-table */
SPI_freetuptable(_SPI_current->tuptable);
_SPI_current->tuptable = NULL;
}
}
2004-03-17 02:05:10 +01:00
/* Pushes SPI stack to allow recursive SPI calls */
void
SPI_push(void)
{
_SPI_curid++;
}
2004-03-17 02:05:10 +01:00
/* Pops SPI stack to allow recursive SPI calls */
void
SPI_pop(void)
{
_SPI_curid--;
}
/* Restore state of SPI stack after aborting a subtransaction */
void
SPI_restore_connection(void)
{
Assert(_SPI_connected >= 0);
_SPI_curid = _SPI_connected - 1;
}
/* Parse, plan, and execute a query string */
1997-08-29 11:05:57 +02:00
int
SPI_execute(const char *src, bool read_only, long tcount)
{
_SPI_plan plan;
int res;
if (src == NULL || tcount < 0)
1998-09-01 05:29:17 +02:00
return SPI_ERROR_ARGUMENT;
res = _SPI_begin_call(true);
if (res < 0)
1998-09-01 05:29:17 +02:00
return res;
plan.plancxt = NULL; /* doesn't have own context */
plan.query = src;
plan.nargs = 0;
plan.argtypes = NULL;
_SPI_prepare_plan(src, &plan);
res = _SPI_execute_plan(&plan, NULL, NULL,
InvalidSnapshot, InvalidSnapshot,
read_only, tcount);
_SPI_end_call(true);
1998-09-01 05:29:17 +02:00
return res;
}
/* Obsolete version of SPI_execute */
int
SPI_exec(const char *src, long tcount)
{
return SPI_execute(src, false, tcount);
}
/* Execute a previously prepared plan */
int
SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
bool read_only, long tcount)
{
int res;
if (plan == NULL || tcount < 0)
1998-09-01 05:29:17 +02:00
return SPI_ERROR_ARGUMENT;
1997-09-11 09:24:37 +02:00
if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
1998-09-01 05:29:17 +02:00
return SPI_ERROR_PARAM;
res = _SPI_begin_call(true);
if (res < 0)
1998-09-01 05:29:17 +02:00
return res;
res = _SPI_execute_plan((_SPI_plan *) plan,
Values, Nulls,
InvalidSnapshot, InvalidSnapshot,
read_only, tcount);
_SPI_end_call(true);
return res;
}
/* Obsolete version of SPI_execute_plan */
int
SPI_execp(void *plan, Datum *Values, const char *Nulls, long tcount)
{
return SPI_execute_plan(plan, Values, Nulls, false, tcount);
}
/*
* SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow
* the caller to specify exactly which snapshots to use. 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(void *plan,
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, long tcount)
{
int res;
if (plan == NULL || tcount < 0)
return SPI_ERROR_ARGUMENT;
if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
return SPI_ERROR_PARAM;
res = _SPI_begin_call(true);
if (res < 0)
return res;
res = _SPI_execute_plan((_SPI_plan *) plan,
Values, Nulls,
snapshot, crosscheck_snapshot,
read_only, tcount);
_SPI_end_call(true);
1998-09-01 05:29:17 +02:00
return res;
}
void *
SPI_prepare(const char *src, int nargs, Oid *argtypes)
{
_SPI_plan plan;
_SPI_plan *result;
if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL))
{
SPI_result = SPI_ERROR_ARGUMENT;
1998-09-01 05:29:17 +02:00
return NULL;
}
SPI_result = _SPI_begin_call(true);
if (SPI_result < 0)
1998-09-01 05:29:17 +02:00
return NULL;
plan.plancxt = NULL; /* doesn't have own context */
plan.query = src;
plan.nargs = nargs;
plan.argtypes = argtypes;
_SPI_prepare_plan(src, &plan);
/* copy plan to procedure context */
result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
_SPI_end_call(true);
return (void *) result;
1997-09-06 13:23:05 +02:00
}
void *
SPI_saveplan(void *plan)
1997-09-06 13:23:05 +02:00
{
_SPI_plan *newplan;
if (plan == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
1998-09-01 05:29:17 +02:00
return NULL;
}
SPI_result = _SPI_begin_call(false); /* don't change context */
if (SPI_result < 0)
1998-09-01 05:29:17 +02:00
return NULL;
1997-09-11 09:24:37 +02:00
newplan = _SPI_copy_plan((_SPI_plan *) plan, _SPI_CPLAN_TOPCXT);
_SPI_curid--;
SPI_result = 0;
1998-09-01 05:29:17 +02:00
return (void *) newplan;
1997-09-06 13:23:05 +02:00
}
int
SPI_freeplan(void *plan)
{
_SPI_plan *spiplan = (_SPI_plan *) plan;
if (plan == NULL)
return SPI_ERROR_ARGUMENT;
MemoryContextDelete(spiplan->plancxt);
return 0;
}
HeapTuple
SPI_copytuple(HeapTuple tuple)
{
MemoryContext oldcxt = NULL;
HeapTuple ctuple;
if (tuple == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
1998-09-01 05:29:17 +02:00
return NULL;
}
if (_SPI_curid + 1 == _SPI_connected) /* connected */
{
if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
elog(ERROR, "SPI stack corrupted");
oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
}
ctuple = heap_copytuple(tuple);
if (oldcxt)
MemoryContextSwitchTo(oldcxt);
1998-09-01 05:29:17 +02:00
return ctuple;
}
HeapTupleHeader
SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
{
MemoryContext oldcxt = NULL;
2004-08-29 07:07:03 +02:00
HeapTupleHeader dtup;
if (tuple == NULL || tupdesc == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
return NULL;
}
/* For RECORD results, make sure a typmod has been assigned */
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
assign_record_type_typmod(tupdesc);
if (_SPI_curid + 1 == _SPI_connected) /* connected */
{
if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
elog(ERROR, "SPI stack corrupted");
oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
}
dtup = (HeapTupleHeader) palloc(tuple->t_len);
memcpy((char *) dtup, (char *) tuple->t_data, tuple->t_len);
HeapTupleHeaderSetDatumLength(dtup, tuple->t_len);
HeapTupleHeaderSetTypeId(dtup, tupdesc->tdtypeid);
HeapTupleHeaderSetTypMod(dtup, tupdesc->tdtypmod);
if (oldcxt)
MemoryContextSwitchTo(oldcxt);
return dtup;
}
HeapTuple
SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
Datum *Values, const char *Nulls)
{
MemoryContext oldcxt = NULL;
HeapTuple mtuple;
int numberOfAttributes;
Datum *v;
char *n;
int i;
if (rel == NULL || tuple == NULL || natts < 0 || attnum == NULL || Values == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
1998-09-01 05:29:17 +02:00
return NULL;
}
if (_SPI_curid + 1 == _SPI_connected) /* connected */
{
if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
elog(ERROR, "SPI stack corrupted");
oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
}
SPI_result = 0;
numberOfAttributes = rel->rd_att->natts;
v = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
n = (char *) palloc(numberOfAttributes * sizeof(char));
/* fetch old values and nulls */
heap_deformtuple(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') ? 'n' : ' ';
}
1998-09-01 05:29:17 +02:00
if (i == natts) /* no errors in *attnum */
{
mtuple = heap_formtuple(rel->rd_att, v, n);
2002-09-04 22:31:48 +02:00
/*
2005-10-15 04:49:52 +02:00
* 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;
if (rel->rd_att->tdhasoid)
HeapTupleSetOid(mtuple, HeapTupleGetOid(tuple));
}
else
{
mtuple = NULL;
SPI_result = SPI_ERROR_NOATTRIBUTE;
}
pfree(v);
pfree(n);
if (oldcxt)
MemoryContextSwitchTo(oldcxt);
1998-09-01 05:29:17 +02:00
return mtuple;
}
1997-09-06 13:23:05 +02:00
int
SPI_fnumber(TupleDesc tupdesc, const char *fname)
1997-09-06 13:23:05 +02:00
{
int res;
Form_pg_attribute sysatt;
for (res = 0; res < tupdesc->natts; res++)
{
if (namestrcmp(&tupdesc->attrs[res]->attname, fname) == 0)
1998-09-01 05:29:17 +02:00
return res + 1;
}
sysatt = SystemAttributeByName(fname, true /* "oid" will be accepted */ );
if (sysatt != NULL)
return sysatt->attnum;
/* SPI_ERROR_NOATTRIBUTE is different from all sys column numbers */
1998-09-01 05:29:17 +02:00
return SPI_ERROR_NOATTRIBUTE;
1997-09-06 13:23:05 +02:00
}
char *
1997-09-11 09:24:37 +02:00
SPI_fname(TupleDesc tupdesc, int fnumber)
{
Form_pg_attribute att;
1997-09-11 09:24:37 +02:00
SPI_result = 0;
if (fnumber > tupdesc->natts || fnumber == 0 ||
fnumber <= FirstLowInvalidHeapAttributeNumber)
1997-09-11 09:24:37 +02:00
{
SPI_result = SPI_ERROR_NOATTRIBUTE;
1998-09-01 05:29:17 +02:00
return NULL;
1997-09-11 09:24:37 +02:00
}
if (fnumber > 0)
att = tupdesc->attrs[fnumber - 1];
else
att = SystemAttributeDefinition(fnumber, true);
return pstrdup(NameStr(att->attname));
1997-09-11 09:24:37 +02:00
}
char *
SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
1997-09-06 13:23:05 +02:00
{
char *result;
Datum origval,
val;
bool isnull;
Oid typoid,
foutoid;
bool typisvarlena;
SPI_result = 0;
if (fnumber > HeapTupleHeaderGetNatts(tuple->t_data) || fnumber == 0 ||
fnumber <= FirstLowInvalidHeapAttributeNumber)
{
SPI_result = SPI_ERROR_NOATTRIBUTE;
1998-09-01 05:29:17 +02:00
return NULL;
}
origval = heap_getattr(tuple, fnumber, tupdesc, &isnull);
if (isnull)
1998-09-01 05:29:17 +02:00
return NULL;
if (fnumber > 0)
typoid = tupdesc->attrs[fnumber - 1]->atttypid;
else
typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
/*
2005-10-15 04:49:52 +02:00
* If we have a toasted datum, forcibly detoast it here to avoid memory
* leakage inside the type's output routine.
*/
if (typisvarlena)
val = PointerGetDatum(PG_DETOAST_DATUM(origval));
else
val = origval;
result = OidOutputFunctionCall(foutoid, val);
/* Clean up detoasted copy, if any */
if (val != origval)
pfree(DatumGetPointer(val));
return result;
1997-09-06 13:23:05 +02:00
}
Datum
SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull)
1997-09-06 13:23:05 +02:00
{
SPI_result = 0;
if (fnumber > HeapTupleHeaderGetNatts(tuple->t_data) || fnumber == 0 ||
fnumber <= FirstLowInvalidHeapAttributeNumber)
{
SPI_result = SPI_ERROR_NOATTRIBUTE;
*isnull = true;
1998-09-01 05:29:17 +02:00
return (Datum) NULL;
}
return heap_getattr(tuple, fnumber, tupdesc, isnull);
1997-09-06 13:23:05 +02:00
}
char *
SPI_gettype(TupleDesc tupdesc, int fnumber)
1997-09-06 13:23:05 +02:00
{
Oid typoid;
HeapTuple typeTuple;
char *result;
SPI_result = 0;
if (fnumber > tupdesc->natts || fnumber == 0 ||
fnumber <= FirstLowInvalidHeapAttributeNumber)
{
SPI_result = SPI_ERROR_NOATTRIBUTE;
1998-09-01 05:29:17 +02:00
return NULL;
}
if (fnumber > 0)
typoid = tupdesc->attrs[fnumber - 1]->atttypid;
else
typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
typeTuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(typoid),
0, 0, 0);
if (!HeapTupleIsValid(typeTuple))
{
SPI_result = SPI_ERROR_TYPUNKNOWN;
1998-09-01 05:29:17 +02:00
return NULL;
}
result = pstrdup(NameStr(((Form_pg_type) GETSTRUCT(typeTuple))->typname));
ReleaseSysCache(typeTuple);
return result;
1997-09-06 13:23:05 +02:00
}
Oid
SPI_gettypeid(TupleDesc tupdesc, int fnumber)
1997-09-06 13:23:05 +02:00
{
SPI_result = 0;
if (fnumber > tupdesc->natts || fnumber == 0 ||
fnumber <= FirstLowInvalidHeapAttributeNumber)
{
SPI_result = SPI_ERROR_NOATTRIBUTE;
1998-09-01 05:29:17 +02:00
return InvalidOid;
}
if (fnumber > 0)
return tupdesc->attrs[fnumber - 1]->atttypid;
else
return (SystemAttributeDefinition(fnumber, true))->atttypid;
1997-09-06 13:23:05 +02:00
}
char *
SPI_getrelname(Relation rel)
1997-09-06 13:23:05 +02:00
{
return pstrdup(RelationGetRelationName(rel));
1997-09-06 13:23:05 +02:00
}
char *
SPI_getnspname(Relation rel)
{
2005-10-15 04:49:52 +02:00
return get_namespace_name(RelationGetNamespace(rel));
}
void *
SPI_palloc(Size size)
{
MemoryContext oldcxt = NULL;
void *pointer;
if (_SPI_curid + 1 == _SPI_connected) /* connected */
{
if (_SPI_current != &(_SPI_stack[_SPI_curid + 1]))
elog(ERROR, "SPI stack corrupted");
oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt);
}
pointer = palloc(size);
if (oldcxt)
MemoryContextSwitchTo(oldcxt);
1998-09-01 05:29:17 +02:00
return pointer;
}
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);
}
void
SPI_freetuple(HeapTuple tuple)
{
/* No longer need to worry which context tuple was in... */
heap_freetuple(tuple);
}
void
SPI_freetuptable(SPITupleTable *tuptable)
{
if (tuptable != NULL)
MemoryContextDelete(tuptable->tuptabcxt);
}
/*
* SPI_cursor_open()
*
* Open a prepared SPI plan as a portal
*/
Portal
SPI_cursor_open(const char *name, void *plan,
Datum *Values, const char *Nulls,
bool read_only)
{
_SPI_plan *spiplan = (_SPI_plan *) plan;
List *qtlist;
List *ptlist;
ParamListInfo paramLI;
Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
int k;
/*
2006-10-04 02:30:14 +02:00
* Check that the plan is something the Portal code will special-case as
* returning one tupleset.
*/
if (!SPI_is_cursor_plan(spiplan))
{
/* try to give a good error message */
Query *queryTree;
if (list_length(spiplan->qtlist) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open multi-query plan as cursor")));
queryTree = PortalListGetPrimaryQuery((List *) linitial(spiplan->qtlist));
if (queryTree == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open empty query as cursor")));
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
2006-10-04 02:30:14 +02:00
/* translator: %s is name of a SQL command, eg INSERT */
errmsg("cannot open %s query as cursor",
CreateQueryTag(queryTree))));
}
Assert(list_length(spiplan->qtlist) == 1);
qtlist = (List *) linitial(spiplan->qtlist);
ptlist = spiplan->ptlist;
if (list_length(qtlist) != list_length(ptlist))
elog(ERROR, "corrupted SPI plan lists");
/* 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);
}
/* Switch to portal's memory and copy the parsetrees and plans to there */
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
qtlist = copyObject(qtlist);
ptlist = copyObject(ptlist);
/* If the plan has parameters, set them up */
if (spiplan->nargs > 0)
{
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
2006-10-04 02:30:14 +02:00
(spiplan->nargs - 1) *sizeof(ParamExternData));
paramLI->numParams = spiplan->nargs;
for (k = 0; k < spiplan->nargs; k++)
{
ParamExternData *prm = &paramLI->params[k];
prm->ptype = spiplan->argtypes[k];
prm->pflags = 0;
prm->isnull = (Nulls && Nulls[k] == 'n');
if (prm->isnull)
{
/* nulls just copy */
prm->value = Values[k];
}
else
{
/* pass-by-ref values must be copied into portal context */
int16 paramTypLen;
bool paramTypByVal;
get_typlenbyval(prm->ptype, &paramTypLen, &paramTypByVal);
prm->value = datumCopy(Values[k],
paramTypByVal, paramTypLen);
}
}
}
else
paramLI = NULL;
/*
* Set up the portal.
*/
PortalDefineQuery(portal,
NULL, /* no statement name */
spiplan->query,
CreateQueryTag(PortalListGetPrimaryQuery(qtlist)),
qtlist,
ptlist,
PortalGetHeapMemory(portal));
MemoryContextSwitchTo(oldcontext);
/*
* Set up options for portal.
*/
portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
if (list_length(ptlist) == 1 &&
ExecSupportsBackwardScan((Plan *) linitial(ptlist)))
portal->cursorOptions |= CURSOR_OPT_SCROLL;
else
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
/*
2005-10-15 04:49:52 +02:00
* Set up the snapshot to use. (PortalStart will do CopySnapshot, so we
* skip that here.)
*/
if (read_only)
snapshot = ActiveSnapshot;
else
{
CommandCounterIncrement();
snapshot = GetTransactionSnapshot();
}
/*
* Start portal execution.
*/
PortalStart(portal, paramLI, snapshot);
Assert(portal->strategy != PORTAL_MULTI_QUERY);
/* 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, count,
CreateDestReceiver(DestSPI, NULL));
/* 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, 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");
This patch implements holdable cursors, following the proposal (materialization into a tuple store) discussed on pgsql-hackers earlier. I've updated the documentation and the regression tests. Notes on the implementation: - I needed to change the tuple store API slightly -- it assumes that it won't be used to hold data across transaction boundaries, so the temp files that it uses for on-disk storage are automatically reclaimed at end-of-transaction. I added a flag to tuplestore_begin_heap() to control this behavior. Is changing the tuple store API in this fashion OK? - in order to store executor results in a tuple store, I added a new CommandDest. This works well for the most part, with one exception: the current DestFunction API doesn't provide enough information to allow the Executor to store results into an arbitrary tuple store (where the particular tuple store to use is chosen by the call site of ExecutorRun). To workaround this, I've temporarily hacked up a solution that works, but is not ideal: since the receiveTuple DestFunction is passed the portal name, we can use that to lookup the Portal data structure for the cursor and then use that to get at the tuple store the Portal is using. This unnecessarily ties the Portal code with the tupleReceiver code, but it works... The proper fix for this is probably to change the DestFunction API -- Tom suggested passing the full QueryDesc to the receiveTuple function. In that case, callers of ExecutorRun could "subclass" QueryDesc to add any additional fields that their particular CommandDest needed to get access to. This approach would work, but I'd like to think about it for a little bit longer before deciding which route to go. In the mean time, the code works fine, so I don't think a fix is urgent. - (semi-related) I added a NO SCROLL keyword to DECLARE CURSOR, and adjusted the behavior of SCROLL in accordance with the discussion on -hackers. - (unrelated) Cleaned up some SGML markup in sql.sgml, copy.sgml Neil Conway
2003-03-27 17:51:29 +01:00
PortalDrop(portal, false);
}
/*
* Returns the Oid representing the type id for argument at argIndex. First
* parameter is at index zero.
*/
Oid
SPI_getargtypeid(void *plan, int argIndex)
{
2004-08-29 07:07:03 +02:00
if (plan == NULL || argIndex < 0 || argIndex >= ((_SPI_plan *) plan)->nargs)
{
SPI_result = SPI_ERROR_ARGUMENT;
return InvalidOid;
}
return ((_SPI_plan *) plan)->argtypes[argIndex];
}
/*
* Returns the number of arguments for the prepared plan.
*/
int
SPI_getargcount(void *plan)
{
if (plan == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
return -1;
}
return ((_SPI_plan *) 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(void *plan)
{
2004-08-29 07:07:03 +02:00
_SPI_plan *spiplan = (_SPI_plan *) plan;
if (spiplan == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
return false;
}
if (list_length(spiplan->qtlist) != 1)
return false; /* not exactly 1 pre-rewrite command */
switch (ChoosePortalStrategy((List *) linitial(spiplan->qtlist)))
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_RETURNING:
case PORTAL_UTIL_SELECT:
/* OK */
return true;
case PORTAL_MULTI_QUERY:
/* will not return tuples */
break;
}
return false;
}
/*
* SPI_result_code_string --- convert any SPI return code to a string
*
2004-08-29 07:07:03 +02:00
* 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_CURSOR:
return "SPI_ERROR_CURSOR";
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_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";
}
/* Unrecognized code ... return something useful ... */
sprintf(buf, "Unrecognized SPI code %d", code);
return buf;
}
/* =================== 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;
/*
* When called by Executor _SPI_curid expected to be equal to
* _SPI_connected
*/
if (_SPI_curid != _SPI_connected || _SPI_connected < 0)
elog(ERROR, "improper call to spi_dest_startup");
if (_SPI_current != &(_SPI_stack[_SPI_curid]))
elog(ERROR, "SPI stack corrupted");
if (_SPI_current->tuptable != NULL)
elog(ERROR, "improper call to spi_dest_startup");
oldcxt = _SPI_procmem(); /* switch to procedure memory context */
tuptabcxt = AllocSetContextCreate(CurrentMemoryContext,
"SPI TupTable",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(tuptabcxt);
_SPI_current->tuptable = tuptable = (SPITupleTable *)
palloc(sizeof(SPITupleTable));
tuptable->tuptabcxt = tuptabcxt;
tuptable->alloced = tuptable->free = 128;
tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
MemoryContextSwitchTo(oldcxt);
}
1997-09-06 13:23:05 +02:00
/*
1999-05-25 18:15:34 +02:00
* spi_printtup
* store tuple retrieved by Executor into SPITupleTable
* of current SPI procedure
1997-09-06 13:23:05 +02:00
*/
void
spi_printtup(TupleTableSlot *slot, DestReceiver *self)
1997-09-06 13:23:05 +02:00
{
SPITupleTable *tuptable;
MemoryContext oldcxt;
/*
* When called by Executor _SPI_curid expected to be equal to
* _SPI_connected
*/
if (_SPI_curid != _SPI_connected || _SPI_connected < 0)
elog(ERROR, "improper call to spi_printtup");
if (_SPI_current != &(_SPI_stack[_SPI_curid]))
elog(ERROR, "SPI stack corrupted");
tuptable = _SPI_current->tuptable;
if (tuptable == NULL)
elog(ERROR, "improper call to spi_printtup");
oldcxt = MemoryContextSwitchTo(tuptable->tuptabcxt);
if (tuptable->free == 0)
{
tuptable->free = 256;
tuptable->alloced += tuptable->free;
tuptable->vals = (HeapTuple *) repalloc(tuptable->vals,
2005-10-15 04:49:52 +02:00
tuptable->alloced * sizeof(HeapTuple));
}
tuptable->vals[tuptable->alloced - tuptable->free] =
ExecCopySlotTuple(slot);
(tuptable->free)--;
MemoryContextSwitchTo(oldcxt);
}
1997-09-06 13:23:05 +02:00
/*
* Static functions
*/
/*
* Parse and plan a querystring.
*
* At entry, plan->argtypes and plan->nargs must be valid.
*
* Query and plan lists are stored into *plan.
*/
static void
_SPI_prepare_plan(const char *src, _SPI_plan *plan)
1997-08-29 11:05:57 +02:00
{
List *raw_parsetree_list;
List *query_list_list;
List *plan_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
Oid *argtypes = plan->argtypes;
int nargs = plan->nargs;
/*
2005-10-15 04:49:52 +02:00
* Increment CommandCounter to see changes made by now. We must do this
* to be sure of seeing any schema changes made by a just-preceding SPI
* command. (But we don't bother advancing the snapshot, since the
* planner generally operates under SnapshotNow rules anyway.)
*/
CommandCounterIncrement();
/*
* Setup error traceback support for ereport()
*/
spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = (void *) src;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
/*
* Parse the request string into a list of raw parse trees.
*/
raw_parsetree_list = pg_parse_query(src);
/*
* Do parse analysis and rule rewrite for each raw parsetree.
*
2005-10-15 04:49:52 +02:00
* We save the querytrees from each raw parsetree as a separate sublist.
* This allows _SPI_execute_plan() to know where the boundaries between
* original queries fall.
*/
query_list_list = NIL;
plan_list = NIL;
foreach(list_item, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(list_item);
List *query_list;
query_list = pg_analyze_and_rewrite(parsetree, src, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
plan_list = list_concat(plan_list,
pg_plan_queries(query_list, NULL, false));
}
plan->qtlist = query_list_list;
plan->ptlist = plan_list;
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
1997-08-29 11:05:57 +02:00
}
/*
* Execute the given plan with the given parameter values
*
* 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
* read_only: TRUE for read-only execution (no CommandCounterIncrement)
* tcount: execution tuple-count limit, or 0 for none
*/
1997-08-29 11:05:57 +02:00
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, long tcount)
1997-08-29 11:05:57 +02:00
{
volatile int my_res = 0;
volatile uint32 my_processed = 0;
volatile Oid my_lastoid = InvalidOid;
2005-10-15 04:49:52 +02:00
SPITupleTable *volatile my_tuptable = NULL;
volatile int res = 0;
Snapshot saveActiveSnapshot;
/* Be sure to restore ActiveSnapshot on error exit */
saveActiveSnapshot = ActiveSnapshot;
PG_TRY();
{
List *query_list_list = plan->qtlist;
ListCell *plan_list_item = list_head(plan->ptlist);
ListCell *query_list_list_item;
ErrorContextCallback spierrcontext;
int nargs = plan->nargs;
ParamListInfo paramLI;
/* Convert parameters to form wanted by executor */
if (nargs > 0)
{
int k;
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
2006-10-04 02:30:14 +02:00
(nargs - 1) *sizeof(ParamExternData));
paramLI->numParams = nargs;
for (k = 0; k < nargs; k++)
{
ParamExternData *prm = &paramLI->params[k];
prm->value = Values[k];
prm->isnull = (Nulls && Nulls[k] == 'n');
prm->pflags = 0;
prm->ptype = plan->argtypes[k];
}
}
else
paramLI = NULL;
/*
* Setup error traceback support for ereport()
*/
spierrcontext.callback = _SPI_error_callback;
spierrcontext.arg = (void *) plan->query;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
foreach(query_list_list_item, query_list_list)
{
List *query_list = lfirst(query_list_list_item);
ListCell *query_list_item;
foreach(query_list_item, query_list)
{
Query *queryTree = (Query *) lfirst(query_list_item);
Plan *planTree;
QueryDesc *qdesc;
DestReceiver *dest;
planTree = lfirst(plan_list_item);
plan_list_item = lnext(plan_list_item);
_SPI_current->processed = 0;
_SPI_current->lastoid = InvalidOid;
_SPI_current->tuptable = NULL;
if (queryTree->commandType == CMD_UTILITY)
{
if (IsA(queryTree->utilityStmt, CopyStmt))
{
CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
if (stmt->filename == NULL)
{
my_res = SPI_ERROR_COPY;
goto fail;
}
}
else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
IsA(queryTree->utilityStmt, ClosePortalStmt) ||
IsA(queryTree->utilityStmt, FetchStmt))
{
my_res = SPI_ERROR_CURSOR;
goto fail;
}
else if (IsA(queryTree->utilityStmt, TransactionStmt))
{
my_res = SPI_ERROR_TRANSACTION;
goto fail;
}
}
if (read_only && !QueryIsReadOnly(queryTree))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2005-10-15 04:49:52 +02:00
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
CreateQueryTag(queryTree))));
/*
* If not read-only mode, advance the command counter before
* each command.
*/
if (!read_only)
CommandCounterIncrement();
dest = CreateDestReceiver(queryTree->canSetTag ? DestSPI : DestNone,
NULL);
if (snapshot == InvalidSnapshot)
{
/*
* Default read_only behavior is to use the entry-time
* ActiveSnapshot; if read-write, grab a full new snap.
*/
if (read_only)
ActiveSnapshot = CopySnapshot(saveActiveSnapshot);
else
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
}
else
{
/*
* We interpret read_only with a specified snapshot to be
* exactly that snapshot, but read-write means use the
* snap with advancing of command ID.
*/
ActiveSnapshot = CopySnapshot(snapshot);
if (!read_only)
ActiveSnapshot->curcid = GetCurrentCommandId();
}
if (queryTree->commandType == CMD_UTILITY)
{
ProcessUtility(queryTree->utilityStmt, paramLI,
dest, NULL);
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free;
res = SPI_OK_UTILITY;
}
else
{
qdesc = CreateQueryDesc(queryTree, planTree,
ActiveSnapshot,
crosscheck_snapshot,
dest,
paramLI, false);
res = _SPI_pquery(qdesc,
queryTree->canSetTag ? tcount : 0);
FreeQueryDesc(qdesc);
}
FreeSnapshot(ActiveSnapshot);
ActiveSnapshot = NULL;
2005-10-15 04:49:52 +02:00
/*
2006-10-04 02:30:14 +02:00
* The last canSetTag query sets the status values returned to
* the caller. Be careful to free any tuptables not returned,
* to avoid intratransaction memory leak.
*/
if (queryTree->canSetTag)
{
my_processed = _SPI_current->processed;
my_lastoid = _SPI_current->lastoid;
SPI_freetuptable(my_tuptable);
my_tuptable = _SPI_current->tuptable;
my_res = res;
}
else
{
SPI_freetuptable(_SPI_current->tuptable);
_SPI_current->tuptable = NULL;
}
/* we know that the receiver doesn't need a destroy call */
if (res < 0)
{
my_res = res;
goto fail;
}
}
}
fail:
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
}
PG_CATCH();
{
/* Restore global vars and propagate error */
ActiveSnapshot = saveActiveSnapshot;
PG_RE_THROW();
}
PG_END_TRY();
ActiveSnapshot = saveActiveSnapshot;
/* Save results for caller */
SPI_processed = my_processed;
SPI_lastoid = my_lastoid;
SPI_tuptable = my_tuptable;
/* tuptable now is caller's responsibility, not SPI's */
_SPI_current->tuptable = NULL;
/*
* If none of the queries had canSetTag, we return the last query's result
* code, but not its auxiliary results (for backwards compatibility).
*/
if (my_res == 0)
my_res = res;
return my_res;
1997-09-06 13:23:05 +02:00
}
static int
_SPI_pquery(QueryDesc *queryDesc, long tcount)
1997-09-06 13:23:05 +02:00
{
1997-09-25 14:16:05 +02:00
int operation = queryDesc->operation;
int res;
switch (operation)
{
case CMD_SELECT:
2005-10-15 04:49:52 +02:00
if (queryDesc->parsetree->into) /* select into table? */
res = SPI_OK_SELINTO;
else if (queryDesc->dest->mydest != DestSPI)
{
/* 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->parsetree->returningList)
res = SPI_OK_INSERT_RETURNING;
else
res = SPI_OK_INSERT;
break;
case CMD_DELETE:
if (queryDesc->parsetree->returningList)
res = SPI_OK_DELETE_RETURNING;
else
res = SPI_OK_DELETE;
break;
case CMD_UPDATE:
if (queryDesc->parsetree->returningList)
res = SPI_OK_UPDATE_RETURNING;
else
res = SPI_OK_UPDATE;
break;
default:
1998-09-01 05:29:17 +02:00
return SPI_ERROR_OPUNKNOWN;
}
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ResetUsage();
#endif
AfterTriggerBeginQuery();
ExecutorStart(queryDesc, 0);
ExecutorRun(queryDesc, ForwardScanDirection, tcount);
_SPI_current->processed = queryDesc->estate->es_processed;
_SPI_current->lastoid = queryDesc->estate->es_lastoid;
if ((res == SPI_OK_SELECT || queryDesc->parsetree->returningList) &&
queryDesc->dest->mydest == DestSPI)
{
1997-09-25 14:16:05 +02:00
if (_SPI_checktuples())
elog(ERROR, "consistency check on SPI tuple count failed");
}
/* Take care of any queued AFTER triggers */
AfterTriggerEndQuery(queryDesc->estate);
ExecutorEnd(queryDesc);
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ShowUsage("SPI EXECUTOR STATS");
#endif
1997-08-29 11:05:57 +02:00
return res;
1997-08-29 11:05:57 +02:00
}
/*
* _SPI_error_callback
*
* Add context information when a query invoked via SPI fails
*/
static void
_SPI_error_callback(void *arg)
{
const char *query = (const char *) arg;
int syntaxerrposition;
/*
2005-10-15 04:49:52 +02:00
* 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
2004-10-12 23:54:45 +02:00
errcontext("SQL statement \"%s\"", query);
}
/*
* _SPI_cursor_operation()
*
* Do a FETCH or MOVE in a cursor
*/
static void
_SPI_cursor_operation(Portal portal, bool forward, long count,
DestReceiver *dest)
{
long 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,
forward ? FETCH_FORWARD : FETCH_BACKWARD,
count,
dest);
/*
2005-10-15 04:49:52 +02:00
* 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)
1997-08-29 11:05:57 +02:00
{
return MemoryContextSwitchTo(_SPI_current->execCxt);
1997-08-29 11:05:57 +02:00
}
static MemoryContext
_SPI_procmem(void)
1997-08-29 11:05:57 +02:00
{
return MemoryContextSwitchTo(_SPI_current->procCxt);
1997-08-29 11:05:57 +02:00
}
/*
* _SPI_begin_call: begin a SPI operation within a connected procedure
1997-08-29 11:05:57 +02:00
*/
static int
_SPI_begin_call(bool execmem)
1997-08-29 11:05:57 +02:00
{
if (_SPI_curid + 1 != _SPI_connected)
1998-09-01 05:29:17 +02:00
return SPI_ERROR_UNCONNECTED;
_SPI_curid++;
if (_SPI_current != &(_SPI_stack[_SPI_curid]))
elog(ERROR, "SPI stack corrupted");
if (execmem) /* switch to the Executor memory context */
_SPI_execmem();
1998-09-01 05:29:17 +02:00
return 0;
1997-08-29 11:05:57 +02:00
}
/*
* _SPI_end_call: end a SPI operation within a connected procedure
*
* Note: this currently has no failure return cases, so callers don't check
*/
1997-08-29 11:05:57 +02:00
static int
_SPI_end_call(bool procmem)
1997-08-29 11:05:57 +02:00
{
/*
* We're returning to procedure where _SPI_curid == _SPI_connected - 1
*/
_SPI_curid--;
if (procmem) /* switch to the procedure memory context */
{
_SPI_procmem();
/* and free Executor memory */
MemoryContextResetAndDeleteChildren(_SPI_current->execCxt);
}
1998-09-01 05:29:17 +02:00
return 0;
1997-08-29 11:05:57 +02:00
}
static bool
_SPI_checktuples(void)
1997-08-29 11:05:57 +02:00
{
uint32 processed = _SPI_current->processed;
SPITupleTable *tuptable = _SPI_current->tuptable;
bool failed = false;
2003-08-04 02:43:34 +02:00
if (tuptable == NULL) /* spi_dest_startup was not called */
failed = true;
else if (processed != (tuptable->alloced - tuptable->free))
failed = true;
1998-09-01 05:29:17 +02:00
return failed;
1997-08-29 11:05:57 +02:00
}
1997-09-06 13:23:05 +02:00
static _SPI_plan *
_SPI_copy_plan(_SPI_plan *plan, int location)
{
_SPI_plan *newplan;
MemoryContext oldcxt;
MemoryContext plancxt;
MemoryContext parentcxt;
/* Determine correct parent for the plan's memory context */
1997-09-11 09:24:37 +02:00
if (location == _SPI_CPLAN_PROCXT)
parentcxt = _SPI_current->procCxt;
1997-09-11 09:24:37 +02:00
else if (location == _SPI_CPLAN_TOPCXT)
parentcxt = TopMemoryContext;
2005-10-15 04:49:52 +02:00
else
/* (this case not currently used) */
parentcxt = CurrentMemoryContext;
/*
2005-10-15 04:49:52 +02:00
* Create a memory context for the plan. We don't expect the plan to be
* very large, so use smaller-than-default alloc parameters.
*/
plancxt = AllocSetContextCreate(parentcxt,
"SPI Plan",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(plancxt);
/* Copy the SPI plan into its own context */
newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
newplan->plancxt = plancxt;
newplan->query = pstrdup(plan->query);
newplan->qtlist = (List *) copyObject(plan->qtlist);
newplan->ptlist = (List *) copyObject(plan->ptlist);
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;
MemoryContextSwitchTo(oldcxt);
1998-09-01 05:29:17 +02:00
return newplan;
}