aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
-rw-r--r--src/include/executor/execdesc.h6
-rw-r--r--src/include/executor/executor.h5
-rw-r--r--src/include/executor/spi.h18
-rw-r--r--src/include/tcop/pquery.h11
-rw-r--r--src/include/tcop/utility.h6
-rw-r--r--src/include/utils/tqual.h17
-rw-r--r--src/pl/plperl/plperl.c153
-rw-r--r--src/pl/plperl/spi_internal.c85
-rw-r--r--src/pl/plperl/spi_internal.h1
-rw-r--r--src/pl/plpgsql/src/pl_comp.c5
-rw-r--r--src/pl/plpgsql/src/pl_exec.c52
-rw-r--r--src/pl/plpgsql/src/plpgsql.h5
-rw-r--r--src/pl/plpython/plpython.c50
-rw-r--r--src/pl/tcl/pltcl.c110
-rw-r--r--src/test/regress/expected/transactions.out78
-rw-r--r--src/test/regress/sql/transactions.sql43
34 files changed, 1209 insertions, 709 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 47c501c393e..ffcc324eb3e 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 0bce21ffb96..6f06063b9ef 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 b25c8eee98c..9c49001b27e 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 9b7cf1ae491..f51b991b4d0 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 6e550e67c68..bdbf8708b13 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 390e20aa2e9..98ffe4ae47a 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 032fe4acbcd..2a6db32788b 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 8ace2777d43..ec39789f097 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 b009e73e105..ea9dce019b1 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 3611c85a5fc..1db5a4339ff 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 636eed31eee..6650da9b626 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 165b46475cf..66edf545d9c 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 50364bd79d3..85fcc49c839 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 fca98fc0fa5..5a1c8b4867b 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 6aec17bf9a5..d44e986e3c1 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 9c32d57c111..20ad56c31f1 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 af859222c36..89a9e2451dd 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 ffdc4b9e6d9..5df7beaabdd 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 */
}
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index 131fa727098..c22266a4c41 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/executor/execdesc.h,v 1.28 2004/08/29 04:13:06 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/execdesc.h,v 1.29 2004/09/13 20:07:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -33,6 +33,8 @@ typedef struct QueryDesc
CmdType operation; /* CMD_SELECT, CMD_UPDATE, etc. */
Query *parsetree; /* rewritten parsetree */
Plan *plantree; /* planner's output */
+ Snapshot snapshot; /* snapshot to use for query */
+ Snapshot crosscheck_snapshot; /* crosscheck for RI update/delete */
DestReceiver *dest; /* the destination for tuple output */
ParamListInfo params; /* param values being passed in */
bool doInstrument; /* TRUE requests runtime instrumentation */
@@ -45,6 +47,8 @@ typedef struct QueryDesc
/* in pquery.c */
extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
DestReceiver *dest,
ParamListInfo params,
bool doInstrument);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 81b53d272c5..7f894f26d8b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.112 2004/08/29 05:06:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.113 2004/09/13 20:07:52 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -95,8 +95,7 @@ extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot);
/*
* prototypes from functions in execMain.c
*/
-extern void ExecutorStart(QueryDesc *queryDesc, bool useCurrentSnapshot,
- bool explainOnly);
+extern void ExecutorStart(QueryDesc *queryDesc, bool explainOnly);
extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
ScanDirection direction, long count);
extern void ExecutorEnd(QueryDesc *queryDesc);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index ef10789c614..d58f66ad314 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -2,7 +2,7 @@
*
* spi.h
*
- * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.47 2004/08/29 05:06:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.48 2004/09/13 20:07:53 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -81,11 +81,17 @@ extern int SPI_connect(void);
extern int SPI_finish(void);
extern void SPI_push(void);
extern void SPI_pop(void);
+extern int SPI_execute(const char *src, bool read_only, int tcount);
+extern int SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
+ bool read_only, int tcount);
extern int SPI_exec(const char *src, int tcount);
-extern int SPI_execp(void *plan, Datum *values, const char *Nulls,
- int tcount);
-extern int SPI_execp_current(void *plan, Datum *values, const char *Nulls,
- bool useCurrentSnapshot, int tcount);
+extern int SPI_execp(void *plan, Datum *Values, const char *Nulls,
+ int tcount);
+extern int SPI_execute_snapshot(void *plan,
+ Datum *Values, const char *Nulls,
+ Snapshot snapshot,
+ Snapshot crosscheck_snapshot,
+ bool read_only, int tcount);
extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
extern void *SPI_saveplan(void *plan);
extern int SPI_freeplan(void *plan);
@@ -113,7 +119,7 @@ extern void SPI_freetuple(HeapTuple pointer);
extern void SPI_freetuptable(SPITupleTable *tuptable);
extern Portal SPI_cursor_open(const char *name, void *plan,
- Datum *Values, const char *Nulls);
+ Datum *Values, const char *Nulls, bool read_only);
extern Portal SPI_cursor_find(const char *name);
extern void SPI_cursor_fetch(Portal portal, bool forward, int count);
extern void SPI_cursor_move(Portal portal, bool forward, int count);
diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h
index e2146a4d640..ced83499b30 100644
--- a/src/include/tcop/pquery.h
+++ b/src/include/tcop/pquery.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.32 2004/08/29 04:13:10 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.33 2004/09/13 20:08:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,15 +20,10 @@
extern DLLIMPORT Portal ActivePortal;
-extern void ProcessQuery(Query *parsetree,
- Plan *plan,
- ParamListInfo params,
- DestReceiver *dest,
- char *completionTag);
-
extern PortalStrategy ChoosePortalStrategy(List *parseTrees);
-extern void PortalStart(Portal portal, ParamListInfo params);
+extern void PortalStart(Portal portal, ParamListInfo params,
+ Snapshot snapshot);
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 68846898450..ce1ed4dcf7c 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.24 2004/08/29 05:06:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.25 2004/09/13 20:08:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -26,6 +26,10 @@ extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
extern const char *CreateCommandTag(Node *parsetree);
+extern const char *CreateQueryTag(Query *parsetree);
+
+extern bool QueryIsReadOnly(Query *parsetree);
+
extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
#endif /* UTILITY_H */
diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h
index 627bcaf75a1..1e7bb617330 100644
--- a/src/include/utils/tqual.h
+++ b/src/include/utils/tqual.h
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/tqual.h,v 1.51 2004/09/11 18:28:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/tqual.h,v 1.52 2004/09/13 20:08:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -54,8 +54,10 @@ typedef SnapshotData *Snapshot;
#define SnapshotToast ((Snapshot) 0x4)
extern DLLIMPORT Snapshot SnapshotDirty;
-extern DLLIMPORT Snapshot QuerySnapshot;
+
extern DLLIMPORT Snapshot SerializableSnapshot;
+extern DLLIMPORT Snapshot LatestSnapshot;
+extern DLLIMPORT Snapshot ActiveSnapshot;
extern TransactionId RecentXmin;
extern TransactionId RecentGlobalXmin;
@@ -121,10 +123,13 @@ extern int HeapTupleSatisfiesUpdate(HeapTupleHeader tuple,
extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple,
TransactionId OldestXmin);
-extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
-extern void SetQuerySnapshot(void);
-extern Snapshot CopyQuerySnapshot(void);
-extern Snapshot CopyCurrentSnapshot(void);
+extern Snapshot GetTransactionSnapshot(void);
+extern Snapshot GetLatestSnapshot(void);
+extern Snapshot CopySnapshot(Snapshot snapshot);
+extern void FreeSnapshot(Snapshot snapshot);
extern void FreeXactSnapshot(void);
+/* in sinval.c; declared here to avoid including tqual.h in sinval.h: */
+extern Snapshot GetSnapshotData(Snapshot snapshot, bool serializable);
+
#endif /* TQUAL_H */
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index ae4a157ac40..c517ca0c3c7 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -33,7 +33,7 @@
* ENHANCEMENTS, OR MODIFICATIONS.
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.50 2004/08/30 02:54:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.51 2004/09/13 20:08:59 tgl Exp $
*
**********************************************************************/
@@ -53,6 +53,7 @@
#include "executor/spi.h"
#include "fmgr.h"
#include "tcop/tcopprot.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -77,6 +78,7 @@ typedef struct plperl_proc_desc
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
bool lanpltrusted;
bool fn_retistuple; /* true, if function returns tuple */
bool fn_retisset; /* true, if function returns set */
@@ -98,11 +100,13 @@ static int plperl_firstcall = 1;
static bool plperl_safe_init_done = false;
static PerlInterpreter *plperl_interp = NULL;
static HV *plperl_proc_hash = NULL;
-static AV *g_row_keys = NULL;
static AV *g_column_keys = NULL;
static SV *srf_perlret = NULL; /* keep returned value */
static int g_attr_num = 0;
+/* this is saved and restored by plperl_call_handler */
+static plperl_proc_desc *plperl_current_prodesc = NULL;
+
/**********************************************************************
* Forward declarations
**********************************************************************/
@@ -119,6 +123,7 @@ static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
static SV *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc);
static void plperl_init_shared_libs(pTHX);
+static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
/*
@@ -435,7 +440,6 @@ static AV *
plperl_get_keys(HV *hv)
{
AV *ret;
- SV **svp;
int key_count;
SV *val;
char *key;
@@ -445,7 +449,7 @@ plperl_get_keys(HV *hv)
ret = newAV();
hv_iterinit(hv);
- while (val = hv_iternextsv(hv, (char **) &key, &klen))
+ while ((val = hv_iternextsv(hv, (char **) &key, &klen)))
{
av_store(ret, key_count, eval_pv(key, TRUE));
key_count++;
@@ -592,26 +596,43 @@ Datum
plperl_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
+ plperl_proc_desc *save_prodesc;
- /************************************************************
- * Initialize interpreter
- ************************************************************/
+ /*
+ * Initialize interpreter if first time through
+ */
plperl_init_all();
- /************************************************************
- * Connect to SPI manager
- ************************************************************/
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(ERROR, "could not connect to SPI manager");
+ /*
+ * Ensure that static pointers are saved/restored properly
+ */
+ save_prodesc = plperl_current_prodesc;
- /************************************************************
- * Determine if called as function or trigger and
- * call appropriate subhandler
- ************************************************************/
- if (CALLED_AS_TRIGGER(fcinfo))
- retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
- else
- retval = plperl_func_handler(fcinfo);
+ PG_TRY();
+ {
+ /************************************************************
+ * Connect to SPI manager
+ ************************************************************/
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /************************************************************
+ * Determine if called as function or trigger and
+ * call appropriate subhandler
+ ************************************************************/
+ if (CALLED_AS_TRIGGER(fcinfo))
+ retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
+ else
+ retval = plperl_func_handler(fcinfo);
+ }
+ PG_CATCH();
+ {
+ plperl_current_prodesc = save_prodesc;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ plperl_current_prodesc = save_prodesc;
return retval;
}
@@ -821,7 +842,6 @@ plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo, S
SV *retval;
int i;
int count;
- char *ret_test;
ENTER;
SAVETMPS;
@@ -874,6 +894,9 @@ plperl_func_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
+
+ plperl_current_prodesc = prodesc;
+
/************************************************************
* Call the Perl function if not returning set
************************************************************/
@@ -1002,7 +1025,6 @@ plperl_func_handler(PG_FUNCTION_ARGS)
{
HV *row_hv;
SV **svp;
- char *row_key;
svp = av_fetch(ret_av, call_cntr, FALSE);
@@ -1052,7 +1074,6 @@ plperl_func_handler(PG_FUNCTION_ARGS)
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcontext;
- int i;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
@@ -1067,7 +1088,6 @@ plperl_func_handler(PG_FUNCTION_ARGS)
Datum result;
AV *array;
SV **svp;
- int i;
array = (AV *) SvRV(perlret);
svp = av_fetch(array, funcctx->call_cntr, FALSE);
@@ -1158,6 +1178,8 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
+ plperl_current_prodesc = prodesc;
+
/************************************************************
* Call the Perl function
************************************************************/
@@ -1323,6 +1345,10 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ /* Remember if function is STABLE/IMMUTABLE */
+ prodesc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
@@ -1560,3 +1586,82 @@ plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)
output = perl_eval_pv(SvPV(output, PL_na), TRUE);
return output;
}
+
+
+HV *
+plperl_spi_exec(char *query, int limit)
+{
+ HV *ret_hv;
+ int spi_rv;
+
+ spi_rv = SPI_execute(query, plperl_current_prodesc->fn_readonly, limit);
+ ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
+
+ return ret_hv;
+}
+
+static HV *
+plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
+{
+ int i;
+ char *attname;
+ char *attdata;
+
+ HV *array;
+
+ array = newHV();
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ /************************************************************
+ * Get the attribute name
+ ************************************************************/
+ attname = tupdesc->attrs[i]->attname.data;
+
+ /************************************************************
+ * Get the attributes value
+ ************************************************************/
+ attdata = SPI_getvalue(tuple, tupdesc, i + 1);
+ if (attdata)
+ hv_store(array, attname, strlen(attname), newSVpv(attdata, 0), 0);
+ else
+ hv_store(array, attname, strlen(attname), newSVpv("undef", 0), 0);
+ }
+ return array;
+}
+
+static HV *
+plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
+{
+ HV *result;
+
+ result = newHV();
+
+ hv_store(result, "status", strlen("status"),
+ newSVpv((char *) SPI_result_code_string(status), 0), 0);
+ hv_store(result, "processed", strlen("processed"),
+ newSViv(processed), 0);
+
+ if (status == SPI_OK_SELECT)
+ {
+ if (processed)
+ {
+ AV *rows;
+ HV *row;
+ int i;
+
+ rows = newAV();
+ for (i = 0; i < processed; i++)
+ {
+ row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
+ av_store(rows, i, newRV_noinc((SV *) row));
+ }
+ hv_store(result, "rows", strlen("rows"),
+ newRV_noinc((SV *) rows), 0);
+ }
+ }
+
+ SPI_freetuptable(tuptable);
+
+ return result;
+}
diff --git a/src/pl/plperl/spi_internal.c b/src/pl/plperl/spi_internal.c
index 5c3bb38a534..390e76a7e77 100644
--- a/src/pl/plperl/spi_internal.c
+++ b/src/pl/plperl/spi_internal.c
@@ -1,15 +1,12 @@
-#include "postgres.h"
-#include "executor/spi.h"
-#include "utils/syscache.h"
/*
* This kludge is necessary because of the conflicting
* definitions of 'DEBUG' between postgres and perl.
* we'll live.
*/
-#include "spi_internal.h"
+#include "postgres.h"
-static HV *plperl_spi_execute_fetch_result(SPITupleTable *, int, int);
+#include "spi_internal.h"
int
@@ -47,81 +44,3 @@ spi_ERROR(void)
{
return ERROR;
}
-
-HV *
-plperl_spi_exec(char *query, int limit)
-{
- HV *ret_hv;
- int spi_rv;
-
- spi_rv = SPI_exec(query, limit);
- ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed, spi_rv);
-
- return ret_hv;
-}
-
-static HV *
-plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
-{
- int i;
- char *attname;
- char *attdata;
-
- HV *array;
-
- array = newHV();
-
- for (i = 0; i < tupdesc->natts; i++)
- {
- /************************************************************
- * Get the attribute name
- ************************************************************/
- attname = tupdesc->attrs[i]->attname.data;
-
- /************************************************************
- * Get the attributes value
- ************************************************************/
- attdata = SPI_getvalue(tuple, tupdesc, i + 1);
- if (attdata)
- hv_store(array, attname, strlen(attname), newSVpv(attdata, 0), 0);
- else
- hv_store(array, attname, strlen(attname), newSVpv("undef", 0), 0);
- }
- return array;
-}
-
-static HV *
-plperl_spi_execute_fetch_result(SPITupleTable *tuptable, int processed, int status)
-{
- HV *result;
-
- result = newHV();
-
- hv_store(result, "status", strlen("status"),
- newSVpv((char *) SPI_result_code_string(status), 0), 0);
- hv_store(result, "processed", strlen("processed"),
- newSViv(processed), 0);
-
- if (status == SPI_OK_SELECT)
- {
- if (processed)
- {
- AV *rows;
- HV *row;
- int i;
-
- rows = newAV();
- for (i = 0; i < processed; i++)
- {
- row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc);
- av_store(rows, i, newRV_noinc((SV *) row));
- }
- hv_store(result, "rows", strlen("rows"),
- newRV_noinc((SV *) rows), 0);
- }
- }
-
- SPI_freetuptable(tuptable);
-
- return result;
-}
diff --git a/src/pl/plperl/spi_internal.h b/src/pl/plperl/spi_internal.h
index 1f1984a1570..b66f43eb2ec 100644
--- a/src/pl/plperl/spi_internal.h
+++ b/src/pl/plperl/spi_internal.h
@@ -15,4 +15,5 @@ int spi_WARNING(void);
int spi_ERROR(void);
+/* this is actually in plperl.c */
HV *plperl_spi_exec(char *, int);
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 02b61c7396a..ea95bef629b 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.81 2004/08/30 02:54:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.82 2004/09/13 20:09:20 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -578,6 +578,9 @@ do_compile(FunctionCallInfo fcinfo,
break;
}
+ /* Remember if function is STABLE/IMMUTABLE */
+ function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/*
* Create the magic FOUND variable.
*/
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 902723843c7..172c0f0fd02 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.118 2004/08/30 02:54:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.119 2004/09/13 20:09:20 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -897,6 +897,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
* sub-transaction
*/
MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
volatile bool caught = false;
int xrc;
@@ -907,12 +908,15 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
BeginInternalSubTransaction(NULL);
/* Want to run statements inside function's memory context */
MemoryContextSwitchTo(oldcontext);
+
if ((xrc = SPI_connect()) != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed: %s",
SPI_result_code_string(xrc));
PG_TRY();
- rc = exec_stmts(estate, block->body);
+ {
+ rc = exec_stmts(estate, block->body);
+ }
PG_CATCH();
{
ErrorData *edata;
@@ -927,6 +931,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
/* Abort the inner transaction (and inner SPI connection) */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
SPI_pop();
@@ -958,8 +963,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
if ((xrc = SPI_finish()) != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed: %s",
SPI_result_code_string(xrc));
+
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
SPI_pop();
}
}
@@ -1984,6 +1992,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->retistuple = func->fn_retistuple;
estate->retisset = func->fn_retset;
+ estate->readonly_func = func->fn_readonly;
+
estate->rettupdesc = NULL;
estate->exitlabel = NULL;
@@ -2019,7 +2029,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
static void
exec_eval_cleanup(PLpgSQL_execstate *estate)
{
- /* Clear result of a full SPI_exec */
+ /* Clear result of a full SPI_execute */
if (estate->eval_tuptable != NULL)
SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL;
@@ -2120,7 +2130,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr);
/*
- * Now build up the values and nulls arguments for SPI_execp()
+ * Now build up the values and nulls arguments for SPI_execute_plan()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
@@ -2142,7 +2152,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
/*
* Execute the plan
*/
- rc = SPI_execp(expr->plan, values, nulls, 0);
+ rc = SPI_execute_plan(expr->plan, values, nulls,
+ estate->readonly_func, 0);
switch (rc)
{
case SPI_OK_UTILITY:
@@ -2168,12 +2179,12 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
errhint("If you want to discard the results, use PERFORM instead.")));
default:
- elog(ERROR, "SPI_execp failed executing query \"%s\": %s",
+ elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
/*
- * Release any result tuples from SPI_execp (probably shouldn't be
+ * Release any result tuples from SPI_execute_plan (probably shouldn't be
* any)
*/
SPI_freetuptable(SPI_tuptable);
@@ -2220,11 +2231,11 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
exec_eval_cleanup(estate);
/*
- * Call SPI_exec() without preparing a saved plan. The returncode can
+ * Call SPI_execute() without preparing a saved plan. The returncode can
* be any standard OK. Note that while a SELECT is allowed, its
* results will be discarded.
*/
- exec_res = SPI_exec(querystr, 0);
+ exec_res = SPI_execute(querystr, estate->readonly_func, 0);
switch (exec_res)
{
case SPI_OK_SELECT:
@@ -2249,7 +2260,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
* behavior is not consistent with SELECT INTO in a normal
* plpgsql context. (We need to reimplement EXECUTE to parse
* the string as a plpgsql command, not just feed it to
- * SPI_exec.) However, CREATE AS should be allowed ... and
+ * SPI_execute.) However, CREATE AS should be allowed ... and
* since it produces the same parsetree as SELECT INTO,
* there's no way to tell the difference except to look at the
* source text. Wotta kluge!
@@ -2284,12 +2295,12 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
- elog(ERROR, "SPI_exec failed executing query \"%s\": %s",
+ elog(ERROR, "SPI_execute failed executing query \"%s\": %s",
querystr, SPI_result_code_string(exec_res));
break;
}
- /* Release any result from SPI_exec, as well as the querystring */
+ /* Release any result from SPI_execute, as well as the querystring */
SPI_freetuptable(SPI_tuptable);
pfree(querystr);
@@ -2357,7 +2368,8 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
- portal = SPI_cursor_open(NULL, plan, NULL, NULL);
+ portal = SPI_cursor_open(NULL, plan, NULL, NULL,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
@@ -2549,7 +2561,8 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
if (curplan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
- portal = SPI_cursor_open(curname, curplan, NULL, NULL);
+ portal = SPI_cursor_open(curname, curplan, NULL, NULL,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor for query \"%s\": %s",
querystr, SPI_result_code_string(SPI_result));
@@ -2643,7 +2656,8 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
* Open the cursor
* ----------
*/
- portal = SPI_cursor_open(curname, query->plan, values, nulls);
+ portal = SPI_cursor_open(curname, query->plan, values, nulls,
+ estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
@@ -3470,7 +3484,7 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr);
/*
- * Now build up the values and nulls arguments for SPI_execp()
+ * Now build up the values and nulls arguments for SPI_execute_plan()
*/
values = (Datum *) palloc(expr->nparams * sizeof(Datum));
nulls = (char *) palloc(expr->nparams * sizeof(char));
@@ -3494,7 +3508,8 @@ exec_run_select(PLpgSQL_execstate *estate,
*/
if (portalP != NULL)
{
- *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls);
+ *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
+ estate->readonly_func);
if (*portalP == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
@@ -3506,7 +3521,8 @@ exec_run_select(PLpgSQL_execstate *estate,
/*
* Execute the query
*/
- rc = SPI_execp(expr->plan, values, nulls, maxtuples);
+ rc = SPI_execute_plan(expr->plan, values, nulls,
+ estate->readonly_func, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index c3919fc3753..26f1bd156b0 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.53 2004/08/30 02:54:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.54 2004/09/13 20:09:21 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -585,6 +585,7 @@ typedef struct PLpgSQL_function
Oid fn_rettypioparam;
bool fn_retistuple;
bool fn_retset;
+ bool fn_readonly;
int fn_nargs;
int fn_argvarnos[FUNC_MAX_ARGS];
@@ -615,6 +616,8 @@ typedef struct
bool retistuple;
bool retisset;
+ bool readonly_func;
+
TupleDesc rettupdesc;
char *exitlabel;
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index ecbf2dbce8a..dd4ef3e455a 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -29,7 +29,7 @@
* MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.55 2004/08/30 02:54:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.56 2004/09/13 20:09:30 tgl Exp $
*
*********************************************************************
*/
@@ -131,6 +131,7 @@ typedef struct PLyProcedure
char *pyname; /* Python name of procedure */
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
PLyTypeInfo result; /* also used to store info for trigger
* tuple type */
PLyTypeInfo args[FUNC_MAX_ARGS];
@@ -257,11 +258,9 @@ static PyObject *PLyString_FromString(const char *);
static int PLy_first_call = 1;
/*
- * Last function called by postgres backend
- *
- * XXX replace this with errcontext mechanism
+ * Currently active plpython function
*/
-static PLyProcedure *PLy_last_procedure = NULL;
+static PLyProcedure *PLy_curr_procedure = NULL;
/*
* When a callback from Python into PG incurs an error, we temporarily store
@@ -322,6 +321,7 @@ Datum
plpython_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
+ PLyProcedure *save_curr_proc;
PLyProcedure *volatile proc = NULL;
PLy_init_all();
@@ -329,6 +329,8 @@ plpython_call_handler(PG_FUNCTION_ARGS)
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
+ save_curr_proc = PLy_curr_procedure;
+
PG_TRY();
{
if (CALLED_AS_TRIGGER(fcinfo))
@@ -338,17 +340,20 @@ plpython_call_handler(PG_FUNCTION_ARGS)
proc = PLy_procedure_get(fcinfo,
RelationGetRelid(tdata->tg_relation));
+ PLy_curr_procedure = proc;
trv = PLy_trigger_handler(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
proc = PLy_procedure_get(fcinfo, InvalidOid);
+ PLy_curr_procedure = proc;
retval = PLy_function_handler(fcinfo, proc);
}
}
PG_CATCH();
{
+ PLy_curr_procedure = save_curr_proc;
if (proc)
{
/* note: Py_DECREF needs braces around it, as of 2003/08 */
@@ -359,6 +364,8 @@ plpython_call_handler(PG_FUNCTION_ARGS)
}
PG_END_TRY();
+ PLy_curr_procedure = save_curr_proc;
+
Py_DECREF(proc->me);
return retval;
@@ -795,14 +802,10 @@ static PyObject *
PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs)
{
PyObject *rv;
- PLyProcedure *current;
- current = PLy_last_procedure;
- PLy_last_procedure = proc;
PyDict_SetItemString(proc->globals, kargs, vargs);
rv = PyEval_EvalCode((PyCodeObject *) proc->code,
proc->globals, proc->globals);
- PLy_last_procedure = current;
/*
* If there was an error in a PG callback, propagate that no matter
@@ -1005,6 +1008,9 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid,
strcpy(proc->pyname, procName);
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ /* Remember if function is STABLE/IMMUTABLE */
+ proc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
PLy_typeinfo_init(&proc->result);
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
@@ -1935,7 +1941,8 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_prepare");
/* XXX this oughta be replaced with errcontext mechanism */
- PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
+ PLy_elog(WARNING, "in function %s:",
+ PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
@@ -2054,7 +2061,8 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
}
}
- rv = SPI_execp(plan->plan, plan->values, nulls, limit);
+ rv = SPI_execute_plan(plan->plan, plan->values, nulls,
+ PLy_curr_procedure->fn_readonly, limit);
pfree(nulls);
}
@@ -2080,7 +2088,9 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_error,
"Unknown error in PLy_spi_execute_plan");
- PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
+ /* XXX this oughta be replaced with errcontext mechanism */
+ PLy_elog(WARNING, "in function %s:",
+ PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
@@ -2098,7 +2108,7 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
- "SPI_execp failed: %s",
+ "SPI_execute_plan failed: %s",
SPI_result_code_string(rv));
return NULL;
}
@@ -2114,7 +2124,9 @@ PLy_spi_execute_query(char *query, int limit)
oldcontext = CurrentMemoryContext;
PG_TRY();
- rv = SPI_exec(query, limit);
+ {
+ rv = SPI_execute(query, PLy_curr_procedure->fn_readonly, limit);
+ }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
@@ -2123,7 +2135,9 @@ PLy_spi_execute_query(char *query, int limit)
if (!PyErr_Occurred())
PyErr_SetString(PLy_exc_spi_error,
"Unknown error in PLy_spi_execute_query");
- PLy_elog(WARNING, "in function %s:", PLy_procedure_name(PLy_last_procedure));
+ /* XXX this oughta be replaced with errcontext mechanism */
+ PLy_elog(WARNING, "in function %s:",
+ PLy_procedure_name(PLy_curr_procedure));
return NULL;
}
PG_END_TRY();
@@ -2131,7 +2145,7 @@ PLy_spi_execute_query(char *query, int limit)
if (rv < 0)
{
PLy_exception_set(PLy_exc_spi_error,
- "SPI_exec failed: %s",
+ "SPI_execute failed: %s",
SPI_result_code_string(rv));
return NULL;
}
@@ -2375,7 +2389,9 @@ PLy_output(volatile int level, PyObject * self, PyObject * args)
oldcontext = CurrentMemoryContext;
PG_TRY();
- elog(level, "%s", sv);
+ {
+ elog(level, "%s", sv);
+ }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index a59cef23f91..f344d4ad676 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -31,7 +31,7 @@
* ENHANCEMENTS, OR MODIFICATIONS.
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.91 2004/08/30 02:54:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.92 2004/09/13 20:09:39 tgl Exp $
*
**********************************************************************/
@@ -87,13 +87,17 @@ utf_e2u(unsigned char *src)
pfree(_pltcl_utf_dst); } while (0)
#define UTF_U2E(x) (_pltcl_utf_dst=utf_u2e(_pltcl_utf_src=(x)))
#define UTF_E2U(x) (_pltcl_utf_dst=utf_e2u(_pltcl_utf_src=(x)))
-#else /* PLTCL_UTF */
+
+#else /* !PLTCL_UTF */
+
#define UTF_BEGIN
#define UTF_END
#define UTF_U2E(x) (x)
#define UTF_E2U(x) (x)
+
#endif /* PLTCL_UTF */
+
/**********************************************************************
* The information we cache about loaded procedures
**********************************************************************/
@@ -102,6 +106,7 @@ typedef struct pltcl_proc_desc
char *proname;
TransactionId fn_xmin;
CommandId fn_cmin;
+ bool fn_readonly;
bool lanpltrusted;
FmgrInfo result_in_func;
Oid result_typioparam;
@@ -137,7 +142,10 @@ static Tcl_Interp *pltcl_safe_interp = NULL;
static Tcl_HashTable *pltcl_proc_hash = NULL;
static Tcl_HashTable *pltcl_norm_query_hash = NULL;
static Tcl_HashTable *pltcl_safe_query_hash = NULL;
+
+/* these are saved and restored by pltcl_call_handler */
static FunctionCallInfo pltcl_current_fcinfo = NULL;
+static pltcl_proc_desc *pltcl_current_prodesc = NULL;
/*
* When a callback from Tcl into PG incurs an error, we temporarily store
@@ -179,11 +187,11 @@ static int pltcl_argisnull(ClientData cdata, Tcl_Interp *interp,
static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
-static int pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
+static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
-static int pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
+static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
static int pltcl_SPI_lastoid(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
@@ -307,11 +315,11 @@ pltcl_init_interp(Tcl_Interp *interp)
pltcl_returnnull, NULL, NULL);
Tcl_CreateCommand(interp, "spi_exec",
- pltcl_SPI_exec, NULL, NULL);
+ pltcl_SPI_execute, NULL, NULL);
Tcl_CreateCommand(interp, "spi_prepare",
pltcl_SPI_prepare, NULL, NULL);
Tcl_CreateCommand(interp, "spi_execp",
- pltcl_SPI_execp, NULL, NULL);
+ pltcl_SPI_execute_plan, NULL, NULL);
Tcl_CreateCommand(interp, "spi_lastoid",
pltcl_SPI_lastoid, NULL, NULL);
}
@@ -334,8 +342,9 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
/************************************************************
* Check if table pltcl_modules exists
************************************************************/
- spi_rc = SPI_exec("select 1 from pg_catalog.pg_class "
- "where relname = 'pltcl_modules'", 1);
+ spi_rc = SPI_execute("select 1 from pg_catalog.pg_class "
+ "where relname = 'pltcl_modules'",
+ false, 1);
SPI_freetuptable(SPI_tuptable);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pg_class failed");
@@ -348,9 +357,10 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
************************************************************/
Tcl_DStringInit(&unknown_src);
- spi_rc = SPI_exec("select modseq, modsrc from pltcl_modules "
- "where modname = 'unknown' "
- "order by modseq", 0);
+ spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules "
+ "where modname = 'unknown' "
+ "order by modseq",
+ false, 0);
if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pltcl_modules failed");
@@ -405,30 +415,46 @@ pltcl_call_handler(PG_FUNCTION_ARGS)
{
Datum retval;
FunctionCallInfo save_fcinfo;
+ pltcl_proc_desc *save_prodesc;
- /************************************************************
+ /*
* Initialize interpreters if first time through
- ************************************************************/
+ */
pltcl_init_all();
- /************************************************************
- * Determine if called as function or trigger and
- * call appropriate subhandler
- ************************************************************/
+ /*
+ * Ensure that static pointers are saved/restored properly
+ */
save_fcinfo = pltcl_current_fcinfo;
+ save_prodesc = pltcl_current_prodesc;
- if (CALLED_AS_TRIGGER(fcinfo))
+ PG_TRY();
{
- pltcl_current_fcinfo = NULL;
- retval = PointerGetDatum(pltcl_trigger_handler(fcinfo));
+ /*
+ * Determine if called as function or trigger and
+ * call appropriate subhandler
+ */
+ if (CALLED_AS_TRIGGER(fcinfo))
+ {
+ pltcl_current_fcinfo = NULL;
+ retval = PointerGetDatum(pltcl_trigger_handler(fcinfo));
+ }
+ else
+ {
+ pltcl_current_fcinfo = fcinfo;
+ retval = pltcl_func_handler(fcinfo);
+ }
}
- else
+ PG_CATCH();
{
- pltcl_current_fcinfo = fcinfo;
- retval = pltcl_func_handler(fcinfo);
+ pltcl_current_fcinfo = save_fcinfo;
+ pltcl_current_prodesc = save_prodesc;
+ PG_RE_THROW();
}
+ PG_END_TRY();
pltcl_current_fcinfo = save_fcinfo;
+ pltcl_current_prodesc = save_prodesc;
return retval;
}
@@ -467,6 +493,8 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid);
+ pltcl_current_prodesc = prodesc;
+
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
@@ -643,6 +671,8 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation));
+ pltcl_current_prodesc = prodesc;
+
if (prodesc->lanpltrusted)
interp = pltcl_safe_interp;
else
@@ -1030,6 +1060,10 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ /* Remember if function is STABLE/IMMUTABLE */
+ prodesc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
@@ -1336,7 +1370,7 @@ pltcl_elog(ClientData cdata, Tcl_Interp *interp,
/**********************************************************************
* pltcl_quote() - quote literal strings that are to
- * be used in SPI_exec query strings
+ * be used in SPI_execute query strings
**********************************************************************/
static int
pltcl_quote(ClientData cdata, Tcl_Interp *interp,
@@ -1484,12 +1518,12 @@ pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
/**********************************************************************
- * pltcl_SPI_exec() - The builtin SPI_exec command
+ * pltcl_SPI_execute() - The builtin SPI_execute command
* for the Tcl interpreter
**********************************************************************/
static int
-pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
- int argc, CONST84 char *argv[])
+pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
+ int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
@@ -1570,7 +1604,8 @@ pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
PG_TRY();
{
UTF_BEGIN;
- spi_rc = SPI_exec(UTF_U2E(argv[query_idx]), count);
+ spi_rc = SPI_execute(UTF_U2E(argv[query_idx]),
+ pltcl_current_prodesc->fn_readonly, count);
UTF_END;
}
PG_CATCH();
@@ -1603,7 +1638,7 @@ pltcl_SPI_exec(ClientData cdata, Tcl_Interp *interp,
break;
default:
- Tcl_AppendResult(interp, "pltcl: SPI_exec failed: ",
+ Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
@@ -1840,11 +1875,11 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
/**********************************************************************
- * pltcl_SPI_execp() - Execute a prepared plan
+ * pltcl_SPI_execute_plan() - Execute a prepared plan
**********************************************************************/
static int
-pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
- int argc, CONST84 char *argv[])
+pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
+ int argc, CONST84 char *argv[])
{
volatile int my_rc;
int spi_rc;
@@ -1992,7 +2027,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
}
/************************************************************
- * Setup the value array for the SPI_execp() using
+ * Setup the value array for SPI_execute_plan() using
* the type specific input functions
************************************************************/
oldcontext = CurrentMemoryContext;
@@ -2046,7 +2081,10 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
************************************************************/
oldcontext = CurrentMemoryContext;
PG_TRY();
- spi_rc = SPI_execp(qdesc->plan, argvalues, nulls, count);
+ {
+ spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
+ pltcl_current_prodesc->fn_readonly, count);
+ }
PG_CATCH();
{
MemoryContextSwitchTo(oldcontext);
@@ -2058,7 +2096,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
PG_END_TRY();
/************************************************************
- * Check the return code from SPI_execp()
+ * Check the return code from SPI_execute_plan()
************************************************************/
switch (spi_rc)
{
@@ -2080,7 +2118,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp,
break;
default:
- Tcl_AppendResult(interp, "pltcl: SPI_execp failed: ",
+ Tcl_AppendResult(interp, "pltcl: SPI_execute_plan failed: ",
SPI_result_code_string(spi_rc), NULL);
SPI_freetuptable(SPI_tuptable);
return TCL_ERROR;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 955558d690b..1c8ba64c5c0 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -374,6 +374,84 @@ ERROR: portal "c" cannot be run
FETCH 10 FROM c;
ERROR: portal "c" cannot be run
COMMIT;
+--
+-- Check that "stable" functions are really stable. They should not be
+-- able to see the partial results of the calling query. (Ideally we would
+-- also check that they don't see commits of concurrent transactions, but
+-- that's a mite hard to do within the limitations of pg_regress.)
+--
+select * from xacttest;
+ a | b
+-----+---------
+ 56 | 7.8
+ 100 | 99.097
+ 0 | 0.09561
+ 42 | 324.78
+ 777 | 777.777
+(5 rows)
+
+create or replace function max_xacttest() returns smallint language sql as
+'select max(a) from xacttest' stable;
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+ a | b
+-----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 787 | 99.097
+ 787 | 324.78
+ 787 | 777.777
+(5 rows)
+
+rollback;
+-- But a volatile function can see the partial results of the calling query
+create or replace function max_xacttest() returns smallint language sql as
+'select max(a) from xacttest' volatile;
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+ a | b
+-----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 797 | 99.097
+ 807 | 324.78
+ 817 | 777.777
+(5 rows)
+
+rollback;
+-- Now the same test with plpgsql (since it depends on SPI which is different)
+create or replace function max_xacttest() returns smallint language plpgsql as
+'begin return max(a) from xacttest; end' stable;
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+ a | b
+-----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 787 | 99.097
+ 787 | 324.78
+ 787 | 777.777
+(5 rows)
+
+rollback;
+create or replace function max_xacttest() returns smallint language plpgsql as
+'begin return max(a) from xacttest; end' volatile;
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+ a | b
+-----+---------
+ 0 | 0.09561
+ 787 | 7.8
+ 797 | 99.097
+ 807 | 324.78
+ 817 | 777.777
+(5 rows)
+
+rollback;
-- test case for problems with dropping an open relation during abort
BEGIN;
savepoint x;
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 81006d16d23..0046974402b 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -231,6 +231,49 @@ BEGIN;
FETCH 10 FROM c;
COMMIT;
+--
+-- Check that "stable" functions are really stable. They should not be
+-- able to see the partial results of the calling query. (Ideally we would
+-- also check that they don't see commits of concurrent transactions, but
+-- that's a mite hard to do within the limitations of pg_regress.)
+--
+select * from xacttest;
+
+create or replace function max_xacttest() returns smallint language sql as
+'select max(a) from xacttest' stable;
+
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+rollback;
+
+-- But a volatile function can see the partial results of the calling query
+create or replace function max_xacttest() returns smallint language sql as
+'select max(a) from xacttest' volatile;
+
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+rollback;
+
+-- Now the same test with plpgsql (since it depends on SPI which is different)
+create or replace function max_xacttest() returns smallint language plpgsql as
+'begin return max(a) from xacttest; end' stable;
+
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+rollback;
+
+create or replace function max_xacttest() returns smallint language plpgsql as
+'begin return max(a) from xacttest; end' volatile;
+
+begin;
+update xacttest set a = max_xacttest() + 10 where a > 0;
+select * from xacttest;
+rollback;
+
+
-- test case for problems with dropping an open relation during abort
BEGIN;
savepoint x;