aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/executor/spi.c402
-rw-r--r--src/backend/utils/adt/ri_triggers.c58
-rw-r--r--src/backend/utils/adt/ruleutils.c10
-rw-r--r--src/backend/utils/adt/xml.c6
-rw-r--r--src/backend/utils/cache/plancache.c24
-rw-r--r--src/include/executor/spi.h34
-rw-r--r--src/include/executor/spi_priv.h59
-rw-r--r--src/include/utils/plancache.h3
-rw-r--r--src/pl/plpgsql/src/pl_exec.c119
-rw-r--r--src/pl/plpgsql/src/plpgsql.h7
-rw-r--r--src/test/regress/expected/plancache.out61
-rw-r--r--src/test/regress/regress.c6
-rw-r--r--src/test/regress/sql/plancache.sql40
13 files changed, 587 insertions, 242 deletions
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index f538f508ba3..7a34add7105 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.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 $
*
*-------------------------------------------------------------------------
*/
@@ -34,9 +34,9 @@ static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
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);
@@ -48,7 +48,8 @@ static void _SPI_error_callback(void *arg);
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);
@@ -306,10 +307,8 @@ SPI_execute(const char *src, bool read_only, long tcount)
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);
@@ -330,22 +329,22 @@ SPI_exec(const char *src, long tcount)
/* 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);
@@ -356,7 +355,7 @@ SPI_execute_plan(void *plan, Datum *Values, const char *Nulls,
/* 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);
}
@@ -371,24 +370,24 @@ SPI_execp(void *plan, Datum *Values, const char *Nulls, long 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);
@@ -397,11 +396,11 @@ SPI_execute_snapshot(void *plan,
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))
{
@@ -413,27 +412,28 @@ SPI_prepare(const char *src, int nargs, Oid *argtypes)
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;
@@ -443,23 +443,36 @@ SPI_saveplan(void *plan)
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;
}
@@ -819,18 +832,18 @@ SPI_freetuptable(SPITupleTable *tuptable)
}
-
/*
* 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;
@@ -842,25 +855,23 @@ SPI_cursor_open(const char *name, void *plan,
* 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;
@@ -880,23 +891,20 @@ SPI_cursor_open(const char *name, void *plan,
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 = &paramLI->params[k];
- prm->ptype = spiplan->argtypes[k];
+ prm->ptype = plan->argtypes[k];
prm->pflags = 0;
prm->isnull = (Nulls && Nulls[k] == 'n');
if (prm->isnull)
@@ -915,21 +923,35 @@ SPI_cursor_open(const char *name, void *plan,
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.
@@ -1023,28 +1045,29 @@ SPI_cursor_close(Portal 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;
}
/*
@@ -1057,31 +1080,32 @@ SPI_getargcount(void *plan)
* 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;
}
@@ -1248,13 +1272,15 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
*
* 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;
@@ -1282,26 +1308,44 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
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
@@ -1319,7 +1363,7 @@ _SPI_prepare_plan(const char *src, _SPI_plan *plan)
* 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)
{
@@ -1334,10 +1378,11 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
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)
@@ -1366,18 +1411,34 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
* 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;
@@ -1510,10 +1571,19 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
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
*/
@@ -1772,22 +1842,18 @@ _SPI_checktuples(void)
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
@@ -1801,10 +1867,11 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
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)
{
@@ -1814,6 +1881,101 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
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;
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 4ba2bb7a98f..72200ced9dc 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -8,16 +8,14 @@
* 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 $
*
* ----------
*/
@@ -35,7 +33,7 @@
#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"
@@ -135,7 +133,7 @@ typedef struct RI_QueryKey
typedef struct RI_QueryHashEntry
{
RI_QueryKey key;
- void *plan;
+ SPIPlanPtr plan;
} RI_QueryHashEntry;
@@ -206,18 +204,18 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
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,
@@ -248,7 +246,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
HeapTuple old_row;
Buffer new_row_buf;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -542,7 +540,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
HeapTuple old_row,
const RI_ConstraintInfo *riinfo)
{
- void *qplan;
+ SPIPlanPtr qplan;
RI_QueryKey qkey;
int i;
bool result;
@@ -678,7 +676,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -855,7 +853,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -1040,7 +1038,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -1203,7 +1201,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
int j;
@@ -1397,7 +1395,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -1569,7 +1567,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -1744,7 +1742,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
Relation pk_rel;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
/*
@@ -1916,7 +1914,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey;
- void *qplan;
+ SPIPlanPtr qplan;
int i;
bool use_cached_query;
@@ -2130,7 +2128,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
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.
@@ -2313,7 +2311,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
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.
@@ -2637,7 +2635,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
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
@@ -3165,12 +3163,12 @@ ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
* 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;
@@ -3212,7 +3210,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
* 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,
@@ -3576,7 +3574,7 @@ ri_InitHashTables(void)
* and saved SPI execution plans. Return the plan if found or NULL.
* ----------
*/
-static void *
+static SPIPlanPtr
ri_FetchPreparedPlan(RI_QueryKey *key)
{
RI_QueryHashEntry *entry;
@@ -3606,7 +3604,7 @@ ri_FetchPreparedPlan(RI_QueryKey *key)
* ----------
*/
static void
-ri_HashPreparedPlan(RI_QueryKey *key, void *plan)
+ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
{
RI_QueryHashEntry *entry;
bool found;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f17061738a4..9ec0aa56f0a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -105,9 +105,9 @@ typedef struct
* 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";
@@ -250,7 +250,7 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
if (plan_getrulebyoid == NULL)
{
Oid argtypes[1];
- void *plan;
+ SPIPlanPtr plan;
argtypes[0] = OIDOID;
plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
@@ -380,7 +380,7 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
if (plan_getviewrule == NULL)
{
Oid argtypes[2];
- void *plan;
+ SPIPlanPtr plan;
argtypes[0] = OIDOID;
argtypes[1] = NAMEOID;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 921fe1d9f50..fa82837fd0c 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -7,7 +7,7 @@
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -1800,7 +1800,7 @@ query_to_xmlschema(PG_FUNCTION_ARGS)
const char *targetns = _textout(PG_GETARG_TEXT_P(3));
const char *result;
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
SPI_connect();
@@ -1871,7 +1871,7 @@ query_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
const char *targetns = _textout(PG_GETARG_TEXT_P(3));
const char *xmlschema;
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
SPI_connect();
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 95ed49818cb..61a576d35d9 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -33,7 +33,7 @@
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -77,7 +77,6 @@ static void ScanQueryForRelids(Query *parsetree,
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);
@@ -153,7 +152,7 @@ CreateCachedPlan(Node *raw_parse_tree,
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;
@@ -225,7 +224,7 @@ FastCreateCachedPlan(Node *raw_parse_tree,
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;
@@ -271,12 +270,13 @@ StoreCachedPlan(CachedPlanSource *plansource,
{
/*
* 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);
/*
@@ -445,7 +445,7 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
* 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 */
@@ -718,14 +718,14 @@ rowmark_member(List *rowMarks, int rt_index)
}
/*
- * 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;
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 9cbbc035dcc..c041726a45a 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -1,8 +1,12 @@
/*-------------------------------------------------------------------------
*
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -24,6 +28,7 @@
#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"
@@ -38,9 +43,9 @@
#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 */
@@ -49,6 +54,9 @@ typedef struct
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)
@@ -86,23 +94,23 @@ extern void SPI_push(void);
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);
@@ -123,7 +131,7 @@ extern void SPI_pfree(void *pointer);
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);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 5e65bd750ae..7ce7d0b0983 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -6,7 +6,7 @@
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -16,6 +16,8 @@
#include "executor/spi.h"
+#define _SPI_PLAN_MAGIC 569278163
+
typedef struct
{
/* current results */
@@ -25,29 +27,46 @@ typedef struct
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 */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 833ec473b13..4f03cd9e0a2 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -8,7 +8,7 @@
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -101,5 +101,6 @@ extern void DropCachedPlan(CachedPlanSource *plansource);
extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
bool useResOwner);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
#endif /* PLANCACHE_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 68a68a6634c..eb29d7333a8 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8,7 +8,7 @@
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -123,8 +123,9 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
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);
@@ -409,7 +410,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
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);
}
@@ -2294,8 +2295,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr)
{
int i;
- _SPI_plan *spi_plan;
- void *plan;
+ SPIPlanPtr plan;
Oid *argtypes;
/*
@@ -2343,12 +2343,11 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
}
}
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);
}
@@ -2374,17 +2373,16 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
*/
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);
@@ -2735,7 +2733,7 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
PLpgSQL_row *row = NULL;
SPITupleTable *tuptab;
int n;
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
bool found = false;
@@ -2959,7 +2957,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
Datum queryD;
Oid restype;
char *querystr;
- void *curplan;
+ SPIPlanPtr curplan;
/* ----------
* We evaluate the string expression after the
@@ -3860,10 +3858,11 @@ exec_eval_expr(PLpgSQL_execstate *estate,
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);
@@ -3872,9 +3871,12 @@ exec_eval_expr(PLpgSQL_execstate *estate,
* 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,
@@ -3994,31 +3996,73 @@ exec_run_select(PLpgSQL_execstate *estate,
* 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.
*/
*rettype = expr->expr_simple_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)
{
@@ -4086,10 +4130,10 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* 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();
@@ -4104,9 +4148,14 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
SPI_pop();
/*
+ * Now we can release our refcount on the cached plan.
+ */
+ ReleaseCachedPlan(cplan, true);
+
+ /*
* That's it.
*/
- return retval;
+ return true;
}
@@ -4673,25 +4722,31 @@ exec_simple_check_node(Node *node)
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
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 9f29fcd5da5..0b9b90f9170 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -8,7 +8,7 @@
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
@@ -173,11 +173,12 @@ typedef struct PLpgSQL_expr
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,
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index 4980a9ab68b..cac9cfd2574 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -100,3 +100,64 @@ EXECUTE vprep;
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)
+
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index 551b157a169..8f96382dfd6 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -1,5 +1,5 @@
/*
- * $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"
@@ -451,7 +451,7 @@ extern Datum set_ttdummy(PG_FUNCTION_ARGS);
#define TTDUMMY_INFINITY 999999
-static void *splan = NULL;
+static SPIPlanPtr splan = NULL;
static bool ttoff = false;
PG_FUNCTION_INFO_V1(ttdummy);
@@ -599,7 +599,7 @@ ttdummy(PG_FUNCTION_ARGS)
/* if there is no plan ... */
if (splan == NULL)
{
- void *pplan;
+ SPIPlanPtr pplan;
Oid *ctypes;
char *query;
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
index b952efe1972..0b34a62c3bd 100644
--- a/src/test/regress/sql/plancache.sql
+++ b/src/test/regress/sql/plancache.sql
@@ -51,3 +51,43 @@ EXECUTE vprep;
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();