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