summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/transam/xact.c12
-rw-r--r--src/backend/commands/cluster.c6
-rw-r--r--src/backend/commands/copy.c7
-rw-r--r--src/backend/commands/explain.c12
-rw-r--r--src/backend/commands/indexcmds.c6
-rw-r--r--src/backend/commands/portalcmds.c4
-rw-r--r--src/backend/commands/prepare.c8
-rw-r--r--src/backend/commands/vacuum.c10
-rw-r--r--src/backend/executor/execMain.c47
-rw-r--r--src/backend/executor/functions.c277
-rw-r--r--src/backend/executor/spi.c478
-rw-r--r--src/backend/tcop/fastpath.c7
-rw-r--r--src/backend/tcop/postgres.c11
-rw-r--r--src/backend/tcop/pquery.c93
-rw-r--r--src/backend/tcop/utility.c93
-rw-r--r--src/backend/utils/adt/ri_triggers.c58
-rw-r--r--src/backend/utils/adt/ruleutils.c6
-rw-r--r--src/backend/utils/time/tqual.c138
18 files changed, 769 insertions, 504 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 47c501c393..ffcc324eb3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.187 2004/09/10 18:39:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.188 2004/09/13 20:06:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -401,11 +401,11 @@ CommandCounterIncrement(void)
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("cannot have more than 2^32-1 commands in a transaction")));
- /* Propagate new command ID into query snapshots, if set */
- if (QuerySnapshot)
- QuerySnapshot->curcid = s->commandId;
+ /* Propagate new command ID into static snapshots, if set */
if (SerializableSnapshot)
SerializableSnapshot->curcid = s->commandId;
+ if (LatestSnapshot)
+ LatestSnapshot->curcid = s->commandId;
/*
* make cache changes visible to me.
@@ -3001,8 +3001,10 @@ CommitSubTransaction(void)
s->state = TRANS_COMMIT;
- /* Mark subtransaction as subcommitted */
+ /* Must CCI to ensure commands of subtransaction are seen as done */
CommandCounterIncrement();
+
+ /* Mark subtransaction as subcommitted */
RecordSubTransactionCommit();
AtSubCommit_childXids();
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0bce21ffb9..6f06063b9e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.129 2004/08/29 05:06:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.130 2004/09/13 20:06:23 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -202,8 +202,8 @@ cluster(ClusterStmt *stmt)
/* Start a new transaction for each relation. */
StartTransactionCommand();
- SetQuerySnapshot(); /* might be needed for functions in
- * indexes */
+ /* functions in indexes may want a snapshot set */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
cluster_rel(rvtc, true);
CommitTransactionCommand();
}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b25c8eee98..9c49001b27 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.231 2004/09/10 18:39:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.232 2004/09/13 20:06:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1182,7 +1182,6 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
Oid *typioparams;
bool *isvarlena;
char *string;
- Snapshot mySnapshot;
ListCell *cur;
MemoryContext oldcontext;
MemoryContext mycontext;
@@ -1260,9 +1259,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
strlen(null_print));
}
- mySnapshot = CopyQuerySnapshot();
-
- scandesc = heap_beginscan(rel, mySnapshot, 0, NULL);
+ scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
{
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 9b7cf1ae49..f51b991b4d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.125 2004/09/10 18:39:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.126 2004/09/13 20:06:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -180,7 +180,9 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
plan = planner(query, isCursor, cursorOptions, NULL);
/* Create a QueryDesc requesting no output */
- queryDesc = CreateQueryDesc(query, plan, None_Receiver, NULL,
+ queryDesc = CreateQueryDesc(query, plan,
+ ActiveSnapshot, InvalidSnapshot,
+ None_Receiver, NULL,
stmt->analyze);
ExplainOnePlan(queryDesc, stmt, tstate);
@@ -212,7 +214,7 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
AfterTriggerBeginQuery();
/* call ExecutorStart to prepare the plan for execution */
- ExecutorStart(queryDesc, false, !stmt->analyze);
+ ExecutorStart(queryDesc, !stmt->analyze);
/* Execute the plan for statistics if asked for */
if (stmt->analyze)
@@ -272,7 +274,9 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
FreeQueryDesc(queryDesc);
- CommandCounterIncrement();
+ /* We need a CCI just in case query expanded to multiple plans */
+ if (stmt->analyze)
+ CommandCounterIncrement();
totaltime += elapsed_time(&starttime);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 6e550e67c6..bdbf8708b1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.125 2004/08/29 05:06:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.126 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1060,8 +1060,8 @@ ReindexDatabase(const char *dbname, bool force /* currently unused */ ,
Oid relid = lfirst_oid(l);
StartTransactionCommand();
- SetQuerySnapshot(); /* might be needed for functions in
- * indexes */
+ /* functions in indexes may want a snapshot set */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
if (reindex_relation(relid, true))
ereport(NOTICE,
(errmsg("table \"%s\" was reindexed",
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 390e20aa2e..98ffe4ae47 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.34 2004/09/10 18:39:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.35 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -135,7 +135,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
/*
* Start execution, inserting parameters if any.
*/
- PortalStart(portal, params);
+ PortalStart(portal, params, ActiveSnapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 032fe4acbc..2a6db32788 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
* Copyright (c) 2002-2004, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.31 2004/08/29 05:06:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.32 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -186,7 +186,7 @@ ExecuteQuery(ExecuteStmt *stmt, DestReceiver *dest, char *completionTag)
/*
* Run the portal to completion.
*/
- PortalStart(portal, paramLI);
+ PortalStart(portal, paramLI, ActiveSnapshot);
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
@@ -544,7 +544,9 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
}
/* Create a QueryDesc requesting no output */
- qdesc = CreateQueryDesc(query, plan, None_Receiver,
+ qdesc = CreateQueryDesc(query, plan,
+ ActiveSnapshot, InvalidSnapshot,
+ None_Receiver,
paramLI, stmt->analyze);
ExplainOnePlan(qdesc, stmt, tstate);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 8ace2777d4..ec39789f09 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.290 2004/08/30 02:54:38 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.291 2004/09/13 20:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -402,8 +402,8 @@ vacuum(VacuumStmt *vacstmt)
if (use_own_xacts)
{
StartTransactionCommand();
- SetQuerySnapshot(); /* might be needed for functions
- * in indexes */
+ /* functions in indexes may want a snapshot set */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
}
else
old_context = MemoryContextSwitchTo(anl_context);
@@ -865,8 +865,8 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind)
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
- SetQuerySnapshot(); /* might be needed for functions in
- * indexes */
+ /* functions in indexes may want a snapshot set */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Tell the cache replacement strategy that vacuum is causing all
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b009e73e10..ea9dce019b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.237 2004/09/11 18:28:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.238 2004/09/13 20:06:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -106,15 +106,6 @@ static void EvalPlanQualStop(evalPlanQual *epq);
* field of the QueryDesc is filled in to describe the tuples that will be
* returned, and the internal fields (estate and planstate) are set up.
*
- * If useCurrentSnapshot is true, run the query with the latest available
- * snapshot, instead of the normal QuerySnapshot. Also, if it's an update
- * or delete query, check that the rows to be updated or deleted would be
- * visible to the normal QuerySnapshot. (This is a special-case behavior
- * needed for referential integrity updates in serializable transactions.
- * We must check all currently-committed rows, but we want to throw a
- * can't-serialize error if any rows that would need updates would not be
- * visible under the normal serializable snapshot.)
- *
* If explainOnly is true, we are not actually intending to run the plan,
* only to set up for EXPLAIN; so skip unwanted side-effects.
*
@@ -123,7 +114,7 @@ static void EvalPlanQualStop(evalPlanQual *epq);
* ----------------------------------------------------------------
*/
void
-ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
+ExecutorStart(QueryDesc *queryDesc, bool explainOnly)
{
EState *estate;
MemoryContext oldcontext;
@@ -156,28 +147,12 @@ ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot, bool explainOnly)
estate->es_param_exec_vals = (ParamExecData *)
palloc0(queryDesc->plantree->nParamExec * sizeof(ParamExecData));
- estate->es_instrument = queryDesc->doInstrument;
-
/*
- * Make our own private copy of the current query snapshot data.
- *
- * This "freezes" our idea of which tuples are good and which are not for
- * the life of this query, even if it outlives the current command and
- * current snapshot.
+ * Copy other important information into the EState
*/
- if (useCurrentSnapshot)
- {
- /* RI update/delete query --- must use an up-to-date snapshot */
- estate->es_snapshot = CopyCurrentSnapshot();
- /* crosscheck updates/deletes against transaction snapshot */
- estate->es_crosscheck_snapshot = CopyQuerySnapshot();
- }
- else
- {
- /* normal query --- use query snapshot, no crosscheck */
- estate->es_snapshot = CopyQuerySnapshot();
- estate->es_crosscheck_snapshot = InvalidSnapshot;
- }
+ estate->es_snapshot = queryDesc->snapshot;
+ estate->es_crosscheck_snapshot = queryDesc->crosscheck_snapshot;
+ estate->es_instrument = queryDesc->doInstrument;
/*
* Initialize the plan state tree
@@ -1454,6 +1429,11 @@ ExecDelete(TupleTableSlot *slot,
/*
* delete the tuple
+ *
+ * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+ * the row to be deleted is visible to that snapshot, and throw a can't-
+ * serialize error if not. This is a special-case behavior needed for
+ * referential integrity updates in serializable transactions.
*/
ldelete:;
result = heap_delete(resultRelationDesc, tupleid,
@@ -1591,6 +1571,11 @@ lreplace:;
/*
* replace the heap tuple
+ *
+ * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+ * the row to be updated is visible to that snapshot, and throw a can't-
+ * serialize error if not. This is a special-case behavior needed for
+ * referential integrity updates in serializable transactions.
*/
result = heap_update(resultRelationDesc, tupleid, tuple,
&ctid,
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 3611c85a5f..1db5a4339f 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.88 2004/09/10 18:39:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.89 2004/09/13 20:06:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -65,6 +65,7 @@ typedef struct
bool typbyval; /* true if return type is pass by value */
bool returnsTuple; /* true if returning whole tuple result */
bool shutdown_reg; /* true if registered shutdown callback */
+ bool readonly_func; /* true to run in "read only" mode */
ParamListInfo paramLI; /* Param list representing current args */
@@ -76,11 +77,12 @@ typedef SQLFunctionCache *SQLFunctionCachePtr;
/* non-export function prototypes */
-static execution_state *init_execution_state(List *queryTree_list);
+static execution_state *init_execution_state(List *queryTree_list,
+ bool readonly_func);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es);
-static void postquel_end(execution_state *es);
+static void postquel_end(execution_state *es, SQLFunctionCachePtr fcache);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
static Datum postquel_execute(execution_state *es,
@@ -91,7 +93,7 @@ static void ShutdownSQLFunction(Datum arg);
static execution_state *
-init_execution_state(List *queryTree_list)
+init_execution_state(List *queryTree_list, bool readonly_func)
{
execution_state *firstes = NULL;
execution_state *preves = NULL;
@@ -103,6 +105,22 @@ init_execution_state(List *queryTree_list)
Plan *planTree;
execution_state *newes;
+ /* Precheck all commands for validity in a function */
+ if (queryTree->commandType == CMD_UTILITY &&
+ IsA(queryTree->utilityStmt, TransactionStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a SQL function",
+ CreateQueryTag(queryTree))));
+
+ if (readonly_func && !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))));
+
planTree = pg_plan_query(queryTree, NULL);
newes = (execution_state *) palloc(sizeof(execution_state));
@@ -172,6 +190,10 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->rettype = rettype;
+ /* Remember if function is STABLE/IMMUTABLE */
+ fcache->readonly_func =
+ (procedureStruct->provolatile != PROVOLATILE_VOLATILE);
+
/* Now look up the actual result type */
typeTuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(rettype),
@@ -253,7 +275,8 @@ init_sql_fcache(FmgrInfo *finfo)
queryTree_list);
/* Finally, plan the queries */
- fcache->func_state = init_execution_state(queryTree_list);
+ fcache->func_state = init_execution_state(queryTree_list,
+ fcache->readonly_func);
pfree(src);
@@ -267,16 +290,37 @@ init_sql_fcache(FmgrInfo *finfo)
static void
postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
{
+ Snapshot snapshot;
+
Assert(es->qd == NULL);
+
+ /*
+ * In a read-only function, use the surrounding query's snapshot;
+ * otherwise take a new snapshot for each query. The snapshot should
+ * include a fresh command ID so that all work to date in this
+ * transaction is visible. We copy in both cases so that postquel_end
+ * can unconditionally do FreeSnapshot.
+ */
+ if (fcache->readonly_func)
+ snapshot = CopySnapshot(ActiveSnapshot);
+ else
+ {
+ CommandCounterIncrement();
+ snapshot = CopySnapshot(GetTransactionSnapshot());
+ }
+
es->qd = CreateQueryDesc(es->query, es->plan,
+ snapshot, InvalidSnapshot,
None_Receiver,
fcache->paramLI, false);
+ /* We assume we don't need to set up ActiveSnapshot for ExecutorStart */
+
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
AfterTriggerBeginQuery();
- ExecutorStart(es->qd, false, false);
+ ExecutorStart(es->qd, false);
}
es->status = F_EXEC_RUN;
@@ -285,46 +329,82 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
static TupleTableSlot *
postquel_getnext(execution_state *es)
{
+ TupleTableSlot *result;
+ Snapshot saveActiveSnapshot;
long count;
- if (es->qd->operation == CMD_UTILITY)
+ /* Make our snapshot the active one for any called functions */
+ saveActiveSnapshot = ActiveSnapshot;
+ PG_TRY();
{
- /* Can't handle starting or committing a transaction */
- if (IsA(es->qd->parsetree->utilityStmt, TransactionStmt))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot begin/end transactions in SQL functions")));
- ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
- es->qd->dest, NULL);
- return NULL;
+ ActiveSnapshot = es->qd->snapshot;
+
+ if (es->qd->operation == CMD_UTILITY)
+ {
+ ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params,
+ es->qd->dest, NULL);
+ result = NULL;
+ }
+ else
+ {
+ /*
+ * If it's the function's last command, and it's a SELECT, fetch
+ * one row at a time so we can return the results. Otherwise just
+ * run it to completion. (If we run to completion then
+ * ExecutorRun is guaranteed to return NULL.)
+ */
+ if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
+ count = 1L;
+ else
+ count = 0L;
+
+ result = ExecutorRun(es->qd, ForwardScanDirection, count);
+ }
}
+ PG_CATCH();
+ {
+ /* Restore global vars and propagate error */
+ ActiveSnapshot = saveActiveSnapshot;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
- /*
- * If it's the function's last command, and it's a SELECT, fetch one
- * row at a time so we can return the results. Otherwise just run it
- * to completion.
- */
- if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
- count = 1L;
- else
- count = 0L;
+ ActiveSnapshot = saveActiveSnapshot;
- return ExecutorRun(es->qd, ForwardScanDirection, count);
+ return result;
}
static void
-postquel_end(execution_state *es)
+postquel_end(execution_state *es, SQLFunctionCachePtr fcache)
{
+ Snapshot saveActiveSnapshot;
+
/* mark status done to ensure we don't do ExecutorEnd twice */
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY)
{
- ExecutorEnd(es->qd);
- AfterTriggerEndQuery();
+ /* Make our snapshot the active one for any called functions */
+ saveActiveSnapshot = ActiveSnapshot;
+ PG_TRY();
+ {
+ ActiveSnapshot = es->qd->snapshot;
+
+ ExecutorEnd(es->qd);
+ AfterTriggerEndQuery();
+ }
+ PG_CATCH();
+ {
+ /* Restore global vars and propagate error */
+ ActiveSnapshot = saveActiveSnapshot;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ ActiveSnapshot = saveActiveSnapshot;
}
+ FreeSnapshot(es->qd->snapshot);
FreeQueryDesc(es->qd);
es->qd = NULL;
}
@@ -368,6 +448,8 @@ postquel_execute(execution_state *es,
SQLFunctionCachePtr fcache)
{
TupleTableSlot *slot;
+ HeapTuple tup;
+ TupleDesc tupDesc;
Datum value;
if (es->status == F_EXEC_START)
@@ -377,101 +459,92 @@ postquel_execute(execution_state *es,
if (TupIsNull(slot))
{
- postquel_end(es);
- fcinfo->isnull = true;
-
/*
- * If this isn't the last command for the function we have to
- * increment the command counter so that subsequent commands can
- * see changes made by previous ones.
+ * We fall out here for all cases except where we have obtained
+ * a row from a function's final SELECT.
*/
- if (!LAST_POSTQUEL_COMMAND(es))
- CommandCounterIncrement();
+ postquel_end(es, fcache);
+ fcinfo->isnull = true;
return (Datum) NULL;
}
- if (LAST_POSTQUEL_COMMAND(es))
+ /*
+ * If we got a row from a command within the function it has to be
+ * the final command. All others shouldn't be returning anything.
+ */
+ Assert(LAST_POSTQUEL_COMMAND(es));
+
+ /*
+ * Set up to return the function value.
+ */
+ tup = slot->val;
+ tupDesc = slot->ttc_tupleDescriptor;
+
+ if (fcache->returnsTuple)
{
/*
- * Set up to return the function value.
+ * We are returning the whole tuple, so copy it into current
+ * execution context and make sure it is a valid Datum.
+ *
+ * XXX do we need to remove junk attrs from the result tuple?
+ * Probably OK to leave them, as long as they are at the end.
*/
- HeapTuple tup = slot->val;
- TupleDesc tupDesc = slot->ttc_tupleDescriptor;
+ HeapTupleHeader dtup;
+ Oid dtuptype;
+ int32 dtuptypmod;
- if (fcache->returnsTuple)
- {
- /*
- * We are returning the whole tuple, so copy it into current
- * execution context and make sure it is a valid Datum.
- *
- * XXX do we need to remove junk attrs from the result tuple?
- * Probably OK to leave them, as long as they are at the end.
- */
- HeapTupleHeader dtup;
- Oid dtuptype;
- int32 dtuptypmod;
-
- dtup = (HeapTupleHeader) palloc(tup->t_len);
- memcpy((char *) dtup, (char *) tup->t_data, tup->t_len);
+ dtup = (HeapTupleHeader) palloc(tup->t_len);
+ memcpy((char *) dtup, (char *) tup->t_data, tup->t_len);
- /*
- * Use the declared return type if it's not RECORD; else take
- * the type from the computed result, making sure a typmod has
- * been assigned.
- */
- if (fcache->rettype != RECORDOID)
- {
- /* function has a named composite return type */
- dtuptype = fcache->rettype;
- dtuptypmod = -1;
- }
- else
- {
- /* function is declared to return RECORD */
- if (tupDesc->tdtypeid == RECORDOID &&
- tupDesc->tdtypmod < 0)
- assign_record_type_typmod(tupDesc);
- dtuptype = tupDesc->tdtypeid;
- dtuptypmod = tupDesc->tdtypmod;
- }
-
- HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
- HeapTupleHeaderSetTypeId(dtup, dtuptype);
- HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
-
- value = PointerGetDatum(dtup);
- fcinfo->isnull = false;
+ /*
+ * Use the declared return type if it's not RECORD; else take
+ * the type from the computed result, making sure a typmod has
+ * been assigned.
+ */
+ if (fcache->rettype != RECORDOID)
+ {
+ /* function has a named composite return type */
+ dtuptype = fcache->rettype;
+ dtuptypmod = -1;
}
else
{
- /*
- * Returning a scalar, which we have to extract from the first
- * column of the SELECT result, and then copy into current
- * execution context if needed.
- */
- value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
-
- if (!fcinfo->isnull)
- value = datumCopy(value, fcache->typbyval, fcache->typlen);
+ /* function is declared to return RECORD */
+ if (tupDesc->tdtypeid == RECORDOID &&
+ tupDesc->tdtypmod < 0)
+ assign_record_type_typmod(tupDesc);
+ dtuptype = tupDesc->tdtypeid;
+ dtuptypmod = tupDesc->tdtypmod;
}
+ HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
+ HeapTupleHeaderSetTypeId(dtup, dtuptype);
+ HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
+
+ value = PointerGetDatum(dtup);
+ fcinfo->isnull = false;
+ }
+ else
+ {
/*
- * If this is a single valued function we have to end the function
- * execution now.
+ * Returning a scalar, which we have to extract from the first
+ * column of the SELECT result, and then copy into current
+ * execution context if needed.
*/
- if (!fcinfo->flinfo->fn_retset)
- postquel_end(es);
+ value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
- return value;
+ if (!fcinfo->isnull)
+ value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
/*
- * If this isn't the last command for the function, we don't return
- * any results, but we have to increment the command counter so that
- * subsequent commands can see changes made by previous ones.
+ * If this is a single valued function we have to end the function
+ * execution now.
*/
- CommandCounterIncrement();
- return (Datum) NULL;
+ if (!fcinfo->flinfo->fn_retset)
+ postquel_end(es, fcache);
+
+ return value;
}
Datum
@@ -726,7 +799,7 @@ ShutdownSQLFunction(Datum arg)
{
/* Shut down anything still running */
if (es->status == F_EXEC_RUN)
- postquel_end(es);
+ postquel_end(es, fcache);
/* Reset states to START in case we're called again */
es->status = F_EXEC_START;
es = es->next;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 636eed31ee..6650da9b62 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.126 2004/09/10 18:39:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.127 2004/09/13 20:06:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -34,13 +34,14 @@ static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1;
static int _SPI_curid = -1;
-static int _SPI_execute(const char *src, int tcount, _SPI_plan *plan);
-static int _SPI_pquery(QueryDesc *queryDesc, bool runit,
- bool useCurrentSnapshot, int tcount);
+static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
static int _SPI_execute_plan(_SPI_plan *plan,
- Datum *Values, const char *Nulls,
- bool useCurrentSnapshot, int tcount);
+ Datum *Values, const char *Nulls,
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool read_only, int tcount);
+
+static int _SPI_pquery(QueryDesc *queryDesc, int tcount);
static void _SPI_error_callback(void *arg);
@@ -252,9 +253,11 @@ SPI_pop(void)
_SPI_curid--;
}
+/* Parse, plan, and execute a querystring */
int
-SPI_exec(const char *src, int tcount)
+SPI_execute(const char *src, bool read_only, int tcount)
{
+ _SPI_plan plan;
int res;
if (src == NULL || tcount < 0)
@@ -264,14 +267,32 @@ SPI_exec(const char *src, int tcount)
if (res < 0)
return res;
- res = _SPI_execute(src, tcount, NULL);
+ 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);
return res;
}
+/* Obsolete version of SPI_execute */
int
-SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
+SPI_exec(const char *src, int 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)
{
int res;
@@ -285,21 +306,36 @@ SPI_execp(void *plan, Datum *Values, const char *Nulls, int tcount)
if (res < 0)
return res;
- res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, false, tcount);
+ 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, int tcount)
+{
+ return SPI_execute_plan(plan, Values, Nulls, false, tcount);
+}
+
/*
- * SPI_execp_current -- identical to SPI_execp, except that we expose the
- * Executor option to use a current snapshot instead of the normal
- * QuerySnapshot. This is currently not documented in spi.sgml because
- * it is only intended for use by RI triggers.
+ * 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_execp_current(void *plan, Datum *Values, const char *Nulls,
- bool useCurrentSnapshot, int tcount)
+extern int
+SPI_execute_snapshot(void *plan,
+ Datum *Values, const char *Nulls,
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool read_only, int tcount)
{
int res;
@@ -313,8 +349,10 @@ SPI_execp_current(void *plan, Datum *Values, const char *Nulls,
if (res < 0)
return res;
- res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls,
- useCurrentSnapshot, tcount);
+ res = _SPI_execute_plan((_SPI_plan *) plan,
+ Values, Nulls,
+ snapshot, crosscheck_snapshot,
+ read_only, tcount);
_SPI_end_call(true);
return res;
@@ -341,12 +379,10 @@ SPI_prepare(const char *src, int nargs, Oid *argtypes)
plan.nargs = nargs;
plan.argtypes = argtypes;
- SPI_result = _SPI_execute(src, 0, &plan);
+ _SPI_prepare_plan(src, &plan);
- if (SPI_result >= 0) /* copy plan to procedure context */
- result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
- else
- result = NULL;
+ /* copy plan to procedure context */
+ result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
_SPI_end_call(true);
@@ -756,7 +792,9 @@ SPI_freetuptable(SPITupleTable *tuptable)
* Open a prepared SPI plan as a portal
*/
Portal
-SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
+SPI_cursor_open(const char *name, void *plan,
+ Datum *Values, const char *Nulls,
+ bool read_only)
{
_SPI_plan *spiplan = (_SPI_plan *) plan;
List *qtlist = spiplan->qtlist;
@@ -764,6 +802,7 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
Query *queryTree;
Plan *planTree;
ParamListInfo paramLI;
+ Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
int k;
@@ -785,9 +824,6 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open SELECT INTO query as cursor")));
- /* Increment CommandCounter to see changes made by now */
- CommandCounterIncrement();
-
/* Reset SPI result */
SPI_processed = 0;
SPI_tuptable = NULL;
@@ -867,9 +903,21 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
/*
+ * 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);
+ PortalStart(portal, paramLI, snapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT);
@@ -1143,38 +1191,31 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
*/
/*
- * Plan and optionally execute a querystring.
+ * Parse and plan a querystring.
+ *
+ * At entry, plan->argtypes and plan->nargs must be valid.
*
- * If plan != NULL, just prepare plan trees and save them in *plan;
- * else execute immediately.
+ * Query and plan lists are stored into *plan.
*/
-static int
-_SPI_execute(const char *src, int tcount, _SPI_plan *plan)
+static void
+_SPI_prepare_plan(const char *src, _SPI_plan *plan)
{
List *raw_parsetree_list;
List *query_list_list;
List *plan_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
- int nargs = 0;
- Oid *argtypes = NULL;
- int res = 0;
-
- if (plan)
- {
- nargs = plan->nargs;
- argtypes = plan->argtypes;
- }
+ Oid *argtypes = plan->argtypes;
+ int nargs = plan->nargs;
- /* Increment CommandCounter to see changes made by now */
+ /*
+ * 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();
- /* 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()
*/
@@ -1191,9 +1232,9 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
/*
* Do parse analysis and rule rewrite for each raw parsetree.
*
- * We save the querytrees from each raw parsetree as a separate sublist.
- * This allows _SPI_execute_plan() to know where the boundaries
- * between original queries fall.
+ * We save the querytrees from each raw parsetree as a separate
+ * sublist. This allows _SPI_execute_plan() to know where the
+ * boundaries between original queries fall.
*/
query_list_list = NIL;
plan_list = NIL;
@@ -1202,203 +1243,221 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
{
Node *parsetree = (Node *) lfirst(list_item);
List *query_list;
- ListCell *query_list_item;
query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
- /* 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);
- Plan *planTree;
- QueryDesc *qdesc;
- DestReceiver *dest;
-
- planTree = pg_plan_query(queryTree, NULL);
- plan_list = lappend(plan_list, planTree);
-
- dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
- if (queryTree->commandType == CMD_UTILITY)
- {
- if (IsA(queryTree->utilityStmt, CopyStmt))
- {
- CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
-
- if (stmt->filename == NULL)
- {
- res = SPI_ERROR_COPY;
- goto fail;
- }
- }
- else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
- IsA(queryTree->utilityStmt, ClosePortalStmt) ||
- IsA(queryTree->utilityStmt, FetchStmt))
- {
- res = SPI_ERROR_CURSOR;
- goto fail;
- }
- else if (IsA(queryTree->utilityStmt, TransactionStmt))
- {
- res = SPI_ERROR_TRANSACTION;
- goto fail;
- }
- res = SPI_OK_UTILITY;
- if (plan == NULL)
- {
- ProcessUtility(queryTree->utilityStmt, NULL, dest, NULL);
- CommandCounterIncrement();
- }
- }
- else if (plan == NULL)
- {
- qdesc = CreateQueryDesc(queryTree, planTree, dest,
- NULL, false);
- res = _SPI_pquery(qdesc, true, false,
- queryTree->canSetTag ? tcount : 0);
- if (res < 0)
- goto fail;
- CommandCounterIncrement();
- }
- else
- {
- qdesc = CreateQueryDesc(queryTree, planTree, dest,
- NULL, false);
- res = _SPI_pquery(qdesc, false, false, 0);
- if (res < 0)
- goto fail;
- }
- }
+ plan_list = list_concat(plan_list,
+ pg_plan_queries(query_list, NULL, false));
}
- if (plan)
- {
- plan->qtlist = query_list_list;
- plan->ptlist = plan_list;
- }
-
-fail:
+ plan->qtlist = query_list_list;
+ plan->ptlist = plan_list;
/*
* Pop the error context stack
*/
error_context_stack = spierrcontext.previous;
-
- return res;
}
+/*
+ * 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
+ */
static int
_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
- bool useCurrentSnapshot, int tcount)
+ Snapshot snapshot, Snapshot crosscheck_snapshot,
+ bool read_only, int tcount)
{
- 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;
- int res = 0;
- ParamListInfo paramLI;
-
- /* Increment CommandCounter to see changes made by now */
- CommandCounterIncrement();
+ volatile int res = 0;
+ Snapshot saveActiveSnapshot;
- /* Convert parameters to form wanted by executor */
- if (nargs > 0)
+ /* Be sure to restore ActiveSnapshot on error exit */
+ saveActiveSnapshot = ActiveSnapshot;
+ PG_TRY();
{
- int k;
-
- paramLI = (ParamListInfo)
- palloc0((nargs + 1) * sizeof(ParamListInfoData));
-
- for (k = 0; k < nargs; k++)
+ 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)
{
- paramLI[k].kind = PARAM_NUM;
- paramLI[k].id = k + 1;
- paramLI[k].ptype = plan->argtypes[k];
- paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
- paramLI[k].value = Values[k];
- }
- paramLI[k].kind = PARAM_INVALID;
- }
- else
- paramLI = NULL;
+ int k;
- /* 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()
- */
- spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = (void *) plan->query;
- spierrcontext.previous = error_context_stack;
- error_context_stack = &spierrcontext;
+ paramLI = (ParamListInfo)
+ palloc0((nargs + 1) * sizeof(ParamListInfoData));
- foreach(query_list_list_item, query_list_list)
- {
- List *query_list = lfirst(query_list_list_item);
- ListCell *query_list_item;
+ for (k = 0; k < nargs; k++)
+ {
+ paramLI[k].kind = PARAM_NUM;
+ paramLI[k].id = k + 1;
+ paramLI[k].ptype = plan->argtypes[k];
+ paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
+ paramLI[k].value = Values[k];
+ }
+ paramLI[k].kind = PARAM_INVALID;
+ }
+ else
+ paramLI = NULL;
- /* Reset state for each original parsetree */
- /* (at most one of its querytrees will be marked canSetTag) */
+ /* Reset state (only needed in case string is empty) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
- foreach(query_list_item, query_list)
+ /*
+ * 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)
{
- Query *queryTree = (Query *) lfirst(query_list_item);
- Plan *planTree;
- QueryDesc *qdesc;
- DestReceiver *dest;
+ List *query_list = lfirst(query_list_list_item);
+ ListCell *query_list_item;
- planTree = lfirst(plan_list_item);
- plan_list_item = lnext(plan_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;
- dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
- if (queryTree->commandType == CMD_UTILITY)
- {
- ProcessUtility(queryTree->utilityStmt, paramLI, dest, NULL);
- res = SPI_OK_UTILITY;
- CommandCounterIncrement();
- }
- else
+ foreach(query_list_item, query_list)
{
- qdesc = CreateQueryDesc(queryTree, planTree, dest,
- paramLI, false);
- res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
- queryTree->canSetTag ? tcount : 0);
+ 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);
+
+ if (queryTree->commandType == CMD_UTILITY)
+ {
+ if (IsA(queryTree->utilityStmt, CopyStmt))
+ {
+ CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
+
+ if (stmt->filename == NULL)
+ {
+ res = SPI_ERROR_COPY;
+ goto fail;
+ }
+ }
+ else if (IsA(queryTree->utilityStmt, DeclareCursorStmt) ||
+ IsA(queryTree->utilityStmt, ClosePortalStmt) ||
+ IsA(queryTree->utilityStmt, FetchStmt))
+ {
+ res = SPI_ERROR_CURSOR;
+ goto fail;
+ }
+ else if (IsA(queryTree->utilityStmt, TransactionStmt))
+ {
+ res = SPI_ERROR_TRANSACTION;
+ goto fail;
+ }
+ }
+
+ 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))));
+ /*
+ * If not read-only mode, advance the command counter before
+ * each command.
+ */
+ if (!read_only)
+ CommandCounterIncrement();
+
+ dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None,
+ 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);
+ 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;
+ /* we know that the receiver doesn't need a destroy call */
if (res < 0)
goto fail;
- CommandCounterIncrement();
}
}
- }
fail:
- /*
- * Pop the error context stack
- */
- error_context_stack = spierrcontext.previous;
+ /*
+ * 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;
return res;
}
static int
-_SPI_pquery(QueryDesc *queryDesc, bool runit,
- bool useCurrentSnapshot, int tcount)
+_SPI_pquery(QueryDesc *queryDesc, int tcount)
{
int operation = queryDesc->operation;
int res;
@@ -1427,9 +1486,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
return SPI_ERROR_OPUNKNOWN;
}
- if (!runit) /* plan preparation, don't execute */
- return res;
-
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ResetUsage();
@@ -1437,7 +1493,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
AfterTriggerBeginQuery();
- ExecutorStart(queryDesc, useCurrentSnapshot, false);
+ ExecutorStart(queryDesc, false);
ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
@@ -1467,8 +1523,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
res = SPI_OK_UTILITY;
}
- FreeQueryDesc(queryDesc);
-
#ifdef SPI_EXECUTOR_STATS
if (ShowExecutorStats)
ShowUsage("SPI EXECUTOR STATS");
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 165b46475c..66edf545d9 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/fastpath.c,v 1.75 2004/08/29 05:06:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/fastpath.c,v 1.76 2004/09/13 20:07:05 tgl Exp $
*
* NOTES
* This cruft is the server side of PQfn.
@@ -334,11 +334,6 @@ HandleFunctionRequest(StringInfo msgBuf)
get_func_name(fid));
/*
- * Set up a query snapshot in case function needs one.
- */
- SetQuerySnapshot();
-
- /*
* Prepare function call info block and insert arguments.
*/
MemSet(&fcinfo, 0, sizeof(fcinfo));
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 50364bd79d..85fcc49c83 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.431 2004/09/10 18:39:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.432 2004/09/13 20:07:05 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -700,7 +700,7 @@ pg_plan_queries(List *querytrees, ParamListInfo boundParams,
{
if (needSnapshot)
{
- SetQuerySnapshot();
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
needSnapshot = false;
}
plan = pg_plan_query(query, boundParams);
@@ -883,7 +883,7 @@ exec_simple_query(const char *query_string)
/*
* Start the portal. No parameters here.
*/
- PortalStart(portal, NULL);
+ PortalStart(portal, NULL, InvalidSnapshot);
/*
* Select the appropriate output format: text unless we are doing
@@ -1539,7 +1539,7 @@ exec_bind_message(StringInfo input_message)
pstmt->plan_list,
pstmt->context);
- PortalStart(portal, params);
+ PortalStart(portal, params, InvalidSnapshot);
/*
* Apply the result format requests to the portal.
@@ -3027,6 +3027,9 @@ PostgresMain(int argc, char *argv[], const char *username)
/* switch back to message context */
MemoryContextSwitchTo(MessageContext);
+ /* set snapshot in case function needs one */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
+
if (HandleFunctionRequest(&input_message) == EOF)
{
/* lost frontend connection during F message input */
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index fca98fc0fa..5a1c8b4867 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.86 2004/09/10 18:40:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.87 2004/09/13 20:07:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -32,6 +32,11 @@
Portal ActivePortal = NULL;
+static void ProcessQuery(Query *parsetree,
+ Plan *plan,
+ ParamListInfo params,
+ DestReceiver *dest,
+ char *completionTag);
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
DestReceiver *dest);
static long PortalRunSelect(Portal portal, bool forward, long count,
@@ -54,6 +59,8 @@ static void DoPortalRewind(Portal portal);
QueryDesc *
CreateQueryDesc(Query *parsetree,
Plan *plantree,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument)
@@ -63,6 +70,8 @@ CreateQueryDesc(Query *parsetree,
qd->operation = parsetree->commandType; /* operation */
qd->parsetree = parsetree; /* parse tree */
qd->plantree = plantree; /* plan */
+ qd->snapshot = snapshot; /* snapshot */
+ qd->crosscheck_snapshot = crosscheck_snapshot; /* RI check snapshot */
qd->dest = dest; /* output dest */
qd->params = params; /* parameter values passed into query */
qd->doInstrument = doInstrument; /* instrumentation wanted? */
@@ -90,7 +99,7 @@ FreeQueryDesc(QueryDesc *qdesc)
/*
* ProcessQuery
- * Execute a single query
+ * Execute a single plannable query within a PORTAL_MULTI_QUERY portal
*
* parsetree: the query tree
* plan: the plan tree for the query
@@ -104,7 +113,7 @@ FreeQueryDesc(QueryDesc *qdesc)
* Must be called in a memory context that will be reset or deleted on
* error; otherwise the executor's memory usage will be leaked.
*/
-void
+static void
ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
@@ -114,6 +123,9 @@ ProcessQuery(Query *parsetree,
int operation = parsetree->commandType;
QueryDesc *queryDesc;
+ ereport(DEBUG3,
+ (errmsg_internal("ProcessQuery")));
+
/*
* Check for special-case destinations
*/
@@ -133,9 +145,17 @@ ProcessQuery(Query *parsetree,
}
/*
+ * Must always set snapshot for plannable queries. Note we assume
+ * that caller will take care of restoring ActiveSnapshot on exit/error.
+ */
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
+
+ /*
* Create the QueryDesc object
*/
- queryDesc = CreateQueryDesc(parsetree, plan, dest, params, false);
+ queryDesc = CreateQueryDesc(parsetree, plan,
+ ActiveSnapshot, InvalidSnapshot,
+ dest, params, false);
/*
* Set up to collect AFTER triggers
@@ -145,7 +165,7 @@ ProcessQuery(Query *parsetree,
/*
* Call ExecStart to prepare the plan for execution
*/
- ExecutorStart(queryDesc, false, false);
+ ExecutorStart(queryDesc, false);
/*
* Run the plan to completion.
@@ -195,6 +215,9 @@ ProcessQuery(Query *parsetree,
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc);
+
+ FreeSnapshot(ActiveSnapshot);
+ ActiveSnapshot = NULL;
}
/*
@@ -238,13 +261,19 @@ ChoosePortalStrategy(List *parseTrees)
* the query, they must be passed in here (caller is responsible for
* giving them appropriate lifetime).
*
+ * The caller can optionally pass a snapshot to be used; pass InvalidSnapshot
+ * for the normal behavior of setting a new snapshot. This parameter is
+ * presently ignored for non-PORTAL_ONE_SELECT portals (it's only intended
+ * to be used for cursors).
+ *
* On return, portal is ready to accept PortalRun() calls, and the result
* tupdesc (if any) is known.
*/
void
-PortalStart(Portal portal, ParamListInfo params)
+PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
{
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext oldContext;
@@ -259,11 +288,13 @@ PortalStart(Portal portal, ParamListInfo params)
* QueryContext?)
*/
saveActivePortal = ActivePortal;
+ saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
PG_TRY();
{
ActivePortal = portal;
+ ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
@@ -285,9 +316,13 @@ PortalStart(Portal portal, ParamListInfo params)
case PORTAL_ONE_SELECT:
/*
- * Must set query snapshot before starting executor.
+ * Must set snapshot before starting executor. Be sure to
+ * copy it into the portal's context.
*/
- SetQuerySnapshot();
+ if (snapshot)
+ ActiveSnapshot = CopySnapshot(snapshot);
+ else
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Create QueryDesc in portal's context; for the moment,
@@ -295,6 +330,8 @@ PortalStart(Portal portal, ParamListInfo params)
*/
queryDesc = CreateQueryDesc((Query *) linitial(portal->parseTrees),
(Plan *) linitial(portal->planTrees),
+ ActiveSnapshot,
+ InvalidSnapshot,
None_Receiver,
params,
false);
@@ -309,7 +346,7 @@ PortalStart(Portal portal, ParamListInfo params)
/*
* Call ExecStart to prepare the plan for execution
*/
- ExecutorStart(queryDesc, false, false);
+ ExecutorStart(queryDesc, false);
/*
* This tells PortalCleanup to shut down the executor
@@ -333,8 +370,8 @@ PortalStart(Portal portal, ParamListInfo params)
case PORTAL_UTIL_SELECT:
/*
- * We don't set query snapshot here, because
- * PortalRunUtility will take care of it.
+ * We don't set snapshot here, because
+ * PortalRunUtility will take care of it if needed.
*/
portal->tupDesc =
UtilityTupleDescriptor(((Query *) linitial(portal->parseTrees))->utilityStmt);
@@ -361,6 +398,7 @@ PortalStart(Portal portal, ParamListInfo params)
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
@@ -371,6 +409,7 @@ PortalStart(Portal portal, ParamListInfo params)
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
@@ -453,6 +492,7 @@ PortalRun(Portal portal, long count,
{
bool result;
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
@@ -485,12 +525,14 @@ PortalRun(Portal portal, long count,
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
+ saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
+ ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
@@ -579,6 +621,7 @@ PortalRun(Portal portal, long count,
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
@@ -590,6 +633,7 @@ PortalRun(Portal portal, long count,
MemoryContextSwitchTo(oldContext);
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
@@ -670,6 +714,7 @@ PortalRunSelect(Portal portal,
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
+ ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
@@ -711,6 +756,7 @@ PortalRunSelect(Portal portal,
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
+ ActiveSnapshot = queryDesc->snapshot;
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
@@ -834,6 +880,9 @@ PortalRunUtility(Portal portal, Query *query,
* the database --- if, say, it has to update an index with
* expressions that invoke user-defined functions, then it had better
* have a snapshot.
+ *
+ * Note we assume that caller will take care of restoring ActiveSnapshot
+ * on exit/error.
*/
if (!(IsA(utilityStmt, TransactionStmt) ||
IsA(utilityStmt, LockStmt) ||
@@ -847,7 +896,9 @@ PortalRunUtility(Portal portal, Query *query,
IsA(utilityStmt, NotifyStmt) ||
IsA(utilityStmt, UnlistenStmt) ||
IsA(utilityStmt, CheckPointStmt)))
- SetQuerySnapshot();
+ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
+ else
+ ActiveSnapshot = NULL;
if (query->canSetTag)
{
@@ -864,6 +915,10 @@ PortalRunUtility(Portal portal, Query *query,
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
+ if (ActiveSnapshot)
+ FreeSnapshot(ActiveSnapshot);
+ ActiveSnapshot = NULL;
}
/*
@@ -924,15 +979,6 @@ PortalRunMulti(Portal portal,
/*
* process a plannable query.
*/
- ereport(DEBUG3,
- (errmsg_internal("ProcessQuery")));
-
- /* Must always set snapshot for plannable queries */
- SetQuerySnapshot();
-
- /*
- * execute the plan
- */
if (log_executor_stats)
ResetUsage();
@@ -1005,6 +1051,7 @@ PortalRunFetch(Portal portal,
{
long result;
Portal saveActivePortal;
+ Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
@@ -1025,12 +1072,14 @@ PortalRunFetch(Portal portal,
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
+ saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
+ ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
@@ -1056,6 +1105,7 @@ PortalRunFetch(Portal portal,
/* Restore global vars and propagate error */
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
@@ -1070,6 +1120,7 @@ PortalRunFetch(Portal portal,
portal->status = PORTAL_READY;
ActivePortal = saveActivePortal;
+ ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6aec17bf9a..d44e986e3c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.229 2004/09/10 18:40:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.230 2004/09/13 20:07:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -222,6 +222,46 @@ CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
}
+/*
+ * QueryIsReadOnly: is an analyzed/rewritten query read-only?
+ *
+ * This is a much stricter test than we apply for XactReadOnly mode;
+ * the query must be *in truth* read-only, because the caller wishes
+ * not to do CommandCounterIncrement for it.
+ */
+bool
+QueryIsReadOnly(Query *parsetree)
+{
+ switch (parsetree->commandType)
+ {
+ case CMD_SELECT:
+ if (parsetree->into != NULL)
+ return false; /* SELECT INTO */
+ else if (parsetree->rowMarks != NIL)
+ return false; /* SELECT FOR UPDATE */
+ else
+ return true;
+ case CMD_UPDATE:
+ case CMD_INSERT:
+ case CMD_DELETE:
+ return false;
+ case CMD_UTILITY:
+ /* For now, treat all utility commands as read/write */
+ return false;
+ default:
+ elog(WARNING, "unrecognized commandType: %d",
+ (int) parsetree->commandType);
+ break;
+ }
+ return false;
+}
+
+/*
+ * check_xact_readonly: is a utility command read-only?
+ *
+ * Here we use the loose rules of XactReadOnly mode: no permanent effects
+ * on the database are allowed.
+ */
static void
check_xact_readonly(Node *parsetree)
{
@@ -299,8 +339,7 @@ check_xact_readonly(Node *parsetree)
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
*
- * completionTag is only set nonempty if we want to return a nondefault
- * status (currently, only used for MOVE/FETCH).
+ * completionTag is only set nonempty if we want to return a nondefault status.
*
* completionTag may be NULL if caller doesn't want a status string.
*/
@@ -1586,3 +1625,51 @@ CreateCommandTag(Node *parsetree)
return tag;
}
+
+/*
+ * CreateQueryTag
+ * utility to get a string representation of a Query operation.
+ *
+ * This is exactly like CreateCommandTag, except it works on a Query
+ * that has already been through parse analysis (and possibly further).
+ */
+const char *
+CreateQueryTag(Query *parsetree)
+{
+ const char *tag;
+
+ switch (parsetree->commandType)
+ {
+ case CMD_SELECT:
+ /*
+ * We take a little extra care here so that the result will
+ * be useful for complaints about read-only statements
+ */
+ if (parsetree->into != NULL)
+ tag = "SELECT INTO";
+ else if (parsetree->rowMarks != NIL)
+ tag = "SELECT FOR UPDATE";
+ else
+ tag = "SELECT";
+ break;
+ case CMD_UPDATE:
+ tag = "UPDATE";
+ break;
+ case CMD_INSERT:
+ tag = "INSERT";
+ break;
+ case CMD_DELETE:
+ tag = "DELETE";
+ break;
+ case CMD_UTILITY:
+ tag = CreateCommandTag(parsetree->utilityStmt);
+ break;
+ default:
+ elog(WARNING, "unrecognized commandType: %d",
+ (int) parsetree->commandType);
+ tag = "???";
+ break;
+ }
+
+ return tag;
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 9c32d57c11..20ad56c31f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -17,7 +17,7 @@
*
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.72 2004/09/10 18:40:04 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.73 2004/09/13 20:07:13 tgl Exp $
*
* ----------
*/
@@ -2698,16 +2698,20 @@ RI_Initial_Check(FkConstraint *fkconstraint, Relation rel, Relation pkrel)
elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
/*
- * Run the plan. For safety we force a current query snapshot to be
- * used. (In serializable mode, this arguably violates
- * serializability, but we really haven't got much choice.) We need
- * at most one tuple returned, so pass limit = 1.
+ * Run the plan. For safety we force a current snapshot to be used.
+ * (In serializable mode, this arguably violates serializability, but we
+ * really haven't got much choice.) We need at most one tuple returned,
+ * so pass limit = 1.
*/
- spi_result = SPI_execp_current(qplan, NULL, NULL, true, 1);
+ spi_result = SPI_execute_snapshot(qplan,
+ NULL, NULL,
+ CopySnapshot(GetLatestSnapshot()),
+ InvalidSnapshot,
+ true, 1);
/* Check result */
if (spi_result != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp_current returned %d", spi_result);
+ elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
/* Did we find a tuple violating the constraint? */
if (SPI_processed > 0)
@@ -3043,7 +3047,8 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
Relation query_rel,
source_rel;
int key_idx;
- bool useCurrentSnapshot;
+ Snapshot test_snapshot;
+ Snapshot crosscheck_snapshot;
int limit;
int spi_result;
AclId save_uid;
@@ -3094,21 +3099,26 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
}
/*
- * In READ COMMITTED mode, we just need to make sure the regular query
- * snapshot is up-to-date, and we will see all rows that could be
- * interesting. In SERIALIZABLE mode, we can't update the regular
- * query snapshot. If the caller passes detectNewRows == false then
- * it's okay to do the query with the transaction snapshot; otherwise
- * we tell the executor to force a current snapshot (and error out if
- * it finds any rows under current snapshot that wouldn't be visible
- * per the transaction snapshot).
+ * In READ COMMITTED mode, we just need to use an up-to-date regular
+ * snapshot, and we will see all rows that could be interesting.
+ * But in SERIALIZABLE mode, we can't change the transaction snapshot.
+ * If the caller passes detectNewRows == false then it's okay to do the
+ * query with the transaction snapshot; otherwise we use a current
+ * snapshot, and tell the executor to error out if it finds any rows under
+ * the current snapshot that wouldn't be visible per the transaction
+ * snapshot.
*/
- if (IsXactIsoLevelSerializable)
- useCurrentSnapshot = detectNewRows;
+ if (IsXactIsoLevelSerializable && detectNewRows)
+ {
+ CommandCounterIncrement(); /* be sure all my own work is visible */
+ test_snapshot = CopySnapshot(GetLatestSnapshot());
+ crosscheck_snapshot = CopySnapshot(GetTransactionSnapshot());
+ }
else
{
- SetQuerySnapshot();
- useCurrentSnapshot = false;
+ /* the default SPI behavior is okay */
+ test_snapshot = InvalidSnapshot;
+ crosscheck_snapshot = InvalidSnapshot;
}
/*
@@ -3124,15 +3134,17 @@ ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
SetUserId(RelationGetForm(query_rel)->relowner);
/* Finally we can run the query. */
- spi_result = SPI_execp_current(qplan, vals, nulls,
- useCurrentSnapshot, limit);
+ spi_result = SPI_execute_snapshot(qplan,
+ vals, nulls,
+ test_snapshot, crosscheck_snapshot,
+ false, limit);
/* Restore UID */
SetUserId(save_uid);
/* Check result */
if (spi_result < 0)
- elog(ERROR, "SPI_execp_current returned %d", spi_result);
+ elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
if (expect_OK >= 0 && spi_result != expect_OK)
ri_ReportViolation(qkey, constrname ? constrname : "",
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af859222c3..89a9e2451d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
* back to source text
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.180 2004/09/01 23:58:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.181 2004/09/13 20:07:13 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -290,7 +290,7 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
- spirc = SPI_execp(plan_getrulebyoid, args, nulls, 1);
+ spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 1);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
if (SPI_processed != 1)
@@ -425,7 +425,7 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
args[1] = PointerGetDatum(ViewSelectRuleName);
nulls[0] = ' ';
nulls[1] = ' ';
- spirc = SPI_execp(plan_getviewrule, args, nulls, 2);
+ spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 2);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
if (SPI_processed != 1)
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index ffdc4b9e6d..5df7beaabd 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -16,7 +16,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.77 2004/08/29 05:06:52 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.78 2004/09/13 20:07:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -28,18 +28,24 @@
#include "utils/tqual.h"
/*
- * The SnapshotData structs are static to simplify memory allocation
+ * These SnapshotData structs are static to simplify memory allocation
* (see the hack in GetSnapshotData to avoid repeated malloc/free).
*/
-static SnapshotData QuerySnapshotData;
-static SnapshotData SerializableSnapshotData;
-static SnapshotData CurrentSnapshotData;
static SnapshotData SnapshotDirtyData;
+static SnapshotData SerializableSnapshotData;
+static SnapshotData LatestSnapshotData;
/* Externally visible pointers to valid snapshots: */
-Snapshot QuerySnapshot = NULL;
-Snapshot SerializableSnapshot = NULL;
Snapshot SnapshotDirty = &SnapshotDirtyData;
+Snapshot SerializableSnapshot = NULL;
+Snapshot LatestSnapshot = NULL;
+
+/*
+ * This pointer is not maintained by this module, but it's convenient
+ * to declare it here anyway. Callers typically assign a copy of
+ * GetTransactionSnapshot's result to ActiveSnapshot.
+ */
+Snapshot ActiveSnapshot = NULL;
/* These are updated by GetSnapshotData: */
TransactionId RecentXmin = InvalidTransactionId;
@@ -1028,101 +1034,94 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
/*
- * SetQuerySnapshot
- * Initialize query snapshot for a new query
+ * GetTransactionSnapshot
+ * Get the appropriate snapshot for a new query in a transaction.
*
* The SerializableSnapshot is the first one taken in a transaction.
* In serializable mode we just use that one throughout the transaction.
- * In read-committed mode, we take a new snapshot at the start of each query.
+ * In read-committed mode, we take a new snapshot each time we are called.
+ *
+ * Note that the return value points at static storage that will be modified
+ * by future calls and by CommandCounterIncrement(). Callers should copy
+ * the result with CopySnapshot() if it is to be used very long.
*/
-void
-SetQuerySnapshot(void)
+Snapshot
+GetTransactionSnapshot(void)
{
- /* 1st call in xaction? */
+ /* First call in transaction? */
if (SerializableSnapshot == NULL)
{
SerializableSnapshot = GetSnapshotData(&SerializableSnapshotData, true);
- QuerySnapshot = SerializableSnapshot;
- Assert(QuerySnapshot != NULL);
- return;
+ return SerializableSnapshot;
}
if (IsXactIsoLevelSerializable)
- QuerySnapshot = SerializableSnapshot;
- else
- QuerySnapshot = GetSnapshotData(&QuerySnapshotData, false);
+ return SerializableSnapshot;
- Assert(QuerySnapshot != NULL);
+ LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
+
+ return LatestSnapshot;
}
/*
- * CopyQuerySnapshot
- * Copy the current query snapshot.
- *
- * Copying the snapshot is done so that a query is guaranteed to use a
- * consistent snapshot for its entire execution life, even if the command
- * counter is incremented or SetQuerySnapshot() is called while it runs
- * (as could easily happen, due to triggers etc. executing queries).
- *
- * The copy is palloc'd in the current memory context.
+ * GetLatestSnapshot
+ * Get a snapshot that is up-to-date as of the current instant,
+ * even if we are executing in SERIALIZABLE mode.
*/
Snapshot
-CopyQuerySnapshot(void)
+GetLatestSnapshot(void)
{
- Snapshot snapshot;
-
- if (QuerySnapshot == NULL) /* should be set beforehand */
+ /* Should not be first call in transaction */
+ if (SerializableSnapshot == NULL)
elog(ERROR, "no snapshot has been set");
- snapshot = (Snapshot) palloc(sizeof(SnapshotData));
- memcpy(snapshot, QuerySnapshot, sizeof(SnapshotData));
- if (snapshot->xcnt > 0)
- {
- snapshot->xip = (TransactionId *)
- palloc(snapshot->xcnt * sizeof(TransactionId));
- memcpy(snapshot->xip, QuerySnapshot->xip,
- snapshot->xcnt * sizeof(TransactionId));
- }
- else
- snapshot->xip = NULL;
+ LatestSnapshot = GetSnapshotData(&LatestSnapshotData, false);
- return snapshot;
+ return LatestSnapshot;
}
/*
- * CopyCurrentSnapshot
- * Make a snapshot that is up-to-date as of the current instant,
- * and return a copy.
+ * CopySnapshot
+ * Copy the given snapshot.
*
* The copy is palloc'd in the current memory context.
+ *
+ * Note that this will not work on "special" snapshots.
*/
Snapshot
-CopyCurrentSnapshot(void)
+CopySnapshot(Snapshot snapshot)
{
- Snapshot currentSnapshot;
- Snapshot snapshot;
-
- if (QuerySnapshot == NULL) /* should not be first call in xact */
- elog(ERROR, "no snapshot has been set");
-
- /* Update the static struct */
- currentSnapshot = GetSnapshotData(&CurrentSnapshotData, false);
- currentSnapshot->curcid = GetCurrentCommandId();
+ Snapshot newsnap;
- /* Make a copy */
- snapshot = (Snapshot) palloc(sizeof(SnapshotData));
- memcpy(snapshot, currentSnapshot, sizeof(SnapshotData));
+ /* We allocate any XID array needed in the same palloc block. */
+ newsnap = (Snapshot) palloc(sizeof(SnapshotData) +
+ snapshot->xcnt * sizeof(TransactionId));
+ memcpy(newsnap, snapshot, sizeof(SnapshotData));
if (snapshot->xcnt > 0)
{
- snapshot->xip = (TransactionId *)
- palloc(snapshot->xcnt * sizeof(TransactionId));
- memcpy(snapshot->xip, currentSnapshot->xip,
+ newsnap->xip = (TransactionId *) (newsnap + 1);
+ memcpy(newsnap->xip, snapshot->xip,
snapshot->xcnt * sizeof(TransactionId));
}
else
- snapshot->xip = NULL;
+ newsnap->xip = NULL;
- return snapshot;
+ return newsnap;
+}
+
+/*
+ * FreeSnapshot
+ * Free a snapshot previously copied with CopySnapshot.
+ *
+ * This is currently identical to pfree, but is provided for cleanliness.
+ *
+ * Do *not* apply this to the results of GetTransactionSnapshot or
+ * GetLatestSnapshot.
+ */
+void
+FreeSnapshot(Snapshot snapshot)
+{
+ pfree(snapshot);
}
/*
@@ -1133,10 +1132,11 @@ void
FreeXactSnapshot(void)
{
/*
- * We do not free the xip arrays for the snapshot structs; they will
- * be reused soon. So this is now just a state change to prevent
+ * We do not free the xip arrays for the static snapshot structs; they
+ * will be reused soon. So this is now just a state change to prevent
* outside callers from accessing the snapshots.
*/
- QuerySnapshot = NULL;
SerializableSnapshot = NULL;
+ LatestSnapshot = NULL;
+ ActiveSnapshot = NULL; /* just for cleanliness */
}