From 95f6d2d20921b7c2dbec29bf2706fd9448208aa6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 15 Mar 2007 23:12:07 +0000 Subject: [PATCH] Make use of plancache module for SPI plans. In particular, since plpgsql uses SPI plans, this finally fixes the ancient gotcha that you can't drop and recreate a temp table used by a plpgsql function. Along the way, clean up SPI's API a little bit by declaring SPI plan pointers as "SPIPlanPtr" instead of "void *". This is cosmetic but helps to forestall simple programming mistakes. (I have changed some but not all of the callers to match; there are still some "void *"'s in contrib and the PL's. This is intentional so that we can see if anyone's compiler complains about it.) --- contrib/spi/refint.c | 10 +- contrib/spi/timetravel.c | 4 +- doc/src/sgml/spi.sgml | 100 +++--- src/backend/executor/spi.c | 402 +++++++++++++++++------- src/backend/utils/adt/ri_triggers.c | 58 ++-- src/backend/utils/adt/ruleutils.c | 10 +- src/backend/utils/adt/xml.c | 6 +- src/backend/utils/cache/plancache.c | 24 +- src/include/executor/spi.h | 34 +- src/include/executor/spi_priv.h | 59 ++-- src/include/utils/plancache.h | 3 +- src/pl/plpgsql/src/pl_exec.c | 119 +++++-- src/pl/plpgsql/src/plpgsql.h | 7 +- src/test/regress/expected/plancache.out | 61 ++++ src/test/regress/regress.c | 6 +- src/test/regress/sql/plancache.sql | 40 +++ 16 files changed, 656 insertions(+), 287 deletions(-) diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c index c0c55eb080..d88975d5e9 100644 --- a/contrib/spi/refint.c +++ b/contrib/spi/refint.c @@ -19,7 +19,7 @@ typedef struct { char *ident; int nplans; - void **splan; + SPIPlanPtr *splan; } EPlan; static EPlan *FPlans = NULL; @@ -163,7 +163,7 @@ check_primary_key(PG_FUNCTION_ARGS) */ if (plan->nplans <= 0) { - void *pplan; + SPIPlanPtr pplan; char sql[8192]; /* @@ -191,7 +191,7 @@ check_primary_key(PG_FUNCTION_ARGS) 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; } @@ -413,11 +413,11 @@ check_foreign_key(PG_FUNCTION_ARGS) */ 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++) { diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c index a1f69a10fb..4b238fa425 100644 --- a/contrib/spi/timetravel.c +++ b/contrib/spi/timetravel.c @@ -24,7 +24,7 @@ Datum get_timetravel(PG_FUNCTION_ARGS); typedef struct { char *ident; - void *splan; + SPIPlanPtr splan; } EPlan; static EPlan *Plans = NULL; /* for UPDATE/DELETE */ @@ -308,7 +308,7 @@ timetravel(PG_FUNCTION_ARGS) /* if there is no plan ... */ if (plan->splan == NULL) { - void *pplan; + SPIPlanPtr pplan; Oid *ctypes; char sql[8192]; char separ = ' '; diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index 90a68a5d01..c7c4304b3a 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -1,4 +1,4 @@ - + Server Programming Interface @@ -14,7 +14,7 @@ SQL commands inside their functions. SPI is a set of interface functions to simplify access to the parser, planner, - optimizer, and executor. SPI also does some + and executor. SPI also does some memory management. @@ -322,7 +322,8 @@ SPI_execute("INSERT INTO foo SELECT * FROM bar", false, 5); 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 @@ -699,7 +700,7 @@ int SPI_exec(const char * command, long count< -void * SPI_prepare(const char * command, int nargs, Oid * argtypes) +SPIPlanPtr SPI_prepare(const char * command, int nargs, Oid * argtypes) @@ -790,6 +791,13 @@ void * SPI_prepare(const char * command, int n 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 @@ -816,7 +824,7 @@ void * SPI_prepare(const char * command, int n -int SPI_getargcount(void * plan) +int SPI_getargcount(SPIPlanPtr plan) @@ -834,7 +842,7 @@ int SPI_getargcount(void * plan) - void * plan + SPIPlanPtr plan execution plan (returned by SPI_prepare) @@ -847,9 +855,10 @@ int SPI_getargcount(void * plan) 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. @@ -871,7 +880,7 @@ int SPI_getargcount(void * plan) -Oid SPI_getargtypeid(void * plan, int argIndex) +Oid SPI_getargtypeid(SPIPlanPtr plan, int argIndex) @@ -890,7 +899,7 @@ Oid SPI_getargtypeid(void * plan, int argIndex - void * plan + SPIPlanPtr plan execution plan (returned by SPI_prepare) @@ -912,11 +921,13 @@ Oid SPI_getargtypeid(void * plan, int argIndex 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 - plan + plan, + SPI_result is set to SPI_ERROR_ARGUMENT + and InvalidOid is returned. @@ -939,7 +950,7 @@ Oid SPI_getargtypeid(void * plan, int argIndex -bool SPI_is_cursor_plan(void * plan) +bool SPI_is_cursor_plan(SPIPlanPtr plan) @@ -964,7 +975,7 @@ bool SPI_is_cursor_plan(void * plan) - void * plan + SPIPlanPtr plan execution plan (returned by SPI_prepare) @@ -978,9 +989,10 @@ bool SPI_is_cursor_plan(void * plan) 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. @@ -1001,7 +1013,7 @@ bool SPI_is_cursor_plan(void * plan) -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) @@ -1022,7 +1034,7 @@ int SPI_execute_plan(void * plan, Datum * valu - void * plan + SPIPlanPtr plan execution plan (returned by SPI_prepare) @@ -1091,8 +1103,8 @@ int SPI_execute_plan(void * plan, Datum * valu SPI_ERROR_ARGUMENT - if plan is NULL or - count is less than 0 + if plan is NULL or invalid, + or count is less than 0 @@ -1143,7 +1155,7 @@ int SPI_execute_plan(void * plan, Datum * valu -int SPI_execp(void * plan, Datum * values, const char * nulls, long count) +int SPI_execp(SPIPlanPtr plan, Datum * values, const char * nulls, long count) @@ -1163,7 +1175,7 @@ int SPI_execp(void * plan, Datum * values - void * plan + SPIPlanPtr plan execution plan (returned by SPI_prepare) @@ -1242,7 +1254,7 @@ int SPI_execp(void * plan, Datum * values -Portal SPI_cursor_open(const char * name, void * plan, +Portal SPI_cursor_open(const char * name, SPIPlanPtr plan, Datum * values, const char * nulls, bool read_only) @@ -1268,6 +1280,11 @@ Portal SPI_cursor_open(const char * name, void * + + + The passed-in data will be copied into the cursor's portal, so it + can be freed while the cursor still exists. + @@ -1285,7 +1302,7 @@ Portal SPI_cursor_open(const char * name, void * - void * plan + SPIPlanPtr plan execution plan (returned by SPI_prepare) @@ -1602,7 +1619,7 @@ void SPI_cursor_close(Portal portal) -void * SPI_saveplan(void * plan) +SPIPlanPtr SPI_saveplan(SPIPlanPtr plan) @@ -1611,8 +1628,8 @@ void * SPI_saveplan(void * 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. @@ -1624,7 +1641,7 @@ void * SPI_saveplan(void * plan) - void * plan + SPIPlanPtr plan the plan to be saved @@ -1646,7 +1663,7 @@ void * SPI_saveplan(void * plan) SPI_ERROR_ARGUMENT - if plan is NULL + if plan is NULL or invalid @@ -1666,10 +1683,17 @@ void * SPI_saveplan(void * plan) 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. @@ -2879,7 +2903,7 @@ void SPI_freetuptable(SPITupleTable * tuptable) -int SPI_freeplan(void *plan) +int SPI_freeplan(SPIPlanPtr plan) @@ -2898,7 +2922,7 @@ int SPI_freeplan(void *plan) - void * plan + SPIPlanPtr plan pointer to plan to free @@ -2913,7 +2937,7 @@ int SPI_freeplan(void *plan) SPI_ERROR_ARGUMENT if plan - is NULL. + is NULL or invalid diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index f538f508ba..7a34add710 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 = ¶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) @@ -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 4ba2bb7a98..72200ced9d 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 f17061738a..9ec0aa56f0 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 921fe1d9f5..fa82837fd0 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 95ed49818c..61a576d35d 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 9cbbc035dc..c041726a45 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 5e65bd750a..7ce7d0b098 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 833ec473b1..4f03cd9e0a 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 68a68a6634..eb29d7333a 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,23 +3996,64 @@ 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. */ @@ -4018,7 +4061,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, /* * 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(); @@ -4103,10 +4147,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ActiveSnapshot = saveActiveSnapshot; 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 9f29fcd5da..0b9b90f917 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 4980a9ab68..cac9cfd257 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 551b157a16..8f96382dfd 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 b952efe197..0b34a62c3b 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(); -- 2.40.0