* spi.c
* Server Programming Interface
*
- * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.133 2004/12/31 21:59:45 pgsql Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.150 2006/04/04 19:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "executor/spi_priv.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/typcache.h"
static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
static int _SPI_execute_plan(_SPI_plan *plan,
- Datum *Values, const char *Nulls,
- Snapshot snapshot, Snapshot crosscheck_snapshot,
- bool read_only, int tcount);
+ Datum *Values, const char *Nulls,
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool read_only, long tcount);
-static int _SPI_pquery(QueryDesc *queryDesc, int tcount);
+static int _SPI_pquery(QueryDesc *queryDesc, long tcount);
static void _SPI_error_callback(void *arg);
-static void _SPI_cursor_operation(Portal portal, bool forward, int count,
+static void _SPI_cursor_operation(Portal portal, bool forward, long count,
DestReceiver *dest);
static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
int newdepth;
/*
- * When procedure called by Executor _SPI_curid expected to be equal
- * to _SPI_connected
+ * When procedure called by Executor _SPI_curid expected to be equal to
+ * _SPI_connected
*/
if (_SPI_curid != _SPI_connected)
return SPI_ERROR_CONNECT;
_SPI_current = &(_SPI_stack[_SPI_connected]);
_SPI_current->processed = 0;
+ _SPI_current->lastoid = InvalidOid;
_SPI_current->tuptable = NULL;
- _SPI_current->procCxt = NULL; /* in case we fail to create 'em */
+ _SPI_current->procCxt = NULL; /* in case we fail to create 'em */
_SPI_current->execCxt = NULL;
_SPI_current->connectSubid = GetCurrentSubTransactionId();
* Create memory contexts for this procedure
*
* XXX it would be better to use PortalContext as the parent context, but
- * we may not be inside a portal (consider deferred-trigger
- * execution). Perhaps CurTransactionContext would do? For now it
- * doesn't matter because we clean up explicitly in AtEOSubXact_SPI().
+ * we may not be inside a portal (consider deferred-trigger execution).
+ * Perhaps CurTransactionContext would do? For now it doesn't matter
+ * because we clean up explicitly in AtEOSubXact_SPI().
*/
_SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
"SPI Proc",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
_SPI_current->execCxt = AllocSetContextCreate(TopTransactionContext,
"SPI Exec",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
/* ... and switch to procedure's context */
_SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt);
SPI_tuptable = NULL;
/*
- * After _SPI_begin_call _SPI_connected == _SPI_curid. Now we are
- * closing connection to SPI and returning to upper Executor and so
- * _SPI_connected must be equal to _SPI_curid.
+ * After _SPI_begin_call _SPI_connected == _SPI_curid. Now we are closing
+ * connection to SPI and returning to upper Executor and so _SPI_connected
+ * must be equal to _SPI_curid.
*/
_SPI_connected--;
_SPI_curid--;
AtEOXact_SPI(bool isCommit)
{
/*
- * Note that memory contexts belonging to SPI stack entries will be
- * freed automatically, so we can ignore them here. We just need to
- * restore our static variables to initial state.
+ * Note that memory contexts belonging to SPI stack entries will be freed
+ * automatically, so we can ignore them here. We just need to restore our
+ * static variables to initial state.
*/
if (isCommit && _SPI_connected != -1)
ereport(WARNING,
/*
* Pop the stack entry and reset global variables. Unlike
- * SPI_finish(), we don't risk switching to memory contexts that
- * might be already gone.
+ * SPI_finish(), we don't risk switching to memory contexts that might
+ * be already gone.
*/
_SPI_connected--;
_SPI_curid = _SPI_connected;
_SPI_curid = _SPI_connected - 1;
}
-/* Parse, plan, and execute a querystring */
+/* Parse, plan, and execute a query string */
int
-SPI_execute(const char *src, bool read_only, int tcount)
+SPI_execute(const char *src, bool read_only, long tcount)
{
_SPI_plan plan;
int res;
/* Obsolete version of SPI_execute */
int
-SPI_exec(const char *src, int tcount)
+SPI_exec(const char *src, long tcount)
{
return SPI_execute(src, false, tcount);
}
/* Execute a previously prepared plan */
int
SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
- bool read_only, int tcount)
+ bool read_only, long tcount)
{
int res;
/* Obsolete version of SPI_execute_plan */
int
-SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
+SPI_execp(void *plan, Datum *Values, const char *Nulls, long tcount)
{
return SPI_execute_plan(plan, Values, Nulls, false, tcount);
}
SPI_execute_snapshot(void *plan,
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
- bool read_only, int tcount)
+ bool read_only, long tcount)
{
int res;
mtuple = heap_formtuple(rel->rd_att, v, n);
/*
- * copy the identification info of the old tuple: t_ctid, t_self,
- * and OID (if any)
+ * copy the identification info of the old tuple: t_ctid, t_self, and
+ * OID (if any)
*/
mtuple->t_data->t_ctid = tuple->t_data->t_ctid;
mtuple->t_self = tuple->t_self;
char *
SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
{
+ char *result;
Datum origval,
- val,
- result;
+ val;
bool isnull;
Oid typoid,
- foutoid,
- typioparam;
- int32 typmod;
+ foutoid;
bool typisvarlena;
SPI_result = 0;
return NULL;
if (fnumber > 0)
- {
typoid = tupdesc->attrs[fnumber - 1]->atttypid;
- typmod = tupdesc->attrs[fnumber - 1]->atttypmod;
- }
else
- {
typoid = (SystemAttributeDefinition(fnumber, true))->atttypid;
- typmod = -1;
- }
- getTypeOutputInfo(typoid, &foutoid, &typioparam, &typisvarlena);
+ getTypeOutputInfo(typoid, &foutoid, &typisvarlena);
/*
- * If we have a toasted datum, forcibly detoast it here to avoid
- * memory leakage inside the type's output routine.
+ * If we have a toasted datum, forcibly detoast it here to avoid memory
+ * leakage inside the type's output routine.
*/
if (typisvarlena)
val = PointerGetDatum(PG_DETOAST_DATUM(origval));
else
val = origval;
- result = OidFunctionCall3(foutoid,
- val,
- ObjectIdGetDatum(typioparam),
- Int32GetDatum(typmod));
+ result = OidOutputFunctionCall(foutoid, val);
/* Clean up detoasted copy, if any */
if (val != origval)
pfree(DatumGetPointer(val));
- return DatumGetCString(result);
+ return result;
}
Datum
return pstrdup(RelationGetRelationName(rel));
}
+char *
+SPI_getnspname(Relation rel)
+{
+ return get_namespace_name(RelationGetNamespace(rel));
+}
+
void *
SPI_palloc(Size size)
{
Portal portal;
int k;
- /* Ensure that the plan contains only one regular SELECT query */
+ /* Ensure that the plan contains only one query */
if (list_length(ptlist) != 1 || list_length(qtlist) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
queryTree = (Query *) linitial((List *) linitial(qtlist));
planTree = (Plan *) linitial(ptlist);
- if (queryTree->commandType != CMD_SELECT)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("cannot open non-SELECT query as cursor")));
- if (queryTree->into != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("cannot open SELECT INTO query as cursor")));
+ /* Must be a query that returns tuples */
+ switch (queryTree->commandType)
+ {
+ case CMD_SELECT:
+ if (queryTree->into != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cannot open SELECT INTO query as cursor")));
+ break;
+ case CMD_UTILITY:
+ if (!UtilityReturnsTuples(queryTree->utilityStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cannot open non-SELECT query as cursor")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cannot open non-SELECT query as cursor")));
+ break;
+ }
- /* Reset SPI result */
+ /* Reset SPI result (note we deliberately don't touch lastoid) */
SPI_processed = 0;
SPI_tuptable = NULL;
_SPI_current->processed = 0;
* Set up the portal.
*/
PortalDefineQuery(portal,
- NULL, /* unfortunately don't have sourceText */
- "SELECT", /* cursor's query is always a SELECT */
+ spiplan->query,
+ "SELECT", /* don't have the raw parse tree... */
list_make1(queryTree),
list_make1(planTree),
PortalGetHeapMemory(portal));
* Set up options for portal.
*/
portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
- if (ExecSupportsBackwardScan(plan))
+ if (planTree == NULL || ExecSupportsBackwardScan(planTree))
portal->cursorOptions |= CURSOR_OPT_SCROLL;
else
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
/*
- * Set up the snapshot to use. (PortalStart will do CopySnapshot,
- * so we skip that here.)
+ * Set up the snapshot to use. (PortalStart will do CopySnapshot, so we
+ * skip that here.)
*/
if (read_only)
snapshot = ActiveSnapshot;
*/
PortalStart(portal, paramLI, snapshot);
- Assert(portal->strategy == PORTAL_ONE_SELECT);
+ Assert(portal->strategy == PORTAL_ONE_SELECT ||
+ portal->strategy == PORTAL_UTIL_SELECT);
/* Return the created portal */
return portal;
* Fetch rows in a cursor
*/
void
-SPI_cursor_fetch(Portal portal, bool forward, int count)
+SPI_cursor_fetch(Portal portal, bool forward, long count)
{
_SPI_cursor_operation(portal, forward, count,
- CreateDestReceiver(SPI, NULL));
- /* we know that the SPI receiver doesn't need a destroy call */
+ CreateDestReceiver(DestSPI, NULL));
+ /* we know that the DestSPI receiver doesn't need a destroy call */
}
* Move in a cursor
*/
void
-SPI_cursor_move(Portal portal, bool forward, int count)
+SPI_cursor_move(Portal portal, bool forward, long count)
{
_SPI_cursor_operation(portal, forward, count, None_Receiver);
}
* of current SPI procedure
*/
void
-spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
+spi_printtup(TupleTableSlot *slot, DestReceiver *self)
{
SPITupleTable *tuptable;
MemoryContext oldcxt;
tuptable->free = 256;
tuptable->alloced += tuptable->free;
tuptable->vals = (HeapTuple *) repalloc(tuptable->vals,
- tuptable->alloced * sizeof(HeapTuple));
+ tuptable->alloced * sizeof(HeapTuple));
}
- tuptable->vals[tuptable->alloced - tuptable->free] = heap_copytuple(tuple);
+ tuptable->vals[tuptable->alloced - tuptable->free] =
+ ExecCopySlotTuple(slot);
(tuptable->free)--;
MemoryContextSwitchTo(oldcxt);
int nargs = plan->nargs;
/*
- * Increment CommandCounter to see changes made by now. We must do
- * this to be sure of seeing any schema changes made by a just-preceding
- * SPI command. (But we don't bother advancing the snapshot, since the
+ * Increment CommandCounter to see changes made by now. We must do this
+ * to be sure of seeing any schema changes made by a just-preceding SPI
+ * command. (But we don't bother advancing the snapshot, since the
* planner generally operates under SnapshotNow rules anyway.)
*/
CommandCounterIncrement();
/*
* Do parse analysis and rule rewrite for each raw parsetree.
*
- * We save the querytrees from each raw parsetree as a separate
- * sublist. This allows _SPI_execute_plan() to know where the
- * boundaries between original queries fall.
+ * We save the querytrees from each raw parsetree as a separate sublist.
+ * This allows _SPI_execute_plan() to know where the boundaries between
+ * original queries fall.
*/
query_list_list = NIL;
plan_list = NIL;
Node *parsetree = (Node *) lfirst(list_item);
List *query_list;
- query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
+ query_list = pg_analyze_and_rewrite(parsetree, src, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
- bool read_only, int tcount)
+ bool read_only, long tcount)
{
volatile int res = 0;
+ volatile uint32 my_processed = 0;
+ volatile Oid my_lastoid = InvalidOid;
+ SPITupleTable *volatile my_tuptable = NULL;
Snapshot saveActiveSnapshot;
/* Be sure to restore ActiveSnapshot on error exit */
else
paramLI = NULL;
- /* Reset state (only needed in case string is empty) */
- SPI_processed = 0;
- SPI_lastoid = InvalidOid;
- SPI_tuptable = NULL;
- _SPI_current->tuptable = NULL;
-
/*
* Setup error traceback support for ereport()
*/
List *query_list = lfirst(query_list_list_item);
ListCell *query_list_item;
- /* Reset state for each original parsetree */
- /* (at most one of its querytrees will be marked canSetTag) */
- SPI_processed = 0;
- SPI_lastoid = InvalidOid;
- SPI_tuptable = NULL;
- _SPI_current->tuptable = NULL;
-
foreach(query_list_item, query_list)
{
Query *queryTree = (Query *) lfirst(query_list_item);
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))
if (read_only && !QueryIsReadOnly(queryTree))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- /* translator: %s is a SQL statement name */
- errmsg("%s is not allowed in a non-volatile function",
- CreateQueryTag(queryTree))));
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a non-volatile function",
+ CreateQueryTag(queryTree))));
+
/*
* If not read-only mode, advance the command counter before
* each command.
if (!read_only)
CommandCounterIncrement();
- dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None,
+ dest = CreateDestReceiver(queryTree->canSetTag ? DestSPI : DestNone,
NULL);
if (snapshot == InvalidSnapshot)
}
FreeSnapshot(ActiveSnapshot);
ActiveSnapshot = NULL;
+
+ /*
+ * The last canSetTag query sets the auxiliary values returned
+ * to the caller. Be careful to free any tuptables not
+ * returned, to avoid intratransaction memory leak.
+ */
+ if (queryTree->canSetTag)
+ {
+ my_processed = _SPI_current->processed;
+ my_lastoid = _SPI_current->lastoid;
+ SPI_freetuptable(my_tuptable);
+ my_tuptable = _SPI_current->tuptable;
+ }
+ else
+ {
+ SPI_freetuptable(_SPI_current->tuptable);
+ _SPI_current->tuptable = NULL;
+ }
/* we know that the receiver doesn't need a destroy call */
if (res < 0)
goto fail;
ActiveSnapshot = saveActiveSnapshot;
+ /* Save results for caller */
+ SPI_processed = my_processed;
+ SPI_lastoid = my_lastoid;
+ SPI_tuptable = my_tuptable;
+
return res;
}
static int
-_SPI_pquery(QueryDesc *queryDesc, int tcount)
+_SPI_pquery(QueryDesc *queryDesc, long tcount)
{
int operation = queryDesc->operation;
int res;
- Oid save_lastoid;
switch (operation)
{
case CMD_SELECT:
res = SPI_OK_SELECT;
- if (queryDesc->parsetree->into != NULL) /* select into table */
+ if (queryDesc->parsetree->into) /* select into table? */
{
res = SPI_OK_SELINTO;
queryDesc->dest = None_Receiver; /* don't output results */
}
+ else if (queryDesc->dest->mydest != DestSPI)
+ {
+ /* Don't return SPI_OK_SELECT if we're discarding result */
+ res = SPI_OK_UTILITY;
+ }
break;
case CMD_INSERT:
res = SPI_OK_INSERT;
AfterTriggerBeginQuery();
- ExecutorStart(queryDesc, false);
+ ExecutorStart(queryDesc, 0);
- ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
+ ExecutorRun(queryDesc, ForwardScanDirection, tcount);
_SPI_current->processed = queryDesc->estate->es_processed;
- save_lastoid = queryDesc->estate->es_lastoid;
+ _SPI_current->lastoid = queryDesc->estate->es_lastoid;
- if (operation == CMD_SELECT && queryDesc->dest->mydest == SPI)
+ if (operation == CMD_SELECT && queryDesc->dest->mydest == DestSPI)
{
if (_SPI_checktuples())
elog(ERROR, "consistency check on SPI tuple count failed");
}
- ExecutorEnd(queryDesc);
-
/* Take care of any queued AFTER triggers */
- AfterTriggerEndQuery();
+ AfterTriggerEndQuery(queryDesc->estate);
- if (queryDesc->dest->mydest == SPI)
- {
- SPI_processed = _SPI_current->processed;
- SPI_lastoid = save_lastoid;
- SPI_tuptable = _SPI_current->tuptable;
- }
- else if (res == SPI_OK_SELECT)
- {
- /* Don't return SPI_OK_SELECT if we discarded the result */
- res = SPI_OK_UTILITY;
- }
+ ExecutorEnd(queryDesc);
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
int syntaxerrposition;
/*
- * If there is a syntax error position, convert to internal syntax
- * error; otherwise treat the query as an item of context stack
+ * If there is a syntax error position, convert to internal syntax error;
+ * otherwise treat the query as an item of context stack
*/
syntaxerrposition = geterrposition();
if (syntaxerrposition > 0)
* Do a FETCH or MOVE in a cursor
*/
static void
-_SPI_cursor_operation(Portal portal, bool forward, int count,
+_SPI_cursor_operation(Portal portal, bool forward, long count,
DestReceiver *dest)
{
long nfetched;
if (_SPI_begin_call(true) < 0)
elog(ERROR, "SPI cursor operation called while not connected");
- /* Reset the SPI result */
+ /* Reset the SPI result (note we deliberately don't touch lastoid) */
SPI_processed = 0;
SPI_tuptable = NULL;
_SPI_current->processed = 0;
/* Run the cursor */
nfetched = PortalRunFetch(portal,
forward ? FETCH_FORWARD : FETCH_BACKWARD,
- (long) count,
+ count,
dest);
/*
- * Think not to combine this store with the preceding function call.
- * If the portal contains calls to functions that use SPI, then
- * SPI_stack is likely to move around while the portal runs. When
- * control returns, _SPI_current will point to the correct stack
- * entry... but the pointer may be different than it was beforehand.
- * So we must be sure to re-fetch the pointer after the function call
- * completes.
+ * Think not to combine this store with the preceding function call. If
+ * the portal contains calls to functions that use SPI, then SPI_stack is
+ * likely to move around while the portal runs. When control returns,
+ * _SPI_current will point to the correct stack entry... but the pointer
+ * may be different than it was beforehand. So we must be sure to re-fetch
+ * the pointer after the function call completes.
*/
_SPI_current->processed = nfetched;
- if (dest->mydest == SPI && _SPI_checktuples())
+ if (dest->mydest == DestSPI && _SPI_checktuples())
elog(ERROR, "consistency check on SPI tuple count failed");
/* Put the result into place for access by caller */
parentcxt = _SPI_current->procCxt;
else if (location == _SPI_CPLAN_TOPCXT)
parentcxt = TopMemoryContext;
- else /* (this case not currently used) */
+ else
+ /* (this case not currently used) */
parentcxt = CurrentMemoryContext;
/*
- * Create a memory context for the plan. We don't expect the plan to
- * be very large, so use smaller-than-default alloc parameters.
+ * Create a memory context for the plan. We don't expect the plan to be
+ * very large, so use smaller-than-default alloc parameters.
*/
plancxt = AllocSetContextCreate(parentcxt,
"SPI Plan",