{
char *ident;
int nplans;
- void **splan;
+ SPIPlanPtr *splan;
} EPlan;
static EPlan *FPlans = NULL;
*/
if (plan->nplans <= 0)
{
- void *pplan;
+ SPIPlanPtr pplan;
char sql[8192];
/*
if (pplan == NULL)
/* internal error */
elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result);
- plan->splan = (void **) malloc(sizeof(void *));
+ plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
*(plan->splan) = pplan;
plan->nplans = 1;
}
*/
if (plan->nplans <= 0)
{
- void *pplan;
+ SPIPlanPtr pplan;
char sql[8192];
char **args2 = args;
- plan->splan = (void **) malloc(nrefs * sizeof(void *));
+ plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr));
for (r = 0; r < nrefs; r++)
{
typedef struct
{
char *ident;
- void *splan;
+ SPIPlanPtr splan;
} EPlan;
static EPlan *Plans = NULL; /* for UPDATE/DELETE */
/* if there is no plan ... */
if (plan->splan == NULL)
{
- void *pplan;
+ SPIPlanPtr pplan;
Oid *ctypes;
char sql[8192];
char separ = ' ';
-
+
Server Programming Interface
SQL commands inside their functions.
interface functions to simplify access to the parser, planner,
-
optimizer, and executor.
SPI also does some
+ and executor.
SPI also does some
memory management.
You can pass multiple commands in one string, but later commands cannot
- depend on the creation of objects earlier in the string.
+ depend on the creation of objects earlier in the string, because the
+ whole string will be parsed and planned before execution begins.
SPI_execute returns the
result for the command executed last. The
count
limit applies to each command separately, but it is not applied to
-
void * SPI_prepare(const char *
command, int
nargs, Oid *
argtypes)
+
SPIPlanPtr SPI_prepare(const char *
command, int
nargs, Oid *
argtypes)
Notes
+ SPIPlanPtr> is declared as a pointer to an opaque struct type in
+ spi.h>. It is unwise to try to access its contents
+ directly, as that makes your code much more likely to break in
+ future revisions of
PostgreSQL.
+
+
There is a disadvantage to using parameters: since the planner does
not know the values that will be supplied for the parameters, it
-int SPI_getargcount(
void * plan)
+int SPI_getargcount(
SPIPlanPtr plan)
execution plan (returned by SPI_prepare)
Return Value
- The expected argument count for the
plan, or
-
SPI_ERROR_ARGUMENT if the
plan
- is NULL
+ The count of expected arguments for the
plan.
+ If the
plan is
NULL or invalid,
+ SPI_result is set to SPI_ERROR_ARGUMENT
+ and -1 is returned.
-Oid SPI_getargtypeid(
void * plan, int
argIndex)
+Oid SPI_getargtypeid(
SPIPlanPtr plan, int
argIndex)
execution plan (returned by SPI_prepare)
Return Value
- The type id of the argument at the given index, or
-
SPI_ERROR_ARGUMENT if the
plan is
-
NULL or
argIndex is less than 0 or
+ The type id of the argument at the given index.
+ If the
plan is
NULL or invalid,
+ or
argIndex is less than 0 or
not less than the number of arguments declared for the
+ SPI_result is set to SPI_ERROR_ARGUMENT
+ and InvalidOid is returned.
-bool SPI_is_cursor_plan(
void * plan)
+bool SPI_is_cursor_plan(
SPIPlanPtr plan)
execution plan (returned by SPI_prepare)
Return Value
true or false to indicate if the
-
plan can produce a cursor or not, or
-
SPI_ERROR_ARGUMENT if the
plan
- is NULL
+
plan can produce a cursor or not.
+ If the
plan is
NULL or invalid,
+ SPI_result is set to SPI_ERROR_ARGUMENT
+ and false is returned.
-int SPI_execute_plan(
void * plan, Datum *
values, const char *
nulls,
+int SPI_execute_plan(
SPIPlanPtr plan, Datum *
values, const char *
nulls,
bool
read_only, long
count)
execution plan (returned by SPI_prepare)
SPI_ERROR_ARGUMENT
+ if
plan is
NULL or
invalid,
+
or count is less than 0
-int SPI_execp(
void * plan, Datum *
values, const char *
nulls, long
count)
+int SPI_execp(
SPIPlanPtr plan, Datum *
values, const char *
nulls, long
count)
execution plan (returned by SPI_prepare)
-Portal SPI_cursor_open(const char *
name,
void * plan,
+Portal SPI_cursor_open(const char *
name,
SPIPlanPtr plan,
Datum *
values, const char *
nulls,
to the procedure's caller provides a way of returning a row set as
result.
+
+ The passed-in data will be copied into the cursor's portal, so it
+ can be freed while the cursor still exists.
+
execution plan (returned by SPI_prepare)
-
void * SPI_saveplan(void * plan)
+
SPIPlanPtr SPI_saveplan(SPIPlanPtr plan)
SPI_saveplan saves a passed plan (prepared by
- SPI_prepare) in memory protected from freeing
- by SPI_finish and by the transaction manager
+ SPI_prepare) in memory that will not be freed
+ by SPI_finish nor by the transaction manager,
and returns a pointer to the saved plan. This gives you the
ability to reuse prepared plans in the subsequent invocations of
your procedure in the current session.
the plan to be saved
SPI_ERROR_ARGUMENT
+ if
plan is
NULL or invalid
Notes
+ The passed-in plan is not freed, so you might wish to do
+ SPI_freeplan on it to avoid leaking memory
+ until SPI_finish>.
+
+
If one of the objects (a table, function, etc.) referenced by the
- prepared plan is dropped during the session then the results of
- SPI_execute_plan for this plan will be unpredictable.
+ prepared plan is dropped or redefined, then future executions of
+ SPI_execute_plan may fail or return different
+ results than the plan initially indicates.
-int SPI_freeplan(
void *plan)
+int SPI_freeplan(
SPIPlanPtr plan)
pointer to plan to free
SPI_ERROR_ARGUMENT if
plan
- is NULL.
+ is NULL or invalid
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.171 2007/03/13 00:33:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.172 2007/03/15 23:12:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static int _SPI_connected = -1;
static int _SPI_curid = -1;
-static void _SPI_prepare_plan(const char *src, _SPI_plan *plan);
+static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan);
-static int _SPI_execute_plan(_SPI_plan *plan,
+static int _SPI_execute_plan(SPIPlanPtr plan,
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, long tcount);
static void _SPI_cursor_operation(Portal portal, bool forward, long count,
DestReceiver *dest);
-static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
+static SPIPlanPtr _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt);
+static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
static int _SPI_begin_call(bool execmem);
static int _SPI_end_call(bool procmem);
if (res < 0)
return res;
- plan.plancxt = NULL; /* doesn't have own context */
- plan.query = src;
- plan.nargs = 0;
- plan.argtypes = NULL;
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
_SPI_prepare_plan(src, &plan);
/* Execute a previously prepared plan */
int
-SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
+SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
bool read_only, long tcount)
{
int res;
- if (plan == NULL || tcount < 0)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
return SPI_ERROR_ARGUMENT;
- if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
+ if (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,
+ res = _SPI_execute_plan(plan,
Values, Nulls,
InvalidSnapshot, InvalidSnapshot,
read_only, tcount);
/* Obsolete version of SPI_execute_plan */
int
-SPI_execp(void *plan, Datum *Values, const char *Nulls, long tcount)
+SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
{
return SPI_execute_plan(plan, Values, Nulls, false, tcount);
}
* fetching a new snapshot for each query.
*/
int
-SPI_execute_snapshot(void *plan,
+SPI_execute_snapshot(SPIPlanPtr plan,
Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, long tcount)
{
int res;
- if (plan == NULL || tcount < 0)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
return SPI_ERROR_ARGUMENT;
- if (((_SPI_plan *) plan)->nargs > 0 && Values == NULL)
+ if (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,
+ res = _SPI_execute_plan(plan,
Values, Nulls,
snapshot, crosscheck_snapshot,
read_only, tcount);
return res;
}
-void *
+SPIPlanPtr
SPI_prepare(const char *src, int nargs, Oid *argtypes)
{
_SPI_plan plan;
- _SPI_plan *result;
+ SPIPlanPtr result;
if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL))
{
if (SPI_result < 0)
return NULL;
- plan.plancxt = NULL; /* doesn't have own context */
- plan.query = src;
+ memset(&plan, 0, sizeof(_SPI_plan));
+ plan.magic = _SPI_PLAN_MAGIC;
plan.nargs = nargs;
plan.argtypes = argtypes;
_SPI_prepare_plan(src, &plan);
/* copy plan to procedure context */
- result = _SPI_copy_plan(&plan, _SPI_CPLAN_PROCXT);
+ result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
_SPI_end_call(true);
- return (void *) result;
+ return result;
}
-void *
-SPI_saveplan(void *plan)
+SPIPlanPtr
+SPI_saveplan(SPIPlanPtr plan)
{
- _SPI_plan *newplan;
+ SPIPlanPtr newplan;
- if (plan == NULL)
+ /* We don't currently support copying an already-saved plan */
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
{
SPI_result = SPI_ERROR_ARGUMENT;
return NULL;
if (SPI_result < 0)
return NULL;
- newplan = _SPI_copy_plan((_SPI_plan *) plan, _SPI_CPLAN_TOPCXT);
+ newplan = _SPI_save_plan(plan);
_SPI_curid--;
SPI_result = 0;
- return (void *) newplan;
+ return newplan;
}
int
-SPI_freeplan(void *plan)
+SPI_freeplan(SPIPlanPtr plan)
{
- _SPI_plan *spiplan = (_SPI_plan *) plan;
-
- if (plan == NULL)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
return SPI_ERROR_ARGUMENT;
- MemoryContextDelete(spiplan->plancxt);
+ /* If plancache.c owns the plancache entries, we must release them */
+ if (plan->saved)
+ {
+ ListCell *lc;
+
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+
+ DropCachedPlan(plansource);
+ }
+ }
+
+ /* Now get rid of the _SPI_plan and subsidiary data in its plancxt */
+ MemoryContextDelete(plan->plancxt);
+
return 0;
}
}
-
/*
* SPI_cursor_open()
*
* Open a prepared SPI plan as a portal
*/
Portal
-SPI_cursor_open(const char *name, void *plan,
+SPI_cursor_open(const char *name, SPIPlanPtr plan,
Datum *Values, const char *Nulls,
bool read_only)
{
- _SPI_plan *spiplan = (_SPI_plan *) plan;
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
List *stmt_list;
ParamListInfo paramLI;
Snapshot snapshot;
* Check that the plan is something the Portal code will special-case as
* returning one tupleset.
*/
- if (!SPI_is_cursor_plan(spiplan))
+ if (!SPI_is_cursor_plan(plan))
{
/* try to give a good error message */
- Node *stmt;
-
- if (list_length(spiplan->stmt_list_list) != 1)
+ if (list_length(plan->plancache_list) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open multi-query plan as cursor")));
- stmt = PortalListGetPrimaryStmt((List *) linitial(spiplan->stmt_list_list));
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
/* translator: %s is name of a SQL command, eg INSERT */
errmsg("cannot open %s query as cursor",
- CreateCommandTag(stmt))));
+ plansource->commandTag)));
}
- Assert(list_length(spiplan->stmt_list_list) == 1);
- stmt_list = (List *) linitial(spiplan->stmt_list_list);
+ Assert(list_length(plan->plancache_list) == 1);
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Reset SPI result (note we deliberately don't touch lastoid) */
SPI_processed = 0;
portal = CreatePortal(name, false, false);
}
- /* Switch to portal's memory and copy the plans to there */
- oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
- stmt_list = copyObject(stmt_list);
-
- /* If the plan has parameters, set them up */
- if (spiplan->nargs > 0)
+ /* If the plan has parameters, copy them into the portal */
+ if (plan->nargs > 0)
{
+ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
- (spiplan->nargs - 1) *sizeof(ParamExternData));
- paramLI->numParams = spiplan->nargs;
+ (plan->nargs - 1) *sizeof(ParamExternData));
+ paramLI->numParams = plan->nargs;
- for (k = 0; k < spiplan->nargs; k++)
+ for (k = 0; k < plan->nargs; k++)
{
ParamExternData *prm = ¶mLI->params[k];
- prm->ptype = spiplan->argtypes[k];
+ prm->ptype = plan->argtypes[k];
prm->pflags = 0;
prm->isnull = (Nulls && Nulls[k] == 'n');
if (prm->isnull)
paramTypByVal, paramTypLen);
}
}
+ MemoryContextSwitchTo(oldcontext);
}
else
paramLI = NULL;
+ if (plan->saved)
+ {
+ /* Replan if needed, and increment plan refcount for portal */
+ cplan = RevalidateCachedPlan(plansource, false);
+ stmt_list = cplan->stmt_list;
+ }
+ else
+ {
+ /* No replan, but copy the plan into the portal's context */
+ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+ stmt_list = copyObject(plansource->plan->stmt_list);
+ MemoryContextSwitchTo(oldcontext);
+ cplan = NULL; /* portal shouldn't depend on cplan */
+ }
+
/*
* Set up the portal.
*/
PortalDefineQuery(portal,
NULL, /* no statement name */
- spiplan->query,
- CreateCommandTag(PortalListGetPrimaryStmt(stmt_list)),
+ plansource->query_string,
+ plansource->commandTag,
stmt_list,
- NULL);
-
- MemoryContextSwitchTo(oldcontext);
+ cplan);
/*
* Set up options for portal.
* parameter is at index zero.
*/
Oid
-SPI_getargtypeid(void *plan, int argIndex)
+SPI_getargtypeid(SPIPlanPtr plan, int argIndex)
{
- if (plan == NULL || argIndex < 0 || argIndex >= ((_SPI_plan *) plan)->nargs)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC ||
+ argIndex < 0 || argIndex >= plan->nargs)
{
SPI_result = SPI_ERROR_ARGUMENT;
return InvalidOid;
}
- return ((_SPI_plan *) plan)->argtypes[argIndex];
+ return plan->argtypes[argIndex];
}
/*
* Returns the number of arguments for the prepared plan.
*/
int
-SPI_getargcount(void *plan)
+SPI_getargcount(SPIPlanPtr plan)
{
- if (plan == NULL)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
{
SPI_result = SPI_ERROR_ARGUMENT;
return -1;
}
- return ((_SPI_plan *) plan)->nargs;
+ return plan->nargs;
}
/*
* plan: A plan previously prepared using SPI_prepare
*/
bool
-SPI_is_cursor_plan(void *plan)
+SPI_is_cursor_plan(SPIPlanPtr plan)
{
- _SPI_plan *spiplan = (_SPI_plan *) plan;
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
- if (spiplan == NULL)
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
{
SPI_result = SPI_ERROR_ARGUMENT;
return false;
}
- if (list_length(spiplan->stmt_list_list) != 1)
+ if (list_length(plan->plancache_list) != 1)
return false; /* not exactly 1 pre-rewrite command */
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
- switch (ChoosePortalStrategy((List *) linitial(spiplan->stmt_list_list)))
+ if (plan->saved)
{
- case PORTAL_ONE_SELECT:
- case PORTAL_ONE_RETURNING:
- case PORTAL_UTIL_SELECT:
- /* OK */
- return true;
-
- case PORTAL_MULTI_QUERY:
- /* will not return tuples */
- break;
+ /* Make sure the plan is up to date */
+ cplan = RevalidateCachedPlan(plansource, true);
+ ReleaseCachedPlan(cplan, true);
}
+
+ /* Does it return tuples? */
+ if (plansource->resultDesc)
+ return true;
+
return false;
}
*
* At entry, plan->argtypes and plan->nargs must be valid.
*
- * Result lists are stored into *plan.
+ * Results are stored into *plan (specifically, plan->plancache_list).
+ * Note however that the result trees are all in CurrentMemoryContext
+ * and need to be copied somewhere to survive.
*/
static void
-_SPI_prepare_plan(const char *src, _SPI_plan *plan)
+_SPI_prepare_plan(const char *src, SPIPlanPtr plan)
{
List *raw_parsetree_list;
- List *stmt_list_list;
+ List *plancache_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
Oid *argtypes = plan->argtypes;
raw_parsetree_list = pg_parse_query(src);
/*
- * Do parse analysis and rule rewrite for each raw parsetree.
- *
- * We save the results from each raw parsetree as a separate sublist.
- * This allows _SPI_execute_plan() to know where the boundaries between
- * original queries fall.
+ * Do parse analysis and rule rewrite for each raw parsetree, then
+ * cons up a phony plancache entry for each one.
*/
- stmt_list_list = NIL;
+ plancache_list = NIL;
foreach(list_item, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(list_item);
- List *query_list;
-
- query_list = pg_analyze_and_rewrite(parsetree, src, argtypes, nargs);
-
- stmt_list_list = lappend(stmt_list_list,
- pg_plan_queries(query_list, NULL, false));
+ List *stmt_list;
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
+
+ /* Need a copyObject here to keep parser from modifying raw tree */
+ stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
+ src, argtypes, nargs);
+ stmt_list = pg_plan_queries(stmt_list, NULL, false);
+
+ plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ cplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
+
+ plansource->raw_parse_tree = parsetree;
+ /* cast-away-const here is a bit ugly, but there's no reason to copy */
+ plansource->query_string = (char *) src;
+ plansource->commandTag = CreateCommandTag(parsetree);
+ plansource->param_types = argtypes;
+ plansource->num_params = nargs;
+ plansource->fully_planned = true;
+ plansource->fixed_result = false;
+ plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
+ plansource->plan = cplan;
+
+ cplan->stmt_list = stmt_list;
+ cplan->fully_planned = true;
+
+ plancache_list = lappend(plancache_list, plansource);
}
- plan->stmt_list_list = stmt_list_list;
+ plan->plancache_list = plancache_list;
/*
* Pop the error context stack
* tcount: execution tuple-count limit, or 0 for none
*/
static int
-_SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
+_SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
Snapshot snapshot, Snapshot crosscheck_snapshot,
bool read_only, long tcount)
{
saveActiveSnapshot = ActiveSnapshot;
PG_TRY();
{
- ListCell *stmt_list_list_item;
+ ListCell *lc1;
ErrorContextCallback spierrcontext;
int nargs = plan->nargs;
ParamListInfo paramLI;
+ CachedPlan *cplan = NULL;
/* Convert parameters to form wanted by executor */
if (nargs > 0)
* Setup error traceback support for ereport()
*/
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = (void *) plan->query;
+ spierrcontext.arg = NULL;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
- foreach(stmt_list_list_item, plan->stmt_list_list)
+ foreach(lc1, plan->plancache_list)
{
- List *stmt_list = (List *) lfirst(stmt_list_list_item);
- ListCell *stmt_list_item;
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
+ List *stmt_list;
+ ListCell *lc2;
- foreach(stmt_list_item, stmt_list)
+ spierrcontext.arg = (void *) plansource->query_string;
+
+ if (plan->saved)
{
- Node *stmt = (Node *) lfirst(stmt_list_item);
+ /* Replan if needed, and increment plan refcount locally */
+ cplan = RevalidateCachedPlan(plansource, true);
+ stmt_list = cplan->stmt_list;
+ }
+ else
+ {
+ /* No replan here */
+ cplan = NULL;
+ stmt_list = plansource->plan->stmt_list;
+ }
+
+ foreach(lc2, stmt_list)
+ {
+ Node *stmt = (Node *) lfirst(lc2);
bool canSetTag;
QueryDesc *qdesc;
DestReceiver *dest;
goto fail;
}
}
+
+ /* Done with this plan, so release refcount */
+ if (cplan)
+ ReleaseCachedPlan(cplan, true);
+ cplan = NULL;
}
fail:
+ /* We no longer need the cached plan refcount, if any */
+ if (cplan)
+ ReleaseCachedPlan(cplan, true);
+
/*
* Pop the error context stack
*/
return failed;
}
-static _SPI_plan *
-_SPI_copy_plan(_SPI_plan *plan, int location)
+/*
+ * Make an "unsaved" copy of the given plan, in a child context of parentcxt.
+ */
+static SPIPlanPtr
+_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
{
- _SPI_plan *newplan;
- MemoryContext oldcxt;
+ SPIPlanPtr newplan;
MemoryContext plancxt;
- MemoryContext parentcxt;
+ MemoryContext oldcxt;
+ ListCell *lc;
- /* Determine correct parent for the plan's memory context */
- if (location == _SPI_CPLAN_PROCXT)
- parentcxt = _SPI_current->procCxt;
- else if (location == _SPI_CPLAN_TOPCXT)
- parentcxt = TopMemoryContext;
- else
- /* (this case not currently used) */
- parentcxt = CurrentMemoryContext;
+ Assert(!plan->saved); /* not currently supported */
/*
* Create a memory context for the plan. We don't expect the plan to be
oldcxt = MemoryContextSwitchTo(plancxt);
/* Copy the SPI plan into its own context */
- newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
+ newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
+ newplan->magic = _SPI_PLAN_MAGIC;
+ newplan->saved = false;
+ newplan->plancache_list = NIL;
newplan->plancxt = plancxt;
- newplan->query = pstrdup(plan->query);
- newplan->stmt_list_list = (List *) copyObject(plan->stmt_list_list);
newplan->nargs = plan->nargs;
if (plan->nargs > 0)
{
else
newplan->argtypes = NULL;
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+ CachedPlanSource *newsource;
+ CachedPlan *cplan;
+ CachedPlan *newcplan;
+
+ /* Note: we assume we don't need to revalidate the plan */
+ cplan = plansource->plan;
+
+ newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
+ newcplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
+
+ newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
+ newsource->query_string = pstrdup(plansource->query_string);
+ newsource->commandTag = plansource->commandTag;
+ newsource->param_types = newplan->argtypes;
+ newsource->num_params = newplan->nargs;
+ newsource->fully_planned = plansource->fully_planned;
+ newsource->fixed_result = plansource->fixed_result;
+ if (plansource->resultDesc)
+ newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
+ newsource->plan = newcplan;
+
+ newcplan->stmt_list = copyObject(cplan->stmt_list);
+ newcplan->fully_planned = cplan->fully_planned;
+
+ newplan->plancache_list = lappend(newplan->plancache_list, newsource);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return newplan;
+}
+
+/*
+ * Make a "saved" copy of the given plan, entrusting everything to plancache.c
+ */
+static SPIPlanPtr
+_SPI_save_plan(SPIPlanPtr plan)
+{
+ SPIPlanPtr newplan;
+ MemoryContext plancxt;
+ MemoryContext oldcxt;
+ ListCell *lc;
+
+ Assert(!plan->saved); /* not currently supported */
+
+ /*
+ * Create a memory context for the plan. We don't expect the plan to be
+ * very large, so use smaller-than-default alloc parameters.
+ */
+ plancxt = AllocSetContextCreate(CacheMemoryContext,
+ "SPI Plan",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ oldcxt = MemoryContextSwitchTo(plancxt);
+
+ /* Copy the SPI plan into its own context */
+ newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
+ newplan->magic = _SPI_PLAN_MAGIC;
+ newplan->saved = true;
+ newplan->plancache_list = NIL;
+ newplan->plancxt = plancxt;
+ newplan->nargs = plan->nargs;
+ if (plan->nargs > 0)
+ {
+ newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
+ memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
+ }
+ else
+ newplan->argtypes = NULL;
+
+ foreach(lc, plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
+ CachedPlanSource *newsource;
+ CachedPlan *cplan;
+
+ /* Note: we assume we don't need to revalidate the plan */
+ cplan = plansource->plan;
+
+ newsource = CreateCachedPlan(plansource->raw_parse_tree,
+ plansource->query_string,
+ plansource->commandTag,
+ newplan->argtypes,
+ newplan->nargs,
+ cplan->stmt_list,
+ true,
+ false);
+
+ newplan->plancache_list = lappend(newplan->plancache_list, newsource);
+ }
+
MemoryContextSwitchTo(oldcxt);
return newplan;
* across query and transaction boundaries, in fact they live as long as
* the backend does. This works because the hashtable structures
* themselves are allocated by dynahash.c in its permanent DynaHashCxt,
- * and the parse/plan node trees they point to are copied into
- * TopMemoryContext using SPI_saveplan(). This is pretty ugly, since there
- * is no way to free a no-longer-needed plan tree, but then again we don't
- * yet have any bookkeeping that would allow us to detect that a plan isn't
- * needed anymore. Improve it someday.
+ * and the SPI plans they point to are saved using SPI_saveplan().
+ * There is not currently any provision for throwing away a no-longer-needed
+ * plan --- consider improving this someday.
*
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.91 2007/02/14 01:58:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.92 2007/03/15 23:12:06 tgl Exp $
*
* ----------
*/
#include "catalog/pg_constraint.h"
#include "catalog/pg_operator.h"
#include "commands/trigger.h"
-#include "executor/spi_priv.h"
+#include "executor/spi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
#include "miscadmin.h"
typedef struct RI_QueryHashEntry
{
RI_QueryKey key;
- void *plan;
+ SPIPlanPtr plan;
} RI_QueryHashEntry;
const RI_ConstraintInfo *riinfo);
static void ri_InitHashTables(void);
-static void *ri_FetchPreparedPlan(RI_QueryKey *key);
-static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
+static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan);
static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
int tgkind);
static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
Trigger *trigger, Relation trig_rel, bool rel_is_pk);
-static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
+static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
bool cache_plan);
-static bool ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+static bool ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan,
Relation fk_rel, Relation pk_rel,
HeapTuple old_tuple, HeapTuple new_tuple,
bool detectNewRows,
HeapTuple old_row;
Buffer new_row_buf;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
HeapTuple old_row,
const RI_ConstraintInfo *riinfo)
{
- void *qplan;
+ SPIPlanPtr qplan;
RI_QueryKey qkey;
int i;
bool result;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
int j;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
bool use_cached_query;
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
/*
* Check that this is a valid trigger call on the right time and event.
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
/*
* Check that this is a valid trigger call on the right time and event.
int old_work_mem;
char workmembuf[32];
int spi_result;
- void *qplan;
+ SPIPlanPtr qplan;
/*
* Check to make sure current user has enough permissions to do the test
* If cache_plan is true, the plan is saved into our plan hashtable
* so that we don't need to plan it again.
*/
-static void *
+static SPIPlanPtr
ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
bool cache_plan)
{
- void *qplan;
+ SPIPlanPtr qplan;
Relation query_rel;
Oid save_uid;
* Perform a query to enforce an RI restriction
*/
static bool
-ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan,
Relation fk_rel, Relation pk_rel,
HeapTuple old_tuple, HeapTuple new_tuple,
bool detectNewRows,
* and saved SPI execution plans. Return the plan if found or NULL.
* ----------
*/
-static void *
+static SPIPlanPtr
ri_FetchPreparedPlan(RI_QueryKey *key)
{
RI_QueryHashEntry *entry;
* ----------
*/
static void
-ri_HashPreparedPlan(RI_QueryKey *key, void *plan)
+ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
{
RI_QueryHashEntry *entry;
bool found;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.252 2007/02/27 23:48:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.253 2007/03/15 23:12:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Global data
* ----------
*/
-static void *plan_getrulebyoid = NULL;
+static SPIPlanPtr plan_getrulebyoid = NULL;
static char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
-static void *plan_getviewrule = NULL;
+static SPIPlanPtr plan_getviewrule = NULL;
static char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
if (plan_getrulebyoid == NULL)
{
Oid argtypes[1];
- void *plan;
+ SPIPlanPtr plan;
argtypes[0] = OIDOID;
plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
if (plan_getviewrule == NULL)
{
Oid argtypes[2];
- void *plan;
+ SPIPlanPtr plan;
argtypes[0] = OIDOID;
argtypes[1] = NAMEOID;
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.34 2007/03/03 19:32:55 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.35 2007/03/15 23:12:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
const char *targetns = _textout(PG_GETARG_TEXT_P(3));
const char *result;
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
SPI_connect();
const char *targetns = _textout(PG_GETARG_TEXT_P(3));
const char *xmlschema;
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
SPI_connect();
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.1 2007/03/13 00:33:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.2 2007/03/15 23:12:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
void *arg);
static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
static bool rowmark_member(List *rowMarks, int rt_index);
-static TupleDesc ComputeResultDesc(List *stmt_list);
static void PlanCacheCallback(Datum arg, Oid relid);
static void InvalRelid(Oid relid, LOCKMODE lockmode,
InvalRelidContext *context);
plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result;
plansource->generation = 0; /* StoreCachedPlan will increment */
- plansource->resultDesc = ComputeResultDesc(stmt_list);
+ plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
plansource->plan = NULL;
plansource->context = source_context;
plansource->orig_plan = NULL;
plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result;
plansource->generation = 0; /* StoreCachedPlan will increment */
- plansource->resultDesc = ComputeResultDesc(stmt_list);
+ plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
plansource->plan = NULL;
plansource->context = context;
plansource->orig_plan = NULL;
{
/*
* Make a dedicated memory context for the CachedPlan and its
- * subsidiary data.
+ * subsidiary data. It's probably not going to be large, but
+ * just in case, use the default maxsize parameter.
*/
plan_context = AllocSetContextCreate(CacheMemoryContext,
"CachedPlan",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Check or update the result tupdesc. XXX should we use a weaker
* condition than equalTupleDescs() here?
*/
- resultDesc = ComputeResultDesc(slist);
+ resultDesc = PlanCacheComputeResultDesc(slist);
if (resultDesc == NULL && plansource->resultDesc == NULL)
{
/* OK, doesn't return tuples */
}
/*
- * ComputeResultDesc: given a list of either fully-planned statements or
- * Queries, determine the result tupledesc it will produce. Returns NULL
+ * PlanCacheComputeResultDesc: given a list of either fully-planned statements
+ * or Queries, determine the result tupledesc it will produce. Returns NULL
* if the execution will not return tuples.
*
* Note: the result is created or copied into current memory context.
*/
-static TupleDesc
-ComputeResultDesc(List *stmt_list)
+TupleDesc
+PlanCacheComputeResultDesc(List *stmt_list)
{
Node *node;
Query *query;
/*-------------------------------------------------------------------------
*
* spi.h
+ * Server Programming Interface public declarations
*
- * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.58 2006/10/04 00:30:08 momjian Exp $
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.59 2007/03/15 23:12:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "executor/execdefs.h"
#include "executor/executor.h"
#include "nodes/execnodes.h"
#include "nodes/params.h"
#include "utils/datum.h"
#include "utils/portal.h"
#include "utils/syscache.h"
-#include "executor/execdefs.h"
-typedef struct
+
+typedef struct SPITupleTable
{
MemoryContext tuptabcxt; /* memory context of result table */
uint32 alloced; /* # of alloced vals */
HeapTuple *vals; /* tuples */
} SPITupleTable;
+/* Plans are opaque structs for standard users of SPI */
+typedef struct _SPI_plan *SPIPlanPtr;
+
#define SPI_ERROR_CONNECT (-1)
#define SPI_ERROR_COPY (-2)
#define SPI_ERROR_OPUNKNOWN (-3)
extern void SPI_pop(void);
extern void SPI_restore_connection(void);
extern int SPI_execute(const char *src, bool read_only, long tcount);
-extern int SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
+extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
bool read_only, long tcount);
extern int SPI_exec(const char *src, long tcount);
-extern int SPI_execp(void *plan, Datum *Values, const char *Nulls,
+extern int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls,
long tcount);
-extern int SPI_execute_snapshot(void *plan,
+extern int SPI_execute_snapshot(SPIPlanPtr plan,
Datum *Values, const char *Nulls,
Snapshot snapshot,
Snapshot crosscheck_snapshot,
bool read_only, long tcount);
-extern void *SPI_prepare(const char *src, int nargs, Oid *argtypes);
-extern void *SPI_saveplan(void *plan);
-extern int SPI_freeplan(void *plan);
+extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes);
+extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
+extern int SPI_freeplan(SPIPlanPtr plan);
-extern Oid SPI_getargtypeid(void *plan, int argIndex);
-extern int SPI_getargcount(void *plan);
-extern bool SPI_is_cursor_plan(void *plan);
+extern Oid SPI_getargtypeid(SPIPlanPtr plan, int argIndex);
+extern int SPI_getargcount(SPIPlanPtr plan);
+extern bool SPI_is_cursor_plan(SPIPlanPtr plan);
extern const char *SPI_result_code_string(int code);
extern HeapTuple SPI_copytuple(HeapTuple tuple);
extern void SPI_freetuple(HeapTuple pointer);
extern void SPI_freetuptable(SPITupleTable *tuptable);
-extern Portal SPI_cursor_open(const char *name, void *plan,
+extern Portal SPI_cursor_open(const char *name, SPIPlanPtr plan,
Datum *Values, const char *Nulls, bool read_only);
extern Portal SPI_cursor_find(const char *name);
extern void SPI_cursor_fetch(Portal portal, bool forward, long count);
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.27 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.28 2007/03/15 23:12:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "executor/spi.h"
+#define _SPI_PLAN_MAGIC 569278163
+
typedef struct
{
/* current results */
MemoryContext procCxt; /* procedure context */
MemoryContext execCxt; /* executor context */
- MemoryContext savedcxt;
+ MemoryContext savedcxt; /* context of SPI_connect's caller */
SubTransactionId connectSubid; /* ID of connecting subtransaction */
} _SPI_connection;
-typedef struct
+/*
+ * SPI plans have two states: saved or unsaved.
+ *
+ * For an unsaved plan, the _SPI_plan struct and all its subsidiary data are in
+ * a dedicated memory context identified by plancxt. An unsaved plan is good
+ * at most for the current transaction, since the locks that protect it from
+ * schema changes will be lost at end of transaction. Hence the plancxt is
+ * always a transient one.
+ *
+ * For a saved plan, the _SPI_plan struct and the argument type array are in
+ * the plancxt (which can be really small). All the other subsidiary state
+ * is in plancache entries identified by plancache_list (note: the list cells
+ * themselves are in plancxt). We rely on plancache.c to keep the cache
+ * entries up-to-date as needed. The plancxt is a child of CacheMemoryContext
+ * since it should persist until explicitly destroyed.
+ *
+ * To avoid redundant coding, the representation of unsaved plans matches
+ * that of saved plans, ie, plancache_list is a list of CachedPlanSource
+ * structs which in turn point to CachedPlan structs. However, in an unsaved
+ * plan all these structs are just created by spi.c and are not known to
+ * plancache.c. We don't try very hard to make all their fields valid,
+ * only the ones spi.c actually uses.
+ *
+ * Note: if the original query string contained only whitespace and comments,
+ * the plancache_list will be NIL and so there is no place to store the
+ * query string. We don't care about that, but we do care about the
+ * argument type array, which is why it's seemingly-redundantly stored.
+ */
+typedef struct _SPI_plan
{
- /* Context containing _SPI_plan itself as well as subsidiary data */
- MemoryContext plancxt;
- /* Original query string (used for error reporting) */
- const char *query;
- /*
- * List of List of PlannedStmts and utility stmts; one sublist per
- * original parsetree
- */
- List *stmt_list_list;
- /* Argument types, if a prepared plan */
- int nargs;
- Oid *argtypes;
+ int magic; /* should equal _SPI_PLAN_MAGIC */
+ bool saved; /* saved or unsaved plan? */
+ List *plancache_list; /* one CachedPlanSource per parsetree */
+ MemoryContext plancxt; /* Context containing _SPI_plan and data */
+ int nargs; /* number of plan arguments */
+ Oid *argtypes; /* Argument types (NULL if nargs is 0) */
} _SPI_plan;
-
-#define _SPI_CPLAN_CURCXT 0
-#define _SPI_CPLAN_PROCXT 1
-#define _SPI_CPLAN_TOPCXT 2
-
#endif /* SPI_PRIV_H */
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.1 2007/03/13 00:33:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.2 2007/03/15 23:12:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
bool useResOwner);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
#endif /* PLANCACHE_H */
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.189 2007/02/20 17:32:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.190 2007/03/15 23:12:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
PLpgSQL_expr *expr);
static bool exec_simple_check_node(Node *node);
static void exec_simple_check_plan(PLpgSQL_expr *expr);
-static Datum exec_eval_simple_expr(PLpgSQL_execstate *estate,
+static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
+ Datum *result,
bool *isNull,
Oid *rettype);
void *tmp;
len = datumGetSize(estate.retval, false, func->fn_rettyplen);
- tmp = (void *) SPI_palloc(len);
+ tmp = SPI_palloc(len);
memcpy(tmp, DatumGetPointer(estate.retval), len);
estate.retval = PointerGetDatum(tmp);
}
PLpgSQL_expr *expr)
{
int i;
- _SPI_plan *spi_plan;
- void *plan;
+ SPIPlanPtr plan;
Oid *argtypes;
/*
}
}
expr->plan = SPI_saveplan(plan);
- spi_plan = (_SPI_plan *) expr->plan;
- expr->plan_argtypes = spi_plan->argtypes;
- expr->expr_simple_expr = NULL;
+ SPI_freeplan(plan);
+ plan = expr->plan;
+ expr->plan_argtypes = plan->argtypes;
exec_simple_check_plan(expr);
- SPI_freeplan(plan);
pfree(argtypes);
}
*/
if (expr->plan == NULL)
{
- _SPI_plan *spi_plan;
ListCell *l;
exec_prepare_plan(estate, expr);
stmt->mod_stmt = false;
- spi_plan = (_SPI_plan *) expr->plan;
- foreach(l, spi_plan->stmt_list_list)
+ foreach(l, expr->plan->plancache_list)
{
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
ListCell *l2;
- foreach(l2, (List *) lfirst(l))
+ foreach(l2, plansource->plan->stmt_list)
{
PlannedStmt *p = (PlannedStmt *) lfirst(l2);
PLpgSQL_row *row = NULL;
SPITupleTable *tuptab;
int n;
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
bool found = false;
Datum queryD;
Oid restype;
char *querystr;
- void *curplan;
+ SPIPlanPtr curplan;
/* ----------
* We evaluate the string expression after the
bool *isNull,
Oid *rettype)
{
+ Datum result;
int rc;
/*
- * If not already done create a plan for this expression
+ * If first time through, create a plan for this expression.
*/
if (expr->plan == NULL)
exec_prepare_plan(estate, expr);
* If this is a simple expression, bypass SPI and use the executor
* directly
*/
- if (expr->expr_simple_expr != NULL)
- return exec_eval_simple_expr(estate, expr, isNull, rettype);
+ if (exec_eval_simple_expr(estate, expr, &result, isNull, rettype))
+ return result;
+ /*
+ * Else do it the hard way via exec_run_select
+ */
rc = exec_run_select(estate, expr, 2, NULL);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
* exec_eval_simple_expr - Evaluate a simple expression returning
* a Datum by directly calling ExecEvalExpr().
*
+ * If successful, store results into *result, *isNull, *rettype and return
+ * TRUE. If the expression is not simple (any more), return FALSE.
+ *
+ * It is possible though unlikely for a simple expression to become non-simple
+ * (consider for example redefining a trivial view). We must handle that for
+ * correctness; fortunately it's normally inexpensive to do
+ * RevalidateCachedPlan on a simple expression. We do not consider the other
+ * direction (non-simple expression becoming simple) because we'll still give
+ * correct results if that happens, and it's unlikely to be worth the cycles
+ * to check.
+ *
* Note: if pass-by-reference, the result is in the eval_econtext's
* temporary memory context. It will be freed when exec_eval_cleanup
* is done.
* ----------
*/
-static Datum
+static bool
exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
+ Datum *result,
bool *isNull,
Oid *rettype)
{
- Datum retval;
ExprContext *econtext = estate->eval_econtext;
+ CachedPlanSource *plansource;
+ CachedPlan *cplan;
ParamListInfo paramLI;
int i;
Snapshot saveActiveSnapshot;
+ /*
+ * Forget it if expression wasn't simple before.
+ */
+ if (expr->expr_simple_expr == NULL)
+ return false;
+
+ /*
+ * Revalidate cached plan, so that we will notice if it became stale.
+ * (We also need to hold a refcount while using the plan.) Note that
+ * even if replanning occurs, the length of plancache_list can't change,
+ * since it is a property of the raw parsetree generated from the query
+ * text.
+ */
+ Assert(list_length(expr->plan->plancache_list) == 1);
+ plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
+ cplan = RevalidateCachedPlan(plansource, true);
+ if (cplan->generation != expr->expr_simple_generation)
+ {
+ /* It got replanned ... is it still simple? */
+ exec_simple_check_plan(expr);
+ if (expr->expr_simple_expr == NULL)
+ {
+ /* Ooops, release refcount and fail */
+ ReleaseCachedPlan(cplan, true);
+ return false;
+ }
+ }
+
/*
* Pass back previously-determined result type.
*/
/*
* Prepare the expression for execution, if it's not been done already in
- * the current eval_estate.
+ * the current eval_estate. (This will be forced to happen if we called
+ * exec_simple_check_plan above.)
*/
if (expr->expr_simple_id != estate->eval_estate_simple_id)
{
/*
* Finally we can call the executor to evaluate the expression
*/
- retval = ExecEvalExpr(expr->expr_simple_state,
- econtext,
- isNull,
- NULL);
+ *result = ExecEvalExpr(expr->expr_simple_state,
+ econtext,
+ isNull,
+ NULL);
MemoryContextSwitchTo(oldcontext);
}
PG_CATCH();
ActiveSnapshot = saveActiveSnapshot;
SPI_pop();
+ /*
+ * Now we can release our refcount on the cached plan.
+ */
+ ReleaseCachedPlan(cplan, true);
+
/*
* That's it.
*/
- return retval;
+ return true;
}
static void
exec_simple_check_plan(PLpgSQL_expr *expr)
{
- _SPI_plan *spi_plan = (_SPI_plan *) expr->plan;
- List *sublist;
+ CachedPlanSource *plansource;
PlannedStmt *stmt;
Plan *plan;
TargetEntry *tle;
+ /*
+ * Initialize to "not simple", and remember the plan generation number
+ * we last checked. (If the query produces more or less than one parsetree
+ * we just leave expr_simple_generation set to 0.)
+ */
expr->expr_simple_expr = NULL;
+ expr->expr_simple_generation = 0;
/*
* 1. We can only evaluate queries that resulted in one single execution
* plan
*/
- if (list_length(spi_plan->stmt_list_list) != 1)
+ if (list_length(expr->plan->plancache_list) != 1)
return;
- sublist = (List *) linitial(spi_plan->stmt_list_list);
- if (list_length(sublist) != 1)
+ plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
+ expr->expr_simple_generation = plansource->generation;
+ if (list_length(plansource->plan->stmt_list) != 1)
return;
- stmt = (PlannedStmt *) linitial(sublist);
+ stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
/*
* 2. It must be a RESULT plan --> no scan's required
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.85 2007/02/09 03:35:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.86 2007/03/15 23:12:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
int dtype;
int exprno;
char *query;
- void *plan;
+ SPIPlanPtr plan;
Oid *plan_argtypes;
/* fields for "simple expression" fast-path execution: */
Expr *expr_simple_expr; /* NULL means not a simple expr */
- Oid expr_simple_type;
+ int expr_simple_generation; /* plancache generation we checked */
+ Oid expr_simple_type; /* result type Oid, if simple */
/*
* if expr is simple AND prepared in current eval_estate,
4567890123456789 | 2283945061728394
(5 rows)
+-- Check basic SPI plan invalidation
+create function cache_test(int) returns int as $$
+declare total int;
+begin
+ create temp table t1(f1 int);
+ insert into t1 values($1);
+ insert into t1 values(11);
+ insert into t1 values(12);
+ insert into t1 values(13);
+ select sum(f1) into total from t1;
+ drop table t1;
+ return total;
+end
+$$ language plpgsql;
+select cache_test(1);
+ cache_test
+------------
+ 37
+(1 row)
+
+select cache_test(2);
+ cache_test
+------------
+ 38
+(1 row)
+
+select cache_test(3);
+ cache_test
+------------
+ 39
+(1 row)
+
+-- Check invalidation of plpgsql "simple expression"
+create temp view v1 as
+ select 2+2 as f1;
+create function cache_test_2() returns int as $$
+begin
+ return f1 from v1;
+end$$ language plpgsql;
+select cache_test_2();
+ cache_test_2
+--------------
+ 4
+(1 row)
+
+create or replace temp view v1 as
+ select 2+2+4 as f1;
+select cache_test_2();
+ cache_test_2
+--------------
+ 8
+(1 row)
+
+create or replace temp view v1 as
+ select 2+2+4+(select max(unique1) from tenk1) as f1;
+select cache_test_2();
+ cache_test_2
+--------------
+ 10007
+(1 row)
+
/*
- * $PostgreSQL: pgsql/src/test/regress/regress.c,v 1.69 2007/02/01 19:10:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/test/regress/regress.c,v 1.70 2007/03/15 23:12:07 tgl Exp $
*/
#include "postgres.h"
#define TTDUMMY_INFINITY 999999
-static void *splan = NULL;
+static SPIPlanPtr splan = NULL;
static bool ttoff = false;
PG_FUNCTION_INFO_V1(ttdummy);
/* if there is no plan ... */
if (splan == NULL)
{
- void *pplan;
+ SPIPlanPtr pplan;
Oid *ctypes;
char *query;
CREATE OR REPLACE TEMP VIEW voo AS SELECT q1, q2/2 AS q2 FROM foo;
EXECUTE vprep;
+
+-- Check basic SPI plan invalidation
+
+create function cache_test(int) returns int as $$
+declare total int;
+begin
+ create temp table t1(f1 int);
+ insert into t1 values($1);
+ insert into t1 values(11);
+ insert into t1 values(12);
+ insert into t1 values(13);
+ select sum(f1) into total from t1;
+ drop table t1;
+ return total;
+end
+$$ language plpgsql;
+
+select cache_test(1);
+select cache_test(2);
+select cache_test(3);
+
+-- Check invalidation of plpgsql "simple expression"
+
+create temp view v1 as
+ select 2+2 as f1;
+
+create function cache_test_2() returns int as $$
+begin
+ return f1 from v1;
+end$$ language plpgsql;
+
+select cache_test_2();
+
+create or replace temp view v1 as
+ select 2+2+4 as f1;
+select cache_test_2();
+
+create or replace temp view v1 as
+ select 2+2+4+(select max(unique1) from tenk1) as f1;
+select cache_test_2();