diff options
Diffstat (limited to 'src/backend/executor')
-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 |
3 files changed, 457 insertions, 345 deletions
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"); |