mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
Redesign query-snapshot timing so that volatile functions in READ COMMITTED
mode see a fresh snapshot for each command in the function, rather than using the latest interactive command's snapshot. Also, suppress fresh snapshots as well as CommandCounterIncrement inside STABLE and IMMUTABLE functions, instead using the snapshot taken for the most closely nested regular query. (This behavior is only sane for read-only functions, so the patch also enforces that such functions contain only SELECT commands.) As per my proposal of 6-Sep-2004; I note that I floated essentially the same proposal on 19-Jun-2002, but that discussion tailed off without any action. Since 8.0 seems like the right place to be taking possibly nontrivial backwards compatibility hits, let's get it done now.
This commit is contained in:
@ -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,
|
||||
|
@ -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)
|
||||
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)
|
||||
{
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a single valued function we have to end the function
|
||||
* execution now.
|
||||
*/
|
||||
if (!fcinfo->flinfo->fn_retset)
|
||||
postquel_end(es);
|
||||
HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
|
||||
HeapTupleHeaderSetTypeId(dtup, dtuptype);
|
||||
HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
|
||||
|
||||
return value;
|
||||
value = PointerGetDatum(dtup);
|
||||
fcinfo->isnull = false;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
@ -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,42 +267,75 @@ 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_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;
|
||||
|
||||
if (plan == NULL || tcount < 0)
|
||||
return SPI_ERROR_ARGUMENT;
|
||||
|
||||
if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
|
||||
return SPI_ERROR_PARAM;
|
||||
|
||||
res = _SPI_begin_call(true);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
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)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (plan == NULL || tcount < 0)
|
||||
return SPI_ERROR_ARGUMENT;
|
||||
|
||||
if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
|
||||
return SPI_ERROR_PARAM;
|
||||
|
||||
res = _SPI_begin_call(true);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
res = _SPI_execute_plan((_SPI_plan *) plan, Values, Nulls, false, tcount);
|
||||
|
||||
_SPI_end_call(true);
|
||||
return res;
|
||||
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;
|
||||
@ -866,10 +902,22 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
|
||||
else
|
||||
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.
|
||||
*
|
||||
* If plan != NULL, just prepare plan trees and save them in *plan;
|
||||
* else execute immediately.
|
||||
* At entry, plan->argtypes and plan->nargs must be valid.
|
||||
*
|
||||
* 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;
|
||||
Oid *argtypes = plan->argtypes;
|
||||
int nargs = plan->nargs;
|
||||
|
||||
if (plan)
|
||||
{
|
||||
nargs = plan->nargs;
|
||||
argtypes = plan->argtypes;
|
||||
}
|
||||
|
||||
/* 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) */
|
||||
plan_list = list_concat(plan_list,
|
||||
pg_plan_queries(query_list, NULL, false));
|
||||
}
|
||||
|
||||
plan->qtlist = query_list_list;
|
||||
plan->ptlist = plan_list;
|
||||
|
||||
/*
|
||||
* Pop the error context stack
|
||||
*/
|
||||
error_context_stack = spierrcontext.previous;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
Snapshot snapshot, Snapshot crosscheck_snapshot,
|
||||
bool read_only, int tcount)
|
||||
{
|
||||
volatile int res = 0;
|
||||
Snapshot saveActiveSnapshot;
|
||||
|
||||
/* Be sure to restore ActiveSnapshot on error exit */
|
||||
saveActiveSnapshot = ActiveSnapshot;
|
||||
PG_TRY();
|
||||
{
|
||||
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)
|
||||
{
|
||||
int k;
|
||||
|
||||
paramLI = (ParamListInfo)
|
||||
palloc0((nargs + 1) * sizeof(ParamListInfoData));
|
||||
|
||||
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 (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 = pg_plan_query(queryTree, NULL);
|
||||
plan_list = lappend(plan_list, planTree);
|
||||
/* 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)
|
||||
foreach(query_list_item, query_list)
|
||||
{
|
||||
if (IsA(queryTree->utilityStmt, CopyStmt))
|
||||
{
|
||||
CopyStmt *stmt = (CopyStmt *) queryTree->utilityStmt;
|
||||
Query *queryTree = (Query *) lfirst(query_list_item);
|
||||
Plan *planTree;
|
||||
QueryDesc *qdesc;
|
||||
DestReceiver *dest;
|
||||
|
||||
if (stmt->filename == NULL)
|
||||
planTree = lfirst(plan_list_item);
|
||||
plan_list_item = lnext(plan_list_item);
|
||||
|
||||
if (queryTree->commandType == CMD_UTILITY)
|
||||
{
|
||||
if (IsA(queryTree->utilityStmt, CopyStmt))
|
||||
{
|
||||
res = SPI_ERROR_COPY;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
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 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (plan)
|
||||
{
|
||||
plan->qtlist = query_list_list;
|
||||
plan->ptlist = plan_list;
|
||||
}
|
||||
|
||||
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_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
|
||||
bool useCurrentSnapshot, 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();
|
||||
|
||||
/* Convert parameters to form wanted by executor */
|
||||
if (nargs > 0)
|
||||
{
|
||||
int k;
|
||||
|
||||
paramLI = (ParamListInfo)
|
||||
palloc0((nargs + 1) * sizeof(ParamListInfoData));
|
||||
|
||||
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 (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;
|
||||
|
||||
foreach(query_list_list_item, query_list_list)
|
||||
{
|
||||
List *query_list = lfirst(query_list_list_item);
|
||||
ListCell *query_list_item;
|
||||
|
||||
/* Reset state for each original parsetree */
|
||||
/* (at most one of its querytrees will be marked canSetTag) */
|
||||
SPI_processed = 0;
|
||||
SPI_lastoid = InvalidOid;
|
||||
SPI_tuptable = NULL;
|
||||
_SPI_current->tuptable = NULL;
|
||||
|
||||
foreach(query_list_item, query_list)
|
||||
{
|
||||
Query *queryTree = (Query *) lfirst(query_list_item);
|
||||
Plan *planTree;
|
||||
QueryDesc *qdesc;
|
||||
DestReceiver *dest;
|
||||
|
||||
planTree = lfirst(plan_list_item);
|
||||
plan_list_item = lnext(plan_list_item);
|
||||
|
||||
dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
|
||||
if (queryTree->commandType == CMD_UTILITY)
|
||||
{
|
||||
ProcessUtility(queryTree->utilityStmt, paramLI, dest, NULL);
|
||||
res = SPI_OK_UTILITY;
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
else
|
||||
{
|
||||
qdesc = CreateQueryDesc(queryTree, planTree, dest,
|
||||
paramLI, false);
|
||||
res = _SPI_pquery(qdesc, true, useCurrentSnapshot,
|
||||
queryTree->canSetTag ? tcount : 0);
|
||||
if (res < 0)
|
||||
goto fail;
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
|
||||
/*
|
||||
* Pop the error context stack
|
||||
*/
|
||||
error_context_stack = spierrcontext.previous;
|
||||
|
||||
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");
|
||||
|
Reference in New Issue
Block a user