diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/catalog/namespace.c | 18 | ||||
-rw-r--r-- | src/backend/commands/prepare.c | 144 | ||||
-rw-r--r-- | src/backend/executor/spi.c | 363 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 226 | ||||
-rw-r--r-- | src/backend/utils/adt/ri_triggers.c | 4 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 6 | ||||
-rw-r--r-- | src/backend/utils/cache/plancache.c | 1521 | ||||
-rw-r--r-- | src/backend/utils/mmgr/mcxt.c | 12 | ||||
-rw-r--r-- | src/backend/utils/mmgr/portalmem.c | 6 | ||||
-rw-r--r-- | src/include/catalog/namespace.h | 1 | ||||
-rw-r--r-- | src/include/commands/prepare.h | 8 | ||||
-rw-r--r-- | src/include/executor/spi.h | 1 | ||||
-rw-r--r-- | src/include/executor/spi_priv.h | 41 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 3 | ||||
-rw-r--r-- | src/include/utils/memutils.h | 1 | ||||
-rw-r--r-- | src/include/utils/plancache.h | 156 | ||||
-rw-r--r-- | src/pl/plperl/plperl.c | 18 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 215 | ||||
-rw-r--r-- | src/pl/plpython/plpython.c | 11 | ||||
-rw-r--r-- | src/pl/tcl/pltcl.c | 17 | ||||
-rw-r--r-- | src/test/regress/regress.c | 5 |
21 files changed, 1644 insertions, 1133 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index fcb41a8adb8..040bef6addb 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -2941,6 +2941,24 @@ GetOverrideSearchPath(MemoryContext context) } /* + * CopyOverrideSearchPath - copy the specified OverrideSearchPath. + * + * The result structure is allocated in CurrentMemoryContext. + */ +OverrideSearchPath * +CopyOverrideSearchPath(OverrideSearchPath *path) +{ + OverrideSearchPath *result; + + result = (OverrideSearchPath *) palloc(sizeof(OverrideSearchPath)); + result->schemas = list_copy(path->schemas); + result->addCatalog = path->addCatalog; + result->addTemp = path->addTemp; + + return result; +} + +/* * PushOverrideSearchPath - temporarily override the search path * * We allow nested overrides, hence the push/pop terminology. The GUC diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index d929e14e0ed..a94921574fa 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -53,11 +53,11 @@ static Datum build_regtype_array(Oid *param_types, int num_params); void PrepareQuery(PrepareStmt *stmt, const char *queryString) { + CachedPlanSource *plansource; Oid *argtypes = NULL; int nargs; Query *query; - List *query_list, - *plan_list; + List *query_list; int i; /* @@ -69,6 +69,13 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), errmsg("invalid statement name: must not be empty"))); + /* + * Create the CachedPlanSource before we do parse analysis, since it needs + * to see the unmodified raw parse tree. + */ + plansource = CreateCachedPlan(stmt->query, queryString, + CreateCommandTag(stmt->query)); + /* Transform list of TypeNames to array of type OIDs */ nargs = list_length(stmt->argtypes); @@ -102,7 +109,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) * information about unknown parameters to be deduced from context. * * Because parse analysis scribbles on the raw querytree, we must make a - * copy to ensure we have a pristine raw tree to cache. FIXME someday. + * copy to ensure we don't modify the passed-in tree. FIXME someday. */ query = parse_analyze_varparams((Node *) copyObject(stmt->query), queryString, @@ -143,20 +150,22 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) /* Rewrite the query. The result could be 0, 1, or many queries. */ query_list = QueryRewrite(query); - /* Generate plans for queries. */ - plan_list = pg_plan_queries(query_list, 0, NULL); + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + query_list, + NULL, + argtypes, + nargs, + NULL, + NULL, + 0, /* default cursor options */ + true); /* fixed result */ /* * Save the results. */ StorePreparedStatement(stmt->name, - stmt->query, - queryString, - CreateCommandTag((Node *) query), - argtypes, - nargs, - 0, /* default cursor options */ - plan_list, + plansource, true); } @@ -185,10 +194,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, /* Look it up in the hash table */ entry = FetchPreparedStatement(stmt->name, true); - /* Shouldn't have a non-fully-planned plancache entry */ - if (!entry->plansource->fully_planned) - elog(ERROR, "EXECUTE does not support unplanned prepared statements"); - /* Shouldn't get any non-fixed-result cached plan, either */ + /* Shouldn't find a non-fixed-result cached plan */ if (!entry->plansource->fixed_result) elog(ERROR, "EXECUTE does not support variable-result cached plans"); @@ -197,7 +203,9 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, { /* * Need an EState to evaluate parameters; must not delete it till end - * of query, in case parameters are pass-by-reference. + * of query, in case parameters are pass-by-reference. Note that the + * passed-in "params" could possibly be referenced in the parameter + * expressions. */ estate = CreateExecutorState(); estate->es_param_list_info = params; @@ -226,7 +234,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, PlannedStmt *pstmt; /* Replan if needed, and increment plan refcount transiently */ - cplan = RevalidateCachedPlan(entry->plansource, true); + cplan = GetCachedPlan(entry->plansource, paramLI, true); /* Copy plan into portal's context, and modify */ oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); @@ -256,7 +264,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, else { /* Replan if needed, and increment plan refcount for portal */ - cplan = RevalidateCachedPlan(entry->plansource, false); + cplan = GetCachedPlan(entry->plansource, paramLI, false); plan_list = cplan->stmt_list; } @@ -396,7 +404,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params, ParamExternData *prm = ¶mLI->params[i]; prm->ptype = param_types[i]; - prm->pflags = 0; + prm->pflags = PARAM_FLAG_CONST; prm->value = ExecEvalExprSwitchContext(n, GetPerTupleExprContext(estate), &prm->isnull, @@ -430,54 +438,24 @@ InitQueryHashTable(void) /* * Store all the data pertaining to a query in the hash table using - * the specified key. All the given data is copied into either the hashtable - * entry or the underlying plancache entry, so the caller can dispose of its - * copy. - * - * Exception: commandTag is presumed to be a pointer to a constant string, - * or possibly NULL, so it need not be copied. Note that commandTag should - * be NULL only if the original query (before rewriting) was empty. + * the specified key. The passed CachedPlanSource should be "unsaved" + * in case we get an error here; we'll save it once we've created the hash + * table entry. */ void StorePreparedStatement(const char *stmt_name, - Node *raw_parse_tree, - const char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, + CachedPlanSource *plansource, bool from_sql) { PreparedStatement *entry; - CachedPlanSource *plansource; + TimestampTz cur_ts = GetCurrentStatementStartTimestamp(); bool found; /* Initialize the hash table, if necessary */ if (!prepared_queries) InitQueryHashTable(); - /* Check for pre-existing entry of same name */ - hash_search(prepared_queries, stmt_name, HASH_FIND, &found); - - if (found) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_PSTATEMENT), - errmsg("prepared statement \"%s\" already exists", - stmt_name))); - - /* Create a plancache entry */ - plansource = CreateCachedPlan(raw_parse_tree, - query_string, - commandTag, - param_types, - num_params, - cursor_options, - stmt_list, - true, - true); - - /* Now we can add entry to hash table */ + /* Add entry to hash table */ entry = (PreparedStatement *) hash_search(prepared_queries, stmt_name, HASH_ENTER, @@ -485,13 +463,18 @@ StorePreparedStatement(const char *stmt_name, /* Shouldn't get a duplicate entry */ if (found) - elog(ERROR, "duplicate prepared statement \"%s\"", - stmt_name); + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_PSTATEMENT), + errmsg("prepared statement \"%s\" already exists", + stmt_name))); /* Fill in the hash table entry */ entry->plansource = plansource; entry->from_sql = from_sql; - entry->prepare_time = GetCurrentStatementStartTimestamp(); + entry->prepare_time = cur_ts; + + /* Now it's safe to move the CachedPlanSource to permanent memory */ + SaveCachedPlan(plansource); } /* @@ -538,7 +521,7 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt) { /* * Since we don't allow prepared statements' result tupdescs to change, - * there's no need for a revalidate call here. + * there's no need to worry about revalidating the cached plan here. */ Assert(stmt->plansource->fixed_result); if (stmt->plansource->resultDesc) @@ -560,24 +543,12 @@ List * FetchPreparedStatementTargetList(PreparedStatement *stmt) { List *tlist; - CachedPlan *cplan; - - /* No point in looking if it doesn't return tuples */ - if (stmt->plansource->resultDesc == NULL) - return NIL; - /* Make sure the plan is up to date */ - cplan = RevalidateCachedPlan(stmt->plansource, true); - - /* Get the primary statement and find out what it returns */ - tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list)); - - /* Copy into caller's context so we can release the plancache entry */ - tlist = (List *) copyObject(tlist); - - ReleaseCachedPlan(cplan, true); + /* Get the plan's primary targetlist */ + tlist = CachedPlanGetTargetList(stmt->plansource); - return tlist; + /* Copy into caller's context in case plan gets invalidated */ + return (List *) copyObject(tlist); } /* @@ -662,26 +633,20 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, /* Look it up in the hash table */ entry = FetchPreparedStatement(execstmt->name, true); - /* Shouldn't have a non-fully-planned plancache entry */ - if (!entry->plansource->fully_planned) - elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements"); - /* Shouldn't get any non-fixed-result cached plan, either */ + /* Shouldn't find a non-fixed-result cached plan */ if (!entry->plansource->fixed_result) elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans"); query_string = entry->plansource->query_string; - /* Replan if needed, and acquire a transient refcount */ - cplan = RevalidateCachedPlan(entry->plansource, true); - - plan_list = cplan->stmt_list; - /* Evaluate parameters, if any */ if (entry->plansource->num_params) { /* * Need an EState to evaluate parameters; must not delete it till end - * of query, in case parameters are pass-by-reference. + * of query, in case parameters are pass-by-reference. Note that the + * passed-in "params" could possibly be referenced in the parameter + * expressions. */ estate = CreateExecutorState(); estate->es_param_list_info = params; @@ -689,6 +654,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, queryString, estate); } + /* Replan if needed, and acquire a transient refcount */ + cplan = GetCachedPlan(entry->plansource, paramLI, true); + + plan_list = cplan->stmt_list; + /* Explain each query */ foreach(p, plan_list) { @@ -714,7 +684,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, } else { - ExplainOneUtility((Node *) pstmt, es, query_string, params); + ExplainOneUtility((Node *) pstmt, es, query_string, paramLI); } /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index d71ea60b317..688279c716e 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -56,8 +56,7 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, bool read_only, bool fire_triggers, long tcount); static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, - Datum *Values, const char *Nulls, - int pflags); + Datum *Values, const char *Nulls); static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount); @@ -67,7 +66,7 @@ static void _SPI_cursor_operation(Portal portal, FetchDirection direction, long count, DestReceiver *dest); -static SPIPlanPtr _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt); +static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan); static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan); static int _SPI_begin_call(bool execmem); @@ -391,8 +390,7 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, res = _SPI_execute_plan(plan, _SPI_convert_params(plan->nargs, plan->argtypes, - Values, Nulls, - 0), + Values, Nulls), InvalidSnapshot, InvalidSnapshot, read_only, true, tcount); @@ -462,8 +460,7 @@ SPI_execute_snapshot(SPIPlanPtr plan, res = _SPI_execute_plan(plan, _SPI_convert_params(plan->nargs, plan->argtypes, - Values, Nulls, - 0), + Values, Nulls), snapshot, crosscheck_snapshot, read_only, fire_triggers, tcount); @@ -474,11 +471,8 @@ SPI_execute_snapshot(SPIPlanPtr plan, /* * SPI_execute_with_args -- plan and execute a query with supplied arguments * - * This is functionally comparable to SPI_prepare followed by - * SPI_execute_plan, except that since we know the plan will be used only - * once, we can tell the planner to rely on the parameter values as constants. - * This eliminates potential performance disadvantages compared to - * inserting the parameter values directly into the query text. + * This is functionally equivalent to SPI_prepare followed by + * SPI_execute_plan. */ int SPI_execute_with_args(const char *src, @@ -509,13 +503,10 @@ SPI_execute_with_args(const char *src, plan.parserSetupArg = NULL; paramLI = _SPI_convert_params(nargs, argtypes, - Values, Nulls, - PARAM_FLAG_CONST); + Values, Nulls); _SPI_prepare_plan(src, &plan, paramLI); - /* We don't need to copy the plan since it will be thrown away anyway */ - res = _SPI_execute_plan(&plan, paramLI, InvalidSnapshot, InvalidSnapshot, read_only, true, tcount); @@ -558,7 +549,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, _SPI_prepare_plan(src, &plan, NULL); /* copy plan to procedure context */ - result = _SPI_copy_plan(&plan, _SPI_current->procCxt); + result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); @@ -595,20 +586,45 @@ SPI_prepare_params(const char *src, _SPI_prepare_plan(src, &plan, NULL); /* copy plan to procedure context */ - result = _SPI_copy_plan(&plan, _SPI_current->procCxt); + result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); return result; } +int +SPI_keepplan(SPIPlanPtr plan) +{ + ListCell *lc; + + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved) + return SPI_ERROR_ARGUMENT; + + /* + * Mark it saved, reparent it under CacheMemoryContext, and mark all the + * component CachedPlanSources as saved. This sequence cannot fail + * partway through, so there's no risk of long-term memory leakage. + */ + plan->saved = true; + MemoryContextSetParent(plan->plancxt, CacheMemoryContext); + + foreach(lc, plan->plancache_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + + SaveCachedPlan(plansource); + } + + return 0; +} + SPIPlanPtr SPI_saveplan(SPIPlanPtr plan) { SPIPlanPtr newplan; - /* We don't currently support copying an already-saved plan */ - if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved) + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; @@ -620,8 +636,7 @@ SPI_saveplan(SPIPlanPtr plan) newplan = _SPI_save_plan(plan); - _SPI_curid--; - SPI_result = 0; + SPI_result = _SPI_end_call(false); return newplan; } @@ -629,20 +644,17 @@ SPI_saveplan(SPIPlanPtr plan) int SPI_freeplan(SPIPlanPtr plan) { + ListCell *lc; + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) return SPI_ERROR_ARGUMENT; - /* If plancache.c owns the plancache entries, we must release them */ - if (plan->saved) + /* Release the plancache entries */ + foreach(lc, plan->plancache_list) { - ListCell *lc; - - foreach(lc, plan->plancache_list) - { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); - DropCachedPlan(plansource); - } + DropCachedPlan(plansource); } /* Now get rid of the _SPI_plan and subsidiary data in its plancxt */ @@ -1020,8 +1032,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, /* build transient ParamListInfo in caller's context */ paramLI = _SPI_convert_params(plan->nargs, plan->argtypes, - Values, Nulls, - 0); + Values, Nulls); portal = SPI_cursor_open_internal(name, plan, paramLI, read_only); @@ -1036,9 +1047,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, /* * SPI_cursor_open_with_args() * - * Parse and plan a query and open it as a portal. Like SPI_execute_with_args, - * we can tell the planner to rely on the parameter values as constants, - * because the plan will only be used once. + * Parse and plan a query and open it as a portal. */ Portal SPI_cursor_open_with_args(const char *name, @@ -1071,8 +1080,7 @@ SPI_cursor_open_with_args(const char *name, /* build transient ParamListInfo in executor context */ paramLI = _SPI_convert_params(nargs, argtypes, - Values, Nulls, - PARAM_FLAG_CONST); + Values, Nulls); _SPI_prepare_plan(src, &plan, paramLI); @@ -1081,9 +1089,6 @@ SPI_cursor_open_with_args(const char *name, /* Adjust stack so that SPI_cursor_open_internal doesn't complain */ _SPI_curid--; - /* SPI_cursor_open_internal must be called in procedure memory context */ - _SPI_procmem(); - result = SPI_cursor_open_internal(name, &plan, paramLI, read_only); /* And clean up */ @@ -1148,7 +1153,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, plansource = (CachedPlanSource *) linitial(plan->plancache_list); /* Push the SPI stack */ - if (_SPI_begin_call(false) < 0) + if (_SPI_begin_call(true) < 0) elog(ERROR, "SPI_cursor_open called while not connected"); /* Reset SPI result (note we deliberately don't touch lastoid) */ @@ -1174,22 +1179,27 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, plansource->query_string); /* - * Note: we mustn't have any failure occur between RevalidateCachedPlan - * and PortalDefineQuery; that would result in leaking our plancache - * refcount. + * Note: for a saved plan, we mustn't have any failure occur between + * GetCachedPlan and PortalDefineQuery; that would result in leaking our + * plancache refcount. */ - if (plan->saved) - { - /* Replan if needed, and increment plan refcount for portal */ - cplan = RevalidateCachedPlan(plansource, false); - stmt_list = cplan->stmt_list; - } - else + + /* Replan if needed, and increment plan refcount for portal */ + cplan = GetCachedPlan(plansource, paramLI, false); + stmt_list = cplan->stmt_list; + + if (!plan->saved) { - /* No replan, but copy the plan into the portal's context */ + /* + * We don't want the portal to depend on an unsaved CachedPlanSource, + * so must copy the plan into the portal's context. An error here + * will result in leaking our refcount on the plan, but it doesn't + * matter because the plan is unsaved and hence transient anyway. + */ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - stmt_list = copyObject(plansource->plan->stmt_list); + stmt_list = copyObject(stmt_list); MemoryContextSwitchTo(oldcontext); + ReleaseCachedPlan(cplan, false); cplan = NULL; /* portal shouldn't depend on cplan */ } @@ -1238,9 +1248,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, /* * If told to be read-only, we'd better check for read-only queries. This * can't be done earlier because we need to look at the finished, planned - * queries. (In particular, we don't want to do it between - * RevalidateCachedPlan and PortalDefineQuery, because throwing an error - * between those steps would result in leaking our plancache refcount.) + * queries. (In particular, we don't want to do it between GetCachedPlan + * and PortalDefineQuery, because throwing an error between those steps + * would result in leaking our plancache refcount.) */ if (read_only) { @@ -1288,7 +1298,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, Assert(portal->strategy != PORTAL_MULTI_QUERY); /* Pop the SPI stack */ - _SPI_end_call(false); + _SPI_end_call(true); /* Return the created portal */ return portal; @@ -1420,7 +1430,6 @@ bool SPI_is_cursor_plan(SPIPlanPtr plan) { CachedPlanSource *plansource; - CachedPlan *cplan; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { @@ -1435,19 +1444,11 @@ SPI_is_cursor_plan(SPIPlanPtr plan) } plansource = (CachedPlanSource *) linitial(plan->plancache_list); - /* Need _SPI_begin_call in case replanning invokes SPI-using functions */ - SPI_result = _SPI_begin_call(false); - if (SPI_result < 0) - return false; - - if (plan->saved) - { - /* Make sure the plan is up to date */ - cplan = RevalidateCachedPlan(plansource, true); - ReleaseCachedPlan(cplan, true); - } - - _SPI_end_call(false); + /* + * We used to force revalidation of the cached plan here, but that seems + * unnecessary: invalidation could mean a change in the rowtype of the + * tuples returned by a plan, but not whether it returns tuples at all. + */ SPI_result = 0; /* Does it return tuples? */ @@ -1466,25 +1467,18 @@ SPI_is_cursor_plan(SPIPlanPtr plan) bool SPI_plan_is_valid(SPIPlanPtr plan) { - Assert(plan->magic == _SPI_PLAN_MAGIC); - if (plan->saved) - { - ListCell *lc; + ListCell *lc; - foreach(lc, plan->plancache_list) - { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + Assert(plan->magic == _SPI_PLAN_MAGIC); - if (!CachedPlanIsValid(plansource)) - return false; - } - return true; - } - else + foreach(lc, plan->plancache_list) { - /* An unsaved plan is assumed valid for its (short) lifetime */ - return true; + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + + if (!CachedPlanIsValid(plansource)) + return false; } + return true; } /* @@ -1646,7 +1640,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) */ /* - * Parse and plan a querystring. + * Parse and analyze a querystring. * * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup * and plan->parserSetupArg) must be valid, as must plan->cursor_options. @@ -1656,8 +1650,10 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * param type information embedded in the 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. + * Note that the result data is all in CurrentMemoryContext or child contexts + * thereof; in practice this means it is in the SPI executor context, and + * what we are creating is a "temporary" SPIPlan. Cruft generated during + * parsing is also left in CurrentMemoryContext. */ static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) @@ -1682,8 +1678,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) raw_parsetree_list = pg_parse_query(src); /* - * Do parse analysis, rule rewrite, and planning for each raw parsetree, - * then cons up a phony plancache entry for each one. + * Do parse analysis and rule rewrite for each raw parsetree, storing + * the results into unsaved plancache entries. */ plancache_list = NIL; @@ -1692,7 +1688,14 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) Node *parsetree = (Node *) lfirst(list_item); List *stmt_list; CachedPlanSource *plansource; - CachedPlan *cplan; + + /* + * Create the CachedPlanSource before we do parse analysis, since + * it needs to see the unmodified raw parse tree. + */ + plansource = CreateCachedPlan(parsetree, + src, + CreateCommandTag(parsetree)); /* * Parameter datatypes are driven by parserSetup hook if provided, @@ -1701,41 +1704,29 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) if (plan->parserSetup != NULL) { Assert(plan->nargs == 0); - /* Need a copyObject here to keep parser from modifying raw tree */ - stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree), + stmt_list = pg_analyze_and_rewrite_params(parsetree, src, plan->parserSetup, plan->parserSetupArg); } else { - /* Need a copyObject here to keep parser from modifying raw tree */ - stmt_list = pg_analyze_and_rewrite(copyObject(parsetree), + stmt_list = pg_analyze_and_rewrite(parsetree, src, plan->argtypes, plan->nargs); } - stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams); - - 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 = plan->argtypes; - plansource->num_params = plan->nargs; - plansource->parserSetup = plan->parserSetup; - plansource->parserSetupArg = plan->parserSetupArg; - plansource->fully_planned = true; - plansource->fixed_result = false; - /* no need to set search_path, generation or saved_xmin */ - plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); - plansource->plan = cplan; - - cplan->stmt_list = stmt_list; - cplan->fully_planned = true; + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + stmt_list, + NULL, + plan->argtypes, + plan->nargs, + plan->parserSetup, + plan->parserSetupArg, + cursor_options, + false); /* not fixed result */ plancache_list = lappend(plancache_list, plansource); } @@ -1824,18 +1815,12 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, spierrcontext.arg = (void *) plansource->query_string; - if (plan->saved) - { - /* 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; - } + /* + * Replan if needed, and increment plan refcount. If it's a saved + * plan, the refcount must be backed by the CurrentResourceOwner. + */ + cplan = GetCachedPlan(plansource, paramLI, plan->saved); + stmt_list = cplan->stmt_list; /* * In the default non-read-only case, get a new snapshot, replacing @@ -1966,8 +1951,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, } /* Done with this plan, so release refcount */ - if (cplan) - ReleaseCachedPlan(cplan, true); + ReleaseCachedPlan(cplan, plan->saved); cplan = NULL; /* @@ -1987,7 +1971,7 @@ fail: /* We no longer need the cached plan refcount, if any */ if (cplan) - ReleaseCachedPlan(cplan, true); + ReleaseCachedPlan(cplan, plan->saved); /* * Pop the error context stack @@ -2018,8 +2002,7 @@ fail: */ static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, - Datum *Values, const char *Nulls, - int pflags) + Datum *Values, const char *Nulls) { ParamListInfo paramLI; @@ -2043,7 +2026,7 @@ _SPI_convert_params(int nargs, Oid *argtypes, prm->value = Values[i]; prm->isnull = (Nulls && Nulls[i] == 'n'); - prm->pflags = pflags; + prm->pflags = PARAM_FLAG_CONST; prm->ptype = argtypes[i]; } } @@ -2283,21 +2266,31 @@ _SPI_checktuples(void) } /* - * Make an "unsaved" copy of the given plan, in a child context of parentcxt. + * Convert a "temporary" SPIPlan into an "unsaved" plan. + * + * The passed _SPI_plan struct is on the stack, and all its subsidiary data + * is in or under the current SPI executor context. Copy the plan into the + * SPI procedure context so it will survive _SPI_end_call(). To minimize + * data copying, this destructively modifies the input plan, by taking the + * plancache entries away from it and reparenting them to the new SPIPlan. */ static SPIPlanPtr -_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) +_SPI_make_plan_non_temp(SPIPlanPtr plan) { SPIPlanPtr newplan; + MemoryContext parentcxt = _SPI_current->procCxt; MemoryContext plancxt; MemoryContext oldcxt; ListCell *lc; - Assert(!plan->saved); /* not currently supported */ + /* Assert the input is a temporary SPIPlan */ + Assert(plan->magic == _SPI_PLAN_MAGIC); + Assert(plan->plancxt == NULL); /* - * Create a memory context for the plan. We don't expect the plan to be - * very large, so use smaller-than-default alloc parameters. + * Create a memory context for the plan, underneath the procedure context. + * We don't expect the plan to be very large, so use smaller-than-default + * alloc parameters. */ plancxt = AllocSetContextCreate(parentcxt, "SPI Plan", @@ -2306,7 +2299,7 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) ALLOCSET_SMALL_MAXSIZE); oldcxt = MemoryContextSwitchTo(plancxt); - /* Copy the SPI plan into its own context */ + /* Copy the SPI_plan struct and subsidiary data into the new context */ newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->saved = false; @@ -2324,46 +2317,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) newplan->parserSetup = plan->parserSetup; newplan->parserSetupArg = plan->parserSetupArg; + /* + * Reparent all the CachedPlanSources into the procedure context. In + * theory this could fail partway through due to the pallocs, but we + * don't care too much since both the procedure context and the executor + * context would go away on error. + */ 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->parserSetup = newplan->parserSetup; - newsource->parserSetupArg = newplan->parserSetupArg; - newsource->fully_planned = plansource->fully_planned; - newsource->fixed_result = plansource->fixed_result; - /* no need to worry about seach_path, generation or saved_xmin */ - 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); + CachedPlanSetParentContext(plansource, parentcxt); + + /* Build new list, with list cells in plancxt */ + newplan->plancache_list = lappend(newplan->plancache_list, plansource); } MemoryContextSwitchTo(oldcxt); + /* For safety, unlink the CachedPlanSources from the temporary plan */ + plan->plancache_list = NIL; + return newplan; } /* - * Make a "saved" copy of the given plan, entrusting everything to plancache.c + * Make a "saved" copy of the given plan. */ static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan) @@ -2373,13 +2352,12 @@ _SPI_save_plan(SPIPlanPtr plan) 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. + * very large, so use smaller-than-default alloc parameters. It's a + * transient context until we finish copying everything. */ - plancxt = AllocSetContextCreate(CacheMemoryContext, + plancxt = AllocSetContextCreate(CurrentMemoryContext, "SPI Plan", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, @@ -2389,7 +2367,7 @@ _SPI_save_plan(SPIPlanPtr plan) /* Copy the SPI plan into its own context */ newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; - newplan->saved = true; + newplan->saved = false; newplan->plancache_list = NIL; newplan->plancxt = plancxt; newplan->cursor_options = plan->cursor_options; @@ -2404,33 +2382,32 @@ _SPI_save_plan(SPIPlanPtr plan) newplan->parserSetup = plan->parserSetup; newplan->parserSetupArg = plan->parserSetupArg; + /* Copy all the plancache entries */ 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, - newplan->cursor_options, - cplan->stmt_list, - true, - false); - if (newplan->parserSetup != NULL) - CachedPlanSetParserHook(newsource, - newplan->parserSetup, - newplan->parserSetupArg); + newsource = CopyCachedPlan(plansource); newplan->plancache_list = lappend(newplan->plancache_list, newsource); } MemoryContextSwitchTo(oldcxt); + /* + * Mark it saved, reparent it under CacheMemoryContext, and mark all the + * component CachedPlanSources as saved. This sequence cannot fail + * partway through, so there's no risk of long-term memory leakage. + */ + newplan->saved = true; + MemoryContextSetParent(newplan->plancxt, CacheMemoryContext); + + foreach(lc, newplan->plancache_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); + + SaveCachedPlan(plansource); + } + return newplan; } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 072d50c3951..c7eac71e91e 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -161,10 +161,6 @@ static bool ignore_till_sync = false; */ static CachedPlanSource *unnamed_stmt_psrc = NULL; -/* workspace for building a new unnamed statement in */ -static MemoryContext unnamed_stmt_context = NULL; - - /* assorted command-line switches */ static const char *userDoption = NULL; /* -D switch */ @@ -1116,14 +1112,14 @@ exec_parse_message(const char *query_string, /* string to execute */ Oid *paramTypes, /* parameter types */ int numParams) /* number of parameters */ { + MemoryContext unnamed_stmt_context = NULL; MemoryContext oldcontext; List *parsetree_list; Node *raw_parse_tree; const char *commandTag; - List *querytree_list, - *stmt_list; + List *querytree_list; + CachedPlanSource *psrc; bool is_named; - bool fully_planned; bool save_log_statement_stats = log_statement_stats; char msec_str[32]; @@ -1158,11 +1154,11 @@ exec_parse_message(const char *query_string, /* string to execute */ * named or not. For a named prepared statement, we do parsing in * MessageContext and copy the finished trees into the prepared * statement's plancache entry; then the reset of MessageContext releases - * temporary space used by parsing and planning. For an unnamed prepared + * temporary space used by parsing and rewriting. For an unnamed prepared * statement, we assume the statement isn't going to hang around long, so * getting rid of temp space quickly is probably not worth the costs of - * copying parse/plan trees. So in this case, we create the plancache - * entry's context here, and do all the parsing work therein. + * copying parse trees. So in this case, we create the plancache entry's + * query_context here, and do all the parsing work therein. */ is_named = (stmt_name[0] != '\0'); if (is_named) @@ -1174,9 +1170,9 @@ exec_parse_message(const char *query_string, /* string to execute */ { /* Unnamed prepared statement --- release any prior unnamed stmt */ drop_unnamed_stmt(); - /* Create context for parsing/planning */ + /* Create context for parsing */ unnamed_stmt_context = - AllocSetContextCreate(CacheMemoryContext, + AllocSetContextCreate(MessageContext, "unnamed prepared statement", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, @@ -1230,7 +1226,13 @@ exec_parse_message(const char *query_string, /* string to execute */ errdetail_abort())); /* - * Set up a snapshot if parse analysis/planning will need one. + * Create the CachedPlanSource before we do parse analysis, since + * it needs to see the unmodified raw parse tree. + */ + psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag); + + /* + * Set up a snapshot if parse analysis will need one. */ if (analyze_requires_snapshot(raw_parse_tree)) { @@ -1239,18 +1241,14 @@ exec_parse_message(const char *query_string, /* string to execute */ } /* - * OK to analyze, rewrite, and plan this query. Note that the - * originally specified parameter set is not required to be complete, - * so we have to use parse_analyze_varparams(). - * - * XXX must use copyObject here since parse analysis scribbles on its - * input, and we need the unmodified raw parse tree for possible - * replanning later. + * Analyze and rewrite the query. Note that the originally specified + * parameter set is not required to be complete, so we have to use + * parse_analyze_varparams(). */ if (log_parser_stats) ResetUsage(); - query = parse_analyze_varparams(copyObject(raw_parse_tree), + query = parse_analyze_varparams(raw_parse_tree, query_string, ¶mTypes, &numParams); @@ -1274,22 +1272,7 @@ exec_parse_message(const char *query_string, /* string to execute */ querytree_list = pg_rewrite_query(query); - /* - * If this is the unnamed statement and it has parameters, defer query - * planning until Bind. Otherwise do it now. - */ - if (!is_named && numParams > 0) - { - stmt_list = querytree_list; - fully_planned = false; - } - else - { - stmt_list = pg_plan_queries(querytree_list, 0, NULL); - fully_planned = true; - } - - /* Done with the snapshot used for parsing/planning */ + /* Done with the snapshot used for parsing */ if (snapshot_set) PopActiveSnapshot(); } @@ -1298,56 +1281,47 @@ exec_parse_message(const char *query_string, /* string to execute */ /* Empty input string. This is legal. */ raw_parse_tree = NULL; commandTag = NULL; - stmt_list = NIL; - fully_planned = true; + psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag); + querytree_list = NIL; } - /* If we got a cancel signal in analysis or planning, quit */ - CHECK_FOR_INTERRUPTS(); - /* - * Store the query as a prepared statement. See above comments. + * CachedPlanSource must be a direct child of MessageContext before we + * reparent unnamed_stmt_context under it, else we have a disconnected + * circular subgraph. Klugy, but less so than flipping contexts even + * more above. */ + if (unnamed_stmt_context) + MemoryContextSetParent(psrc->context, MessageContext); + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(psrc, + querytree_list, + unnamed_stmt_context, + paramTypes, + numParams, + NULL, + NULL, + 0, /* default cursor options */ + true); /* fixed result */ + + /* If we got a cancel signal during analysis, quit */ + CHECK_FOR_INTERRUPTS(); + if (is_named) { - StorePreparedStatement(stmt_name, - raw_parse_tree, - query_string, - commandTag, - paramTypes, - numParams, - 0, /* default cursor options */ - stmt_list, - false); + /* + * Store the query as a prepared statement. + */ + StorePreparedStatement(stmt_name, psrc, false); } else { /* - * paramTypes and query_string need to be copied into - * unnamed_stmt_context. The rest is there already + * We just save the CachedPlanSource into unnamed_stmt_psrc. */ - Oid *newParamTypes; - - if (numParams > 0) - { - newParamTypes = (Oid *) palloc(numParams * sizeof(Oid)); - memcpy(newParamTypes, paramTypes, numParams * sizeof(Oid)); - } - else - newParamTypes = NULL; - - unnamed_stmt_psrc = FastCreateCachedPlan(raw_parse_tree, - pstrdup(query_string), - commandTag, - newParamTypes, - numParams, - 0, /* cursor options */ - stmt_list, - fully_planned, - true, - unnamed_stmt_context); - /* context now belongs to the plancache entry */ - unnamed_stmt_context = NULL; + SaveCachedPlan(psrc); + unnamed_stmt_psrc = psrc; } MemoryContextSwitchTo(oldcontext); @@ -1412,7 +1386,6 @@ exec_bind_message(StringInfo input_message) char *query_string; char *saved_stmt_name; ParamListInfo params; - List *plan_list; MemoryContext oldContext; bool save_log_statement_stats = log_statement_stats; bool snapshot_set = false; @@ -1437,7 +1410,7 @@ exec_bind_message(StringInfo input_message) } else { - /* Unnamed statements are re-prepared for every bind */ + /* special-case the unnamed statement */ psrc = unnamed_stmt_psrc; if (!psrc) ereport(ERROR, @@ -1522,7 +1495,7 @@ exec_bind_message(StringInfo input_message) /* * Prepare to copy stuff into the portal's memory context. We do all this * copying first, because it could possibly fail (out-of-memory) and we - * don't want a failure to occur between RevalidateCachedPlan and + * don't want a failure to occur between GetCachedPlan and * PortalDefineQuery; that would result in leaking our plancache refcount. */ oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); @@ -1539,7 +1512,9 @@ exec_bind_message(StringInfo input_message) /* * Set a snapshot if we have parameters to fetch (since the input * functions might need it) or the query isn't a utility command (and - * hence could require redoing parse analysis and planning). + * hence could require redoing parse analysis and planning). We keep + * the snapshot active till we're done, so that plancache.c doesn't have + * to take new ones. */ if (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree)) { @@ -1675,10 +1650,8 @@ exec_bind_message(StringInfo input_message) params->params[paramno].isnull = isNull; /* - * We mark the params as CONST. This has no effect if we already - * did planning, but if we didn't, it licenses the planner to - * substitute the parameters directly into the one-shot plan we - * will generate below. + * We mark the params as CONST. This ensures that any custom + * plan makes full use of the parameter values. */ params->params[paramno].pflags = PARAM_FLAG_CONST; params->params[paramno].ptype = ptype; @@ -1703,63 +1676,24 @@ exec_bind_message(StringInfo input_message) pq_getmsgend(input_message); - if (psrc->fully_planned) - { - /* - * Revalidate the cached plan; this may result in replanning. Any - * cruft will be generated in MessageContext. The plan refcount will - * be assigned to the Portal, so it will be released at portal - * destruction. - */ - cplan = RevalidateCachedPlan(psrc, false); - plan_list = cplan->stmt_list; - } - else - { - List *query_list; - - /* - * Revalidate the cached plan; this may result in redoing parse - * analysis and rewriting (but not planning). Any cruft will be - * generated in MessageContext. The plan refcount is assigned to - * CurrentResourceOwner. - */ - cplan = RevalidateCachedPlan(psrc, true); - - /* - * We didn't plan the query before, so do it now. This allows the - * planner to make use of the concrete parameter values we now have. - * Because we use PARAM_FLAG_CONST, the plan is good only for this set - * of param values, and so we generate the plan in the portal's own - * memory context where it will be thrown away after use. As in - * exec_parse_message, we make no attempt to recover planner temporary - * memory until the end of the operation. - * - * XXX because the planner has a bad habit of scribbling on its input, - * we have to make a copy of the parse trees. FIXME someday. - */ - oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - query_list = copyObject(cplan->stmt_list); - plan_list = pg_plan_queries(query_list, 0, params); - MemoryContextSwitchTo(oldContext); - - /* We no longer need the cached plan refcount ... */ - ReleaseCachedPlan(cplan, true); - /* ... and we don't want the portal to depend on it, either */ - cplan = NULL; - } + /* + * Obtain a plan from the CachedPlanSource. Any cruft from (re)planning + * will be generated in MessageContext. The plan refcount will be + * assigned to the Portal, so it will be released at portal destruction. + */ + cplan = GetCachedPlan(psrc, params, false); /* * Now we can define the portal. * * DO NOT put any code that could possibly throw an error between the - * above "RevalidateCachedPlan(psrc, false)" call and here. + * above GetCachedPlan call and here. */ PortalDefineQuery(portal, saved_stmt_name, query_string, psrc->commandTag, - plan_list, + cplan->stmt_list, cplan); /* Done with the snapshot used for parameter I/O and parsing/planning */ @@ -2304,8 +2238,7 @@ exec_describe_statement_message(const char *stmt_name) /* * If we are in aborted transaction state, we can't run - * SendRowDescriptionMessage(), because that needs catalog accesses. (We - * can't do RevalidateCachedPlan, either, but that's a lesser problem.) + * SendRowDescriptionMessage(), because that needs catalog accesses. * Hence, refuse to Describe statements that return data. (We shouldn't * just refuse all Describes, since that might break the ability of some * clients to issue COMMIT or ROLLBACK commands, if they use code that @@ -2342,18 +2275,12 @@ exec_describe_statement_message(const char *stmt_name) */ if (psrc->resultDesc) { - CachedPlan *cplan; List *tlist; - /* Make sure the plan is up to date */ - cplan = RevalidateCachedPlan(psrc, true); - - /* Get the primary statement and find out what it returns */ - tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list)); + /* Get the plan's primary targetlist */ + tlist = CachedPlanGetTargetList(psrc); SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL); - - ReleaseCachedPlan(cplan, true); } else pq_putemptymessage('n'); /* NoData */ @@ -2536,19 +2463,14 @@ IsTransactionStmtList(List *parseTrees) static void drop_unnamed_stmt(void) { - /* Release any completed unnamed statement */ + /* paranoia to avoid a dangling pointer in case of error */ if (unnamed_stmt_psrc) - DropCachedPlan(unnamed_stmt_psrc); - unnamed_stmt_psrc = NULL; + { + CachedPlanSource *psrc = unnamed_stmt_psrc; - /* - * If we failed while trying to build a prior unnamed statement, we may - * have a memory context that wasn't assigned to a completed plancache - * entry. If so, drop it to avoid a permanent memory leak. - */ - if (unnamed_stmt_context) - MemoryContextDelete(unnamed_stmt_context); - unnamed_stmt_context = NULL; + unnamed_stmt_psrc = NULL; + DropCachedPlan(psrc); + } } diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index af194f0c90f..6b5a5a8e44b 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -8,7 +8,7 @@ * 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 SPI plans they point to are saved using SPI_saveplan(). + * and the SPI plans they point to are saved using SPI_keepplan(). * There is not currently any provision for throwing away a no-longer-needed * plan --- consider improving this someday. * @@ -3316,7 +3316,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, /* Save the plan if requested */ if (cache_plan) { - qplan = SPI_saveplan(qplan); + SPI_keepplan(qplan); ri_HashPreparedPlan(qkey, qplan); } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index e5d5b68084c..c112a9cc163 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -316,7 +316,8 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) plan = SPI_prepare(query_getrulebyoid, 1, argtypes); if (plan == NULL) elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid); - plan_getrulebyoid = SPI_saveplan(plan); + SPI_keepplan(plan); + plan_getrulebyoid = plan; } /* @@ -450,7 +451,8 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags) plan = SPI_prepare(query_getviewrule, 2, argtypes); if (plan == NULL) elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule); - plan_getviewrule = SPI_saveplan(plan); + SPI_keepplan(plan); + plan_getviewrule = plan; } /* diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 68b6783e196..62fdf2877db 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -3,19 +3,23 @@ * plancache.c * Plan cache management. * - * We can store a cached plan in either fully-planned format, or just - * parsed-and-rewritten if the caller wishes to postpone planning until - * actual parameter values are available. CachedPlanSource has the same - * contents either way, but CachedPlan contains a list of PlannedStmts - * and bare utility statements in the first case, or a list of Query nodes - * in the second case. + * The plan cache manager has two principal responsibilities: deciding when + * to use a generic plan versus a custom (parameter-value-specific) plan, + * and tracking whether cached plans need to be invalidated because of schema + * changes in the objects they depend on. * - * The plan cache manager itself is principally responsible for tracking - * whether cached plans should be invalidated because of schema changes in - * the objects they depend on. When (and if) the next demand for a cached - * plan occurs, the query will be replanned. Note that this could result - * in an error, for example if a column referenced by the query is no - * longer present. The creator of a cached plan can specify whether it + * The logic for choosing generic or custom plans is in choose_custom_plan, + * which see for comments. + * + * Cache invalidation is driven off sinval events. Any CachedPlanSource + * that matches the event is marked invalid, as is its generic CachedPlan + * if it has one. When (and if) the next demand for a cached plan occurs, + * parse analysis and rewrite is repeated to build a new valid query tree, + * and then planning is performed as normal. + * + * Note that if the sinval was a result of user DDL actions, parse analysis + * could throw an error, for example if a column referenced by the query is + * no longer present. The creator of a cached plan can specify whether it * is allowable for the query to change output tupdesc on replan (this * could happen with "SELECT *" for example) --- if so, it's up to the * caller to notice changes and cope with them. @@ -41,6 +45,8 @@ */ #include "postgres.h" +#include <limits.h> + #include "access/transam.h" #include "catalog/namespace.h" #include "executor/executor.h" @@ -58,15 +64,28 @@ #include "utils/syscache.h" -static List *cached_plans_list = NIL; - -static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list, - MemoryContext plan_context); +/* + * This is the head of the backend's list of "saved" CachedPlanSources (i.e., + * those that are in long-lived storage and are examined for sinval events). + * We thread the structs manually instead of using List cells so that we can + * guarantee to save a CachedPlanSource without error. + */ +static CachedPlanSource *first_saved_plan = NULL; + +static void ReleaseGenericPlan(CachedPlanSource *plansource); +static List *RevalidateCachedQuery(CachedPlanSource *plansource); +static bool CheckCachedPlan(CachedPlanSource *plansource); +static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist, + ParamListInfo boundParams); +static bool choose_custom_plan(CachedPlanSource *plansource, + ParamListInfo boundParams); +static double cached_plan_cost(CachedPlan *plan); static void AcquireExecutorLocks(List *stmt_list, bool acquire); static void AcquirePlannerLocks(List *stmt_list, bool acquire); static void ScanQueryForLocks(Query *parsetree, bool acquire); static bool ScanQueryWalker(Node *node, bool *acquire); static bool plan_list_is_transient(List *stmt_list); +static TupleDesc PlanCacheComputeResultDesc(List *stmt_list); static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); @@ -90,32 +109,33 @@ InitPlanCache(void) /* * CreateCachedPlan: initially create a plan cache entry. * - * The caller must already have successfully parsed/planned the query; - * about all that we do here is copy it into permanent storage. + * Creation of a cached plan is divided into two steps, CreateCachedPlan and + * CompleteCachedPlan. CreateCachedPlan should be called after running the + * query through raw_parser, but before doing parse analysis and rewrite; + * CompleteCachedPlan is called after that. The reason for this arrangement + * is that it can save one round of copying of the raw parse tree, since + * the parser will normally scribble on the raw parse tree. Callers would + * otherwise need to make an extra copy of the parse tree to ensure they + * still had a clean copy to present at plan cache creation time. + * + * All arguments presented to CreateCachedPlan are copied into a memory + * context created as a child of the call-time CurrentMemoryContext, which + * should be a reasonably short-lived working context that will go away in + * event of an error. This ensures that the cached plan data structure will + * likewise disappear if an error occurs before we have fully constructed it. + * Once constructed, the cached plan can be made longer-lived, if needed, + * by calling SaveCachedPlan. * * raw_parse_tree: output of raw_parser() - * query_string: original query text (as of PG 8.4, must not be NULL) + * query_string: original query text * commandTag: compile-time-constant tag for query, or NULL if empty query - * param_types: array of fixed parameter type OIDs, or NULL if none - * num_params: number of fixed parameters - * cursor_options: options bitmask that was/will be passed to planner - * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees - * fully_planned: are we caching planner or rewriter output? - * fixed_result: TRUE to disallow changes in result tupdesc */ CachedPlanSource * CreateCachedPlan(Node *raw_parse_tree, const char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, - bool fully_planned, - bool fixed_result) + const char *commandTag) { CachedPlanSource *plansource; - OverrideSearchPath *search_path; MemoryContext source_context; MemoryContext oldcxt; @@ -123,61 +143,50 @@ CreateCachedPlan(Node *raw_parse_tree, /* * Make a dedicated memory context for the CachedPlanSource and its - * subsidiary data. We expect it can be pretty small. + * permanent subsidiary data. It's probably not going to be large, but + * just in case, use the default maxsize parameter. Initially it's a + * child of the caller's context (which we assume to be transient), so + * that it will be cleaned up on error. */ - source_context = AllocSetContextCreate(CacheMemoryContext, + source_context = AllocSetContextCreate(CurrentMemoryContext, "CachedPlanSource", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, - ALLOCSET_SMALL_MAXSIZE); - - /* - * Fetch current search_path into new context, but do any recalculation - * work required in caller's context. - */ - search_path = GetOverrideSearchPath(source_context); + ALLOCSET_DEFAULT_MAXSIZE); /* * Create and fill the CachedPlanSource struct within the new context. + * Most fields are just left empty for the moment. */ oldcxt = MemoryContextSwitchTo(source_context); - plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource)); + + plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + plansource->magic = CACHEDPLANSOURCE_MAGIC; plansource->raw_parse_tree = copyObject(raw_parse_tree); plansource->query_string = pstrdup(query_string); - plansource->commandTag = commandTag; /* no copying needed */ - if (num_params > 0) - { - plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); - memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); - } - else - plansource->param_types = NULL; - plansource->num_params = num_params; - /* these can be set later with CachedPlanSetParserHook: */ + plansource->commandTag = commandTag; + plansource->param_types = NULL; + plansource->num_params = 0; plansource->parserSetup = NULL; plansource->parserSetupArg = NULL; - plansource->cursor_options = cursor_options; - plansource->fully_planned = fully_planned; - plansource->fixed_result = fixed_result; - plansource->search_path = search_path; - plansource->generation = 0; /* StoreCachedPlan will increment */ - plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); - plansource->plan = NULL; + plansource->cursor_options = 0; + plansource->fixed_result = false; + plansource->resultDesc = NULL; + plansource->search_path = NULL; plansource->context = source_context; - plansource->orig_plan = NULL; - - /* - * Copy the current output plans into the plancache entry. - */ - StoreCachedPlan(plansource, stmt_list, NULL); - - /* - * Now we can add the entry to the list of cached plans. The List nodes - * live in CacheMemoryContext. - */ - MemoryContextSwitchTo(CacheMemoryContext); - - cached_plans_list = lappend(cached_plans_list, plansource); + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + plansource->query_context = NULL; + plansource->gplan = NULL; + plansource->is_complete = false; + plansource->is_saved = false; + plansource->is_valid = false; + plansource->generation = 0; + plansource->next_saved = NULL; + plansource->generic_cost = -1; + plansource->total_custom_cost = 0; + plansource->num_custom_plans = 0; MemoryContextSwitchTo(oldcxt); @@ -185,274 +194,465 @@ CreateCachedPlan(Node *raw_parse_tree, } /* - * FastCreateCachedPlan: create a plan cache entry with minimal data copying. + * CompleteCachedPlan: second step of creating a plan cache entry. + * + * Pass in the analyzed-and-rewritten form of the query, as well as the + * required subsidiary data about parameters and such. All passed values will + * be copied into the CachedPlanSource's memory, except as specified below. + * After this is called, GetCachedPlan can be called to obtain a plan, and + * optionally the CachedPlanSource can be saved using SaveCachedPlan. + * + * If querytree_context is not NULL, the querytree_list must be stored in that + * context (but the other parameters need not be). The querytree_list is not + * copied, rather the given context is kept as the initial query_context of + * the CachedPlanSource. (It should have been created as a child of the + * caller's working memory context, but it will now be reparented to belong + * to the CachedPlanSource.) The querytree_context is normally the context in + * which the caller did raw parsing and parse analysis. This approach saves + * one tree copying step compared to passing NULL, but leaves lots of extra + * cruft in the query_context, namely whatever extraneous stuff parse analysis + * created, as well as whatever went unused from the raw parse tree. Using + * this option is a space-for-time tradeoff that is appropriate if the + * CachedPlanSource is not expected to survive long. * - * For plans that aren't expected to live very long, the copying overhead of - * CreateCachedPlan is annoying. We provide this variant entry point in which - * the caller has already placed all the data in a suitable memory context. - * The source data and completed plan are in the same context, since this - * avoids extra copy steps during plan construction. If the query ever does - * need replanning, we'll generate a separate new CachedPlan at that time, but - * the CachedPlanSource and the initial CachedPlan share the caller-provided - * context and go away together when neither is needed any longer. (Because - * the parser and planner generate extra cruft in addition to their real - * output, this approach means that the context probably contains a bunch of - * useless junk as well as the useful trees. Hence, this method is a - * space-for-time tradeoff, which is worth making for plans expected to be - * short-lived.) + * plancache.c cannot know how to copy the data referenced by parserSetupArg, + * and it would often be inappropriate to do so anyway. When using that + * option, it is caller's responsibility that the referenced data remains + * valid for as long as the CachedPlanSource exists. * - * raw_parse_tree, query_string, param_types, and stmt_list must reside in the - * given context, which must have adequate lifespan (recommendation: make it a - * child of CacheMemoryContext). Otherwise the API is the same as - * CreateCachedPlan. + * plansource: structure returned by CreateCachedPlan + * querytree_list: analyzed-and-rewritten form of query (list of Query nodes) + * querytree_context: memory context containing querytree_list, + * or NULL to copy querytree_list into a fresh context + * param_types: array of fixed parameter type OIDs, or NULL if none + * num_params: number of fixed parameters + * parserSetup: alternate method for handling query parameters + * parserSetupArg: data to pass to parserSetup + * cursor_options: options bitmask to pass to planner + * fixed_result: TRUE to disallow future changes in query's result tupdesc */ -CachedPlanSource * -FastCreateCachedPlan(Node *raw_parse_tree, - char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, - bool fully_planned, - bool fixed_result, - MemoryContext context) +void +CompleteCachedPlan(CachedPlanSource *plansource, + List *querytree_list, + MemoryContext querytree_context, + Oid *param_types, + int num_params, + ParserSetupHook parserSetup, + void *parserSetupArg, + int cursor_options, + bool fixed_result) { - CachedPlanSource *plansource; - OverrideSearchPath *search_path; - MemoryContext oldcxt; + MemoryContext source_context = plansource->context; + MemoryContext oldcxt = CurrentMemoryContext; - Assert(query_string != NULL); /* required as of 8.4 */ + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(!plansource->is_complete); + + /* + * If caller supplied a querytree_context, reparent it underneath the + * CachedPlanSource's context; otherwise, create a suitable context and + * copy the querytree_list into it. + */ + if (querytree_context != NULL) + { + MemoryContextSetParent(querytree_context, source_context); + MemoryContextSwitchTo(querytree_context); + } + else + { + /* Again, it's a good bet the querytree_context can be small */ + querytree_context = AllocSetContextCreate(source_context, + "CachedPlanQuery", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + MemoryContextSwitchTo(querytree_context); + querytree_list = (List *) copyObject(querytree_list); + } + + plansource->query_context = querytree_context; + plansource->query_list = querytree_list; /* - * Fetch current search_path into given context, but do any recalculation - * work required in caller's context. + * Use the planner machinery to extract dependencies. Data is saved in + * query_context. (We assume that not a lot of extra cruft is created + * by this call.) */ - search_path = GetOverrideSearchPath(context); + extract_query_dependencies((Node *) querytree_list, + &plansource->relationOids, + &plansource->invalItems); /* - * Create and fill the CachedPlanSource struct within the given context. + * Save the final parameter types (or other parameter specification data) + * into the source_context, as well as our other parameters. Also save + * the result tuple descriptor. */ - oldcxt = MemoryContextSwitchTo(context); - plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource)); - plansource->raw_parse_tree = raw_parse_tree; - plansource->query_string = query_string; - plansource->commandTag = commandTag; /* no copying needed */ - plansource->param_types = param_types; + MemoryContextSwitchTo(source_context); + + if (num_params > 0) + { + plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); + memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); + } + else + plansource->param_types = NULL; plansource->num_params = num_params; - /* these can be set later with CachedPlanSetParserHook: */ - plansource->parserSetup = NULL; - plansource->parserSetupArg = NULL; + plansource->parserSetup = parserSetup; + plansource->parserSetupArg = parserSetupArg; plansource->cursor_options = cursor_options; - plansource->fully_planned = fully_planned; plansource->fixed_result = fixed_result; - plansource->search_path = search_path; - plansource->generation = 0; /* StoreCachedPlan will increment */ - plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); - plansource->plan = NULL; - plansource->context = context; - plansource->orig_plan = NULL; + plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list); + + MemoryContextSwitchTo(oldcxt); /* - * Store the current output plans into the plancache entry. + * Fetch current search_path into dedicated context, but do any + * recalculation work required in caller's context. */ - StoreCachedPlan(plansource, stmt_list, context); + plansource->search_path = GetOverrideSearchPath(source_context); + + plansource->is_complete = true; + plansource->is_valid = true; +} + +/* + * SaveCachedPlan: save a cached plan permanently + * + * This function moves the cached plan underneath CacheMemoryContext (making + * it live for the life of the backend, unless explicitly dropped), and adds + * it to the list of cached plans that are checked for invalidation when an + * sinval event occurs. + * + * This is guaranteed not to throw error; callers typically depend on that + * since this is called just before or just after adding a pointer to the + * CachedPlanSource to some permanent data structure of their own. Up until + * this is done, a CachedPlanSource is just transient data that will go away + * automatically on transaction abort. + */ +void +SaveCachedPlan(CachedPlanSource *plansource) +{ + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + Assert(!plansource->is_saved); /* - * Since the context is owned by the CachedPlan, advance its refcount. + * In typical use, this function would be called before generating any + * plans from the CachedPlanSource. If there is a generic plan, moving + * it into CacheMemoryContext would be pretty risky since it's unclear + * whether the caller has taken suitable care with making references + * long-lived. Best thing to do seems to be to discard the plan. */ - plansource->orig_plan = plansource->plan; - plansource->orig_plan->refcount++; + ReleaseGenericPlan(plansource); /* - * Now we can add the entry to the list of cached plans. The List nodes - * live in CacheMemoryContext. + * Reparent the source memory context under CacheMemoryContext so that + * it will live indefinitely. The query_context follows along since it's + * already a child of the other one. */ - MemoryContextSwitchTo(CacheMemoryContext); - - cached_plans_list = lappend(cached_plans_list, plansource); + MemoryContextSetParent(plansource->context, CacheMemoryContext); - MemoryContextSwitchTo(oldcxt); + /* + * Add the entry to the global list of cached plans. + */ + plansource->next_saved = first_saved_plan; + first_saved_plan = plansource; - return plansource; + plansource->is_saved = true; } /* - * CachedPlanSetParserHook: set up to use parser callback hooks + * DropCachedPlan: destroy a cached plan. * - * Use this when a caller wants to manage parameter information via parser - * callbacks rather than a fixed parameter-types list. Beware that the - * information pointed to by parserSetupArg must be valid for as long as - * the cached plan might be replanned! + * Actually this only destroys the CachedPlanSource: any referenced CachedPlan + * is released, but not destroyed until its refcount goes to zero. That + * handles the situation where DropCachedPlan is called while the plan is + * still in use. */ void -CachedPlanSetParserHook(CachedPlanSource *plansource, - ParserSetupHook parserSetup, - void *parserSetupArg) +DropCachedPlan(CachedPlanSource *plansource) { - /* Must not have specified a fixed parameter-types list */ - Assert(plansource->param_types == NULL); - Assert(plansource->num_params == 0); - /* OK, save hook info */ - plansource->parserSetup = parserSetup; - plansource->parserSetupArg = parserSetupArg; + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + + /* If it's been saved, remove it from the list */ + if (plansource->is_saved) + { + if (first_saved_plan == plansource) + first_saved_plan = plansource->next_saved; + else + { + CachedPlanSource *psrc; + + for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved) + { + if (psrc->next_saved == plansource) + { + psrc->next_saved = plansource->next_saved; + break; + } + } + } + plansource->is_saved = false; + } + + /* Decrement generic CachePlan's refcount and drop if no longer needed */ + ReleaseGenericPlan(plansource); + + /* + * Remove the CachedPlanSource and all subsidiary data (including the + * query_context if any). + */ + MemoryContextDelete(plansource->context); } /* - * StoreCachedPlan: store a built or rebuilt plan into a plancache entry. - * - * Common subroutine for CreateCachedPlan and RevalidateCachedPlan. + * ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any. */ static void -StoreCachedPlan(CachedPlanSource *plansource, - List *stmt_list, - MemoryContext plan_context) +ReleaseGenericPlan(CachedPlanSource *plansource) { - CachedPlan *plan; + /* Be paranoid about the possibility that ReleaseCachedPlan fails */ + if (plansource->gplan) + { + CachedPlan *plan = plansource->gplan; + + Assert(plan->magic == CACHEDPLAN_MAGIC); + plansource->gplan = NULL; + ReleaseCachedPlan(plan, false); + } +} + +/* + * RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree. + * + * What we do here is re-acquire locks and redo parse analysis if necessary. + * On return, the query_list is valid and we have sufficient locks to begin + * planning. + * + * If any parse analysis activity is required, the caller's memory context is + * used for that work. + * + * The result value is the transient analyzed-and-rewritten query tree if we + * had to do re-analysis, and NIL otherwise. (This is returned just to save + * a tree copying step in a subsequent BuildCachedPlan call.) + */ +static List * +RevalidateCachedQuery(CachedPlanSource *plansource) +{ + bool snapshot_set; + Node *rawtree; + List *tlist; /* transient query-tree list */ + List *qlist; /* permanent query-tree list */ + TupleDesc resultDesc; + MemoryContext querytree_context; MemoryContext oldcxt; - if (plan_context == NULL) + /* + * If the query is currently valid, acquire locks on the referenced + * objects; then check again. We need to do it this way to cover the race + * condition that an invalidation message arrives before we get the locks. + */ + if (plansource->is_valid) { - /* - * Make a dedicated memory context for the CachedPlan and its - * 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_SMALL_MINSIZE, - ALLOCSET_SMALL_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + AcquirePlannerLocks(plansource->query_list, true); /* - * Copy supplied data into the new context. + * By now, if any invalidation has happened, the inval callback + * functions will have marked the query invalid. */ - oldcxt = MemoryContextSwitchTo(plan_context); + if (plansource->is_valid) + { + /* Successfully revalidated and locked the query. */ + return NIL; + } - stmt_list = (List *) copyObject(stmt_list); + /* Ooops, the race case happened. Release useless locks. */ + AcquirePlannerLocks(plansource->query_list, false); } - else + + /* + * Discard the no-longer-useful query tree. (Note: we don't want to + * do this any earlier, else we'd not have been able to release locks + * correctly in the race condition case.) + */ + plansource->is_valid = false; + plansource->query_list = NIL; + plansource->relationOids = NIL; + plansource->invalItems = NIL; + + /* + * Free the query_context. We don't really expect MemoryContextDelete to + * fail, but just in case, make sure the CachedPlanSource is left in a + * reasonably sane state. (The generic plan won't get unlinked yet, + * but that's acceptable.) + */ + if (plansource->query_context) { - /* Assume subsidiary data is in the given context */ - oldcxt = MemoryContextSwitchTo(plan_context); + MemoryContext qcxt = plansource->query_context; + + plansource->query_context = NULL; + MemoryContextDelete(qcxt); } + /* Drop the generic plan reference if any */ + ReleaseGenericPlan(plansource); + /* - * Create and fill the CachedPlan struct within the new context. + * Now re-do parse analysis and rewrite. This not incidentally acquires + * the locks we need to do planning safely. */ - plan = (CachedPlan *) palloc(sizeof(CachedPlan)); - plan->stmt_list = stmt_list; - plan->fully_planned = plansource->fully_planned; - plan->dead = false; - if (plansource->fully_planned && plan_list_is_transient(stmt_list)) + Assert(plansource->is_complete); + + /* + * Restore the search_path that was in use when the plan was made. See + * comments for PushOverrideSearchPath about limitations of this. + * + * (XXX is there anything else we really need to restore?) + */ + PushOverrideSearchPath(plansource->search_path); + + /* + * If a snapshot is already set (the normal case), we can just use that + * for parsing/planning. But if it isn't, install one. Note: no point in + * checking whether parse analysis requires a snapshot; utility commands + * don't have invalidatable plans, so we'd not get here for such a + * command. + */ + snapshot_set = false; + if (!ActiveSnapshotSet()) { - Assert(TransactionIdIsNormal(TransactionXmin)); - plan->saved_xmin = TransactionXmin; + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; } + + /* + * Run parse analysis and rule rewriting. The parser tends to scribble on + * its input, so we must copy the raw parse tree to prevent corruption of + * the cache. + */ + rawtree = copyObject(plansource->raw_parse_tree); + if (plansource->parserSetup != NULL) + tlist = pg_analyze_and_rewrite_params(rawtree, + plansource->query_string, + plansource->parserSetup, + plansource->parserSetupArg); else - plan->saved_xmin = InvalidTransactionId; - plan->refcount = 1; /* for the parent's link */ - plan->generation = ++(plansource->generation); - plan->context = plan_context; - if (plansource->fully_planned) - { - /* - * Planner already extracted dependencies, we don't have to ... except - * in the case of EXPLAIN. We assume here that EXPLAIN can't appear - * in a list with other commands. - */ - plan->relationOids = plan->invalItems = NIL; + tlist = pg_analyze_and_rewrite(rawtree, + plansource->query_string, + plansource->param_types, + plansource->num_params); - if (list_length(stmt_list) == 1 && - IsA(linitial(stmt_list), ExplainStmt)) - { - ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list); + /* Release snapshot if we got one */ + if (snapshot_set) + PopActiveSnapshot(); - extract_query_dependencies(estmt->query, - &plan->relationOids, - &plan->invalItems); - } + /* Now we can restore current search path */ + PopOverrideSearchPath(); + + /* + * Check or update the result tupdesc. XXX should we use a weaker + * condition than equalTupleDescs() here? + * + * We assume the parameter types didn't change from the first time, so no + * need to update that. + */ + resultDesc = PlanCacheComputeResultDesc(tlist); + if (resultDesc == NULL && plansource->resultDesc == NULL) + { + /* OK, doesn't return tuples */ } - else + else if (resultDesc == NULL || plansource->resultDesc == NULL || + !equalTupleDescs(resultDesc, plansource->resultDesc)) { - /* Use the planner machinery to extract dependencies */ - extract_query_dependencies((Node *) stmt_list, - &plan->relationOids, - &plan->invalItems); + /* can we give a better error message? */ + if (plansource->fixed_result) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cached plan must not change result type"))); + oldcxt = MemoryContextSwitchTo(plansource->context); + if (resultDesc) + resultDesc = CreateTupleDescCopy(resultDesc); + if (plansource->resultDesc) + FreeTupleDesc(plansource->resultDesc); + plansource->resultDesc = resultDesc; + MemoryContextSwitchTo(oldcxt); } - Assert(plansource->plan == NULL); - plansource->plan = plan; + /* + * Allocate new query_context and copy the completed querytree into it. + * It's transient until we complete the copying and dependency extraction. + */ + querytree_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlanQuery", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + oldcxt = MemoryContextSwitchTo(querytree_context); - MemoryContextSwitchTo(oldcxt); -} + qlist = (List *) copyObject(tlist); -/* - * DropCachedPlan: destroy a cached plan. - * - * Actually this only destroys the CachedPlanSource: the referenced CachedPlan - * is released, but not destroyed until its refcount goes to zero. That - * handles the situation where DropCachedPlan is called while the plan is - * still in use. - */ -void -DropCachedPlan(CachedPlanSource *plansource) -{ - /* Validity check that we were given a CachedPlanSource */ - Assert(list_member_ptr(cached_plans_list, plansource)); + /* + * Use the planner machinery to extract dependencies. Data is saved in + * query_context. (We assume that not a lot of extra cruft is created + * by this call.) + */ + extract_query_dependencies((Node *) qlist, + &plansource->relationOids, + &plansource->invalItems); - /* Remove it from the list */ - cached_plans_list = list_delete_ptr(cached_plans_list, plansource); + MemoryContextSwitchTo(oldcxt); + + /* Now reparent the finished query_context and save the links */ + MemoryContextSetParent(querytree_context, plansource->context); - /* Decrement child CachePlan's refcount and drop if no longer needed */ - if (plansource->plan) - ReleaseCachedPlan(plansource->plan, false); + plansource->query_context = querytree_context; + plansource->query_list = qlist; /* - * If CachedPlanSource has independent storage, just drop it. Otherwise - * decrement the refcount on the CachePlan that owns the storage. + * Note: we do not reset generic_cost or total_custom_cost, although + * we could choose to do so. If the DDL or statistics change that + * prompted the invalidation meant a significant change in the cost + * estimates, it would be better to reset those variables and start + * fresh; but often it doesn't, and we're better retaining our hard-won + * knowledge about the relative costs. */ - if (plansource->orig_plan == NULL) - { - /* Remove the CachedPlanSource and all subsidiary data */ - MemoryContextDelete(plansource->context); - } - else - { - Assert(plansource->context == plansource->orig_plan->context); - ReleaseCachedPlan(plansource->orig_plan, false); - } + + plansource->is_valid = true; + + /* Return transient copy of querytrees for possible use in planning */ + return tlist; } /* - * RevalidateCachedPlan: prepare for re-use of a previously cached plan. - * - * What we do here is re-acquire locks and rebuild the plan if necessary. - * On return, the plan is valid and we have sufficient locks to begin - * execution (or planning, if not fully_planned). + * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid. * - * On return, the refcount of the plan has been incremented; a later - * ReleaseCachedPlan() call is expected. The refcount has been reported - * to the CurrentResourceOwner if useResOwner is true. + * Caller must have already called RevalidateCachedQuery to verify that the + * querytree is up to date. * - * Note: if any replanning activity is required, the caller's memory context - * is used for that work. + * On a "true" return, we have acquired the locks needed to run the plan. + * (We must do this for the "true" result to be race-condition-free.) */ -CachedPlan * -RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) +static bool +CheckCachedPlan(CachedPlanSource *plansource) { - CachedPlan *plan; + CachedPlan *plan = plansource->gplan; + + /* Assert that caller checked the querytree */ + Assert(plansource->is_valid); - /* Validity check that we were given a CachedPlanSource */ - Assert(list_member_ptr(cached_plans_list, plansource)); + /* If there's no generic plan, just say "false" */ + if (!plan) + return false; + + Assert(plan->magic == CACHEDPLAN_MAGIC); /* - * If the plan currently appears valid, acquire locks on the referenced - * objects; then check again. We need to do it this way to cover the race - * condition that an invalidation message arrives before we get the lock. + * If it appears valid, acquire locks and recheck; this is much the same + * logic as in RevalidateCachedQuery, but for a plan. */ - plan = plansource->plan; - if (plan && !plan->dead) + if (plan->is_valid) { /* * Plan must have positive refcount because it is referenced by @@ -460,164 +660,348 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) */ Assert(plan->refcount > 0); - if (plan->fully_planned) - AcquireExecutorLocks(plan->stmt_list, true); - else - AcquirePlannerLocks(plan->stmt_list, true); + AcquireExecutorLocks(plan->stmt_list, true); /* * If plan was transient, check to see if TransactionXmin has * advanced, and if so invalidate it. */ - if (!plan->dead && + if (plan->is_valid && TransactionIdIsValid(plan->saved_xmin) && !TransactionIdEquals(plan->saved_xmin, TransactionXmin)) - plan->dead = true; + plan->is_valid = false; /* * By now, if any invalidation has happened, the inval callback - * functions will have marked the plan dead. + * functions will have marked the plan invalid. */ - if (plan->dead) + if (plan->is_valid) { - /* Ooops, the race case happened. Release useless locks. */ - if (plan->fully_planned) - AcquireExecutorLocks(plan->stmt_list, false); - else - AcquirePlannerLocks(plan->stmt_list, false); + /* Successfully revalidated and locked the query. */ + return true; } + + /* Ooops, the race case happened. Release useless locks. */ + AcquireExecutorLocks(plan->stmt_list, false); } /* - * If plan has been invalidated, unlink it from the parent and release it. + * Plan has been invalidated, so unlink it from the parent and release it. */ - if (plan && plan->dead) + ReleaseGenericPlan(plansource); + + return false; +} + +/* + * BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource. + * + * qlist should be the result value from a previous RevalidateCachedQuery. + * + * To build a generic, parameter-value-independent plan, pass NULL for + * boundParams. To build a custom plan, pass the actual parameter values via + * boundParams. For best effect, the PARAM_FLAG_CONST flag should be set on + * each parameter value; otherwise the planner will treat the value as a + * hint rather than a hard constant. + * + * Planning work is done in the caller's memory context. The finished plan + * is in a child memory context, which typically should get reparented. + */ +static CachedPlan * +BuildCachedPlan(CachedPlanSource *plansource, List *qlist, + ParamListInfo boundParams) +{ + CachedPlan *plan; + List *plist; + bool snapshot_set; + bool spi_pushed; + MemoryContext plan_context; + MemoryContext oldcxt; + + /* Assert that caller checked the querytree */ + Assert(plansource->is_valid); + + /* + * If we don't already have a copy of the querytree list that can be + * scribbled on by the planner, make one. + */ + if (qlist == NIL) + qlist = (List *) copyObject(plansource->query_list); + + /* + * Restore the search_path that was in use when the plan was made. See + * comments for PushOverrideSearchPath about limitations of this. + * + * (XXX is there anything else we really need to restore?) + * + * Note: it's a bit annoying to do this and snapshot-setting twice in the + * case where we have to do both re-analysis and re-planning. However, + * until there's some evidence that the cost is actually meaningful + * compared to parse analysis + planning, I'm not going to contort the + * code enough to avoid that. + */ + PushOverrideSearchPath(plansource->search_path); + + /* + * If a snapshot is already set (the normal case), we can just use + * that for parsing/planning. But if it isn't, install one. Note: no + * point in checking whether parse analysis requires a snapshot; + * utility commands don't have invalidatable plans, so we'd not get + * here for such a command. + */ + snapshot_set = false; + if (!ActiveSnapshotSet()) { - plansource->plan = NULL; - ReleaseCachedPlan(plan, false); - plan = NULL; + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_set = true; } /* - * Build a new plan if needed. + * The planner may try to call SPI-using functions, which causes a + * problem if we're already inside one. Rather than expect all + * SPI-using code to do SPI_push whenever a replan could happen, + * it seems best to take care of the case here. */ - if (!plan) + spi_pushed = SPI_push_conditional(); + + /* + * Generate the plan. + */ + plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams); + + /* Clean up SPI state */ + SPI_pop_conditional(spi_pushed); + + /* Release snapshot if we got one */ + if (snapshot_set) + PopActiveSnapshot(); + + /* Now we can restore current search path */ + PopOverrideSearchPath(); + + /* + * Make a dedicated memory context for the CachedPlan and its subsidiary + * data. It's probably not going to be large, but just in case, use the + * default maxsize parameter. It's transient for the moment. + */ + plan_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlan", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + /* + * Copy plan into the new context. + */ + oldcxt = MemoryContextSwitchTo(plan_context); + + plist = (List *) copyObject(plist); + + /* + * Create and fill the CachedPlan struct within the new context. + */ + plan = (CachedPlan *) palloc(sizeof(CachedPlan)); + plan->magic = CACHEDPLAN_MAGIC; + plan->stmt_list = plist; + if (plan_list_is_transient(plist)) { - bool snapshot_set = false; - Node *rawtree; - List *slist; - TupleDesc resultDesc; + Assert(TransactionIdIsNormal(TransactionXmin)); + plan->saved_xmin = TransactionXmin; + } + else + plan->saved_xmin = InvalidTransactionId; + plan->refcount = 0; + plan->context = plan_context; + plan->is_saved = false; + plan->is_valid = true; - /* - * Restore the search_path that was in use when the plan was made. See - * comments for PushOverrideSearchPath about limitations of this. - * - * (XXX is there anything else we really need to restore?) - */ - PushOverrideSearchPath(plansource->search_path); + /* assign generation number to new plan */ + plan->generation = ++(plansource->generation); - /* - * If a snapshot is already set (the normal case), we can just use - * that for parsing/planning. But if it isn't, install one. Note: no - * point in checking whether parse analysis requires a snapshot; - * utility commands don't have invalidatable plans, so we'd not get - * here for such a command. - */ - if (!ActiveSnapshotSet()) - { - PushActiveSnapshot(GetTransactionSnapshot()); - snapshot_set = true; - } + MemoryContextSwitchTo(oldcxt); - /* - * Run parse analysis and rule rewriting. The parser tends to - * scribble on its input, so we must copy the raw parse tree to - * prevent corruption of the cache. - */ - rawtree = copyObject(plansource->raw_parse_tree); - if (plansource->parserSetup != NULL) - slist = pg_analyze_and_rewrite_params(rawtree, - plansource->query_string, - plansource->parserSetup, - plansource->parserSetupArg); - else - slist = pg_analyze_and_rewrite(rawtree, - plansource->query_string, - plansource->param_types, - plansource->num_params); + return plan; +} - if (plansource->fully_planned) - { - /* - * Generate plans for queries. - * - * The planner may try to call SPI-using functions, which causes a - * problem if we're already inside one. Rather than expect all - * SPI-using code to do SPI_push whenever a replan could happen, - * it seems best to take care of the case here. - */ - bool pushed; +/* + * choose_custom_plan: choose whether to use custom or generic plan + * + * This defines the policy followed by GetCachedPlan. + */ +static bool +choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams) +{ + double avg_custom_cost; - pushed = SPI_push_conditional(); + /* Never any point in a custom plan if there's no parameters */ + if (boundParams == NULL) + return false; - slist = pg_plan_queries(slist, plansource->cursor_options, NULL); + /* See if caller wants to force the decision */ + if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN) + return false; + if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN) + return true; - SPI_pop_conditional(pushed); - } + /* Generate custom plans until we have done at least 5 (arbitrary) */ + if (plansource->num_custom_plans < 5) + return true; - /* - * Check or update the result tupdesc. XXX should we use a weaker - * condition than equalTupleDescs() here? - */ - resultDesc = PlanCacheComputeResultDesc(slist); - if (resultDesc == NULL && plansource->resultDesc == NULL) + avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans; + + /* + * Prefer generic plan if it's less than 10% more expensive than average + * custom plan. This threshold is a bit arbitrary; it'd be better if we + * had some means of comparing planning time to the estimated runtime + * cost differential. + * + * Note that if generic_cost is -1 (indicating we've not yet determined + * the generic plan cost), we'll always prefer generic at this point. + */ + if (plansource->generic_cost < avg_custom_cost * 1.1) + return false; + + return true; +} + +/* + * cached_plan_cost: calculate estimated cost of a plan + */ +static double +cached_plan_cost(CachedPlan *plan) +{ + double result = 0; + ListCell *lc; + + foreach(lc, plan->stmt_list) + { + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); + + if (!IsA(plannedstmt, PlannedStmt)) + continue; /* Ignore utility statements */ + + result += plannedstmt->planTree->total_cost; + } + + return result; +} + +/* + * GetCachedPlan: get a cached plan from a CachedPlanSource. + * + * This function hides the logic that decides whether to use a generic + * plan or a custom plan for the given parameters: the caller does not know + * which it will get. + * + * On return, the plan is valid and we have sufficient locks to begin + * execution. + * + * On return, the refcount of the plan has been incremented; a later + * ReleaseCachedPlan() call is expected. The refcount has been reported + * to the CurrentResourceOwner if useResOwner is true (note that that must + * only be true if it's a "saved" CachedPlanSource). + * + * Note: if any replanning activity is required, the caller's memory context + * is used for that work. + */ +CachedPlan * +GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams, + bool useResOwner) +{ + CachedPlan *plan; + List *qlist; + bool customplan; + + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + /* This seems worth a real test, though */ + if (useResOwner && !plansource->is_saved) + elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan"); + + /* Make sure the querytree list is valid and we have parse-time locks */ + qlist = RevalidateCachedQuery(plansource); + + /* Decide whether to use a custom plan */ + customplan = choose_custom_plan(plansource, boundParams); + + if (!customplan) + { + if (CheckCachedPlan(plansource)) { - /* OK, doesn't return tuples */ + /* We want a generic plan, and we already have a valid one */ + plan = plansource->gplan; + Assert(plan->magic == CACHEDPLAN_MAGIC); } - else if (resultDesc == NULL || plansource->resultDesc == NULL || - !equalTupleDescs(resultDesc, plansource->resultDesc)) + else { - MemoryContext oldcxt; - - /* can we give a better error message? */ - if (plansource->fixed_result) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cached plan must not change result type"))); - oldcxt = MemoryContextSwitchTo(plansource->context); - if (resultDesc) - resultDesc = CreateTupleDescCopy(resultDesc); - if (plansource->resultDesc) - FreeTupleDesc(plansource->resultDesc); - plansource->resultDesc = resultDesc; - MemoryContextSwitchTo(oldcxt); - } - - /* Release snapshot if we got one */ - if (snapshot_set) - PopActiveSnapshot(); - - /* Now we can restore current search path */ - PopOverrideSearchPath(); + /* Build a new generic plan */ + plan = BuildCachedPlan(plansource, qlist, NULL); + /* Just make real sure plansource->gplan is clear */ + ReleaseGenericPlan(plansource); + /* Link the new generic plan into the plansource */ + plansource->gplan = plan; + plan->refcount++; + /* Immediately reparent into appropriate context */ + if (plansource->is_saved) + { + /* saved plans all live under CacheMemoryContext */ + MemoryContextSetParent(plan->context, CacheMemoryContext); + plan->is_saved = true; + } + else + { + /* otherwise, it should be a sibling of the plansource */ + MemoryContextSetParent(plan->context, + MemoryContextGetParent(plansource->context)); + } + /* Update generic_cost whenever we make a new generic plan */ + plansource->generic_cost = cached_plan_cost(plan); - /* - * Store the plans into the plancache entry, advancing the generation - * count. - */ - StoreCachedPlan(plansource, slist, NULL); + /* + * If, based on the now-known value of generic_cost, we'd not have + * chosen to use a generic plan, then forget it and make a custom + * plan. This is a bit of a wart but is necessary to avoid a + * glitch in behavior when the custom plans are consistently big + * winners; at some point we'll experiment with a generic plan and + * find it's a loser, but we don't want to actually execute that + * plan. + */ + customplan = choose_custom_plan(plansource, boundParams); + } + } - plan = plansource->plan; + if (customplan) + { + /* Build a custom plan */ + plan = BuildCachedPlan(plansource, qlist, boundParams); + /* Accumulate total costs of custom plans, but 'ware overflow */ + if (plansource->num_custom_plans < INT_MAX) + { + plansource->total_custom_cost += cached_plan_cost(plan); + plansource->num_custom_plans++; + } } - /* - * Last step: flag the plan as in use by caller. - */ + /* Flag the plan as in use by caller */ if (useResOwner) ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner); plan->refcount++; if (useResOwner) ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan); + /* + * Saved plans should be under CacheMemoryContext so they will not go away + * until their reference count goes to zero. In the generic-plan cases we + * already took care of that, but for a custom plan, do it as soon as we + * have created a reference-counted link. + */ + if (customplan && plansource->is_saved) + { + MemoryContextSetParent(plan->context, CacheMemoryContext); + plan->is_saved = true; + } + return plan; } @@ -635,8 +1019,12 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) { + Assert(plan->magic == CACHEDPLAN_MAGIC); if (useResOwner) + { + Assert(plan->is_saved); ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan); + } Assert(plan->refcount > 0); plan->refcount--; if (plan->refcount == 0) @@ -644,8 +1032,125 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) } /* - * CachedPlanIsValid: test whether the plan within a CachedPlanSource is - * currently valid (that is, not marked as being in need of revalidation). + * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context + * + * This can only be applied to unsaved plans; once saved, a plan always + * lives underneath CacheMemoryContext. + */ +void +CachedPlanSetParentContext(CachedPlanSource *plansource, + MemoryContext newcontext) +{ + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + + /* This seems worth a real test, though */ + if (plansource->is_saved) + elog(ERROR, "cannot move a saved cached plan to another context"); + + /* OK, let the caller keep the plan where he wishes */ + MemoryContextSetParent(plansource->context, newcontext); + + /* + * The query_context needs no special handling, since it's a child of + * plansource->context. But if there's a generic plan, it should be + * maintained as a sibling of plansource->context. + */ + if (plansource->gplan) + { + Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC); + MemoryContextSetParent(plansource->gplan->context, newcontext); + } +} + +/* + * CopyCachedPlan: make a copy of a CachedPlanSource + * + * This is a convenience routine that does the equivalent of + * CreateCachedPlan + CompleteCachedPlan, using the data stored in the + * input CachedPlanSource. The result is therefore "unsaved" (regardless + * of the state of the source), and we don't copy any generic plan either. + * The result will be currently valid, or not, the same as the source. + */ +CachedPlanSource * +CopyCachedPlan(CachedPlanSource *plansource) +{ + CachedPlanSource *newsource; + MemoryContext source_context; + MemoryContext querytree_context; + MemoryContext oldcxt; + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); + + source_context = AllocSetContextCreate(CurrentMemoryContext, + "CachedPlanSource", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + oldcxt = MemoryContextSwitchTo(source_context); + + newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + newsource->magic = CACHEDPLANSOURCE_MAGIC; + newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree); + newsource->query_string = pstrdup(plansource->query_string); + newsource->commandTag = plansource->commandTag; + if (plansource->num_params > 0) + { + newsource->param_types = (Oid *) + palloc(plansource->num_params * sizeof(Oid)); + memcpy(newsource->param_types, plansource->param_types, + plansource->num_params * sizeof(Oid)); + } + else + newsource->param_types = NULL; + newsource->num_params = plansource->num_params; + newsource->parserSetup = plansource->parserSetup; + newsource->parserSetupArg = plansource->parserSetupArg; + newsource->cursor_options = plansource->cursor_options; + newsource->fixed_result = plansource->fixed_result; + if (plansource->resultDesc) + newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc); + else + newsource->resultDesc = NULL; + newsource->search_path = CopyOverrideSearchPath(plansource->search_path); + newsource->context = source_context; + + querytree_context = AllocSetContextCreate(source_context, + "CachedPlanQuery", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + MemoryContextSwitchTo(querytree_context); + newsource->query_list = (List *) copyObject(plansource->query_list); + newsource->relationOids = (List *) copyObject(plansource->relationOids); + newsource->invalItems = (List *) copyObject(plansource->invalItems); + newsource->query_context = querytree_context; + + newsource->gplan = NULL; + + newsource->is_complete = true; + newsource->is_saved = false; + newsource->is_valid = plansource->is_valid; + newsource->generation = plansource->generation; + newsource->next_saved = NULL; + + /* We may as well copy any acquired cost knowledge */ + newsource->generic_cost = plansource->generic_cost; + newsource->total_custom_cost = plansource->total_custom_cost; + newsource->num_custom_plans = plansource->num_custom_plans; + + MemoryContextSwitchTo(oldcxt); + + return newsource; +} + +/* + * CachedPlanIsValid: test whether the rewritten querytree within a + * CachedPlanSource is currently valid (that is, not marked as being in need + * of revalidation). * * This result is only trustworthy (ie, free from race conditions) if * the caller has acquired locks on all the relations used in the plan. @@ -653,37 +1158,44 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) bool CachedPlanIsValid(CachedPlanSource *plansource) { - CachedPlan *plan; + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + return plansource->is_valid; +} - /* Validity check that we were given a CachedPlanSource */ - Assert(list_member_ptr(cached_plans_list, plansource)); +/* + * CachedPlanGetTargetList: return tlist, if any, describing plan's output + * + * The result is guaranteed up-to-date. However, it is local storage + * within the cached plan, and may disappear next time the plan is updated. + */ +List * +CachedPlanGetTargetList(CachedPlanSource *plansource) +{ + Node *pstmt; - plan = plansource->plan; - if (plan && !plan->dead) - { - /* - * Plan must have positive refcount because it is referenced by - * plansource; so no need to fear it disappears under us here. - */ - Assert(plan->refcount > 0); + /* Assert caller is doing things in a sane order */ + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(plansource->is_complete); - /* - * Although we don't want to acquire locks here, it still seems useful - * to check for expiration of a transient plan. - */ - if (TransactionIdIsValid(plan->saved_xmin) && - !TransactionIdEquals(plan->saved_xmin, TransactionXmin)) - plan->dead = true; - else - return true; - } + /* + * No work needed if statement doesn't return tuples (we assume this + * feature cannot be changed by an invalidation) + */ + if (plansource->resultDesc == NULL) + return NIL; - return false; + /* Make sure the querytree list is valid and we have parse-time locks */ + RevalidateCachedQuery(plansource); + + /* Get the primary statement and find out what it returns */ + pstmt = PortalListGetPrimaryStmt(plansource->query_list); + + return FetchStatementTargetList(pstmt); } /* - * AcquireExecutorLocks: acquire locks needed for execution of a fully-planned - * cached plan; or release them if acquire is false. + * AcquireExecutorLocks: acquire locks needed for execution of a cached plan; + * or release them if acquire is false. */ static void AcquireExecutorLocks(List *stmt_list, bool acquire) @@ -752,8 +1264,8 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) } /* - * AcquirePlannerLocks: acquire locks needed for planning and execution of a - * not-fully-planned cached plan; or release them if acquire is false. + * AcquirePlannerLocks: acquire locks needed for planning of a querytree list; + * or release them if acquire is false. * * Note that we don't actually try to open the relations, and hence will not * fail if one has been dropped entirely --- we'll just transiently acquire @@ -903,64 +1415,36 @@ plan_list_is_transient(List *stmt_list) } /* - * 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. + * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten 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. */ -TupleDesc +static TupleDesc PlanCacheComputeResultDesc(List *stmt_list) { - Node *node; Query *query; - PlannedStmt *pstmt; switch (ChoosePortalStrategy(stmt_list)) { case PORTAL_ONE_SELECT: case PORTAL_ONE_MOD_WITH: - node = (Node *) linitial(stmt_list); - if (IsA(node, Query)) - { - query = (Query *) node; - return ExecCleanTypeFromTL(query->targetList, false); - } - if (IsA(node, PlannedStmt)) - { - pstmt = (PlannedStmt *) node; - return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false); - } - /* other cases shouldn't happen, but return NULL */ - break; + query = (Query *) linitial(stmt_list); + Assert(IsA(query, Query)); + return ExecCleanTypeFromTL(query->targetList, false); case PORTAL_ONE_RETURNING: - node = PortalListGetPrimaryStmt(stmt_list); - if (IsA(node, Query)) - { - query = (Query *) node; - Assert(query->returningList); - return ExecCleanTypeFromTL(query->returningList, false); - } - if (IsA(node, PlannedStmt)) - { - pstmt = (PlannedStmt *) node; - Assert(pstmt->hasReturning); - return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false); - } - /* other cases shouldn't happen, but return NULL */ - break; + query = (Query *) PortalListGetPrimaryStmt(stmt_list); + Assert(IsA(query, Query)); + Assert(query->returningList); + return ExecCleanTypeFromTL(query->returningList, false); case PORTAL_UTIL_SELECT: - node = (Node *) linitial(stmt_list); - if (IsA(node, Query)) - { - query = (Query *) node; - Assert(query->utilityStmt); - return UtilityTupleDescriptor(query->utilityStmt); - } - /* else it's a bare utility statement */ - return UtilityTupleDescriptor(node); + query = (Query *) linitial(stmt_list); + Assert(IsA(query, Query)); + Assert(query->utilityStmt); + return UtilityTupleDescriptor(query->utilityStmt); case PORTAL_MULTI_QUERY: /* will not return tuples */ @@ -979,33 +1463,39 @@ PlanCacheComputeResultDesc(List *stmt_list) static void PlanCacheRelCallback(Datum arg, Oid relid) { - ListCell *lc1; + CachedPlanSource *plansource; - foreach(lc1, cached_plans_list) + for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); - CachedPlan *plan = plansource->plan; + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ - if (!plan || plan->dead) + if (!plansource->is_valid) continue; /* - * Check the list we built ourselves; this covers unplanned cases - * including EXPLAIN. + * Check the dependency list for the rewritten querytree. */ - if ((relid == InvalidOid) ? plan->relationOids != NIL : - list_member_oid(plan->relationOids, relid)) - plan->dead = true; + if ((relid == InvalidOid) ? plansource->relationOids != NIL : + list_member_oid(plansource->relationOids, relid)) + { + /* Invalidate the querytree and generic plan */ + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; + } - if (plan->fully_planned && !plan->dead) + /* + * The generic plan, if any, could have more dependencies than the + * querytree does, so we have to check it too. + */ + if (plansource->gplan && plansource->gplan->is_valid) { - /* Have to check the per-PlannedStmt relid lists */ - ListCell *lc2; + ListCell *lc; - foreach(lc2, plan->stmt_list) + foreach(lc, plansource->gplan->stmt_list) { - PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); Assert(!IsA(plannedstmt, Query)); if (!IsA(plannedstmt, PlannedStmt)) @@ -1013,8 +1503,8 @@ PlanCacheRelCallback(Datum arg, Oid relid) if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL : list_member_oid(plannedstmt->relationOids, relid)) { - /* Invalidate the plan! */ - plan->dead = true; + /* Invalidate the generic plan only */ + plansource->gplan->is_valid = false; break; /* out of stmt_list scan */ } } @@ -1035,43 +1525,47 @@ PlanCacheRelCallback(Datum arg, Oid relid) static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) { - ListCell *lc1; + CachedPlanSource *plansource; - foreach(lc1, cached_plans_list) + for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); - CachedPlan *plan = plansource->plan; - ListCell *lc2; + ListCell *lc; + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ - if (!plan || plan->dead) + if (!plansource->is_valid) continue; /* - * Check the list we built ourselves; this covers unplanned cases - * including EXPLAIN. + * Check the dependency list for the rewritten querytree. */ - foreach(lc2, plan->invalItems) + foreach(lc, plansource->invalItems) { - PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2); + PlanInvalItem *item = (PlanInvalItem *) lfirst(lc); if (item->cacheId != cacheid) continue; if (hashvalue == 0 || item->hashValue == hashvalue) { - /* Invalidate the plan! */ - plan->dead = true; + /* Invalidate the querytree and generic plan */ + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; break; } } - if (plan->fully_planned && !plan->dead) + /* + * The generic plan, if any, could have more dependencies than the + * querytree does, so we have to check it too. + */ + if (plansource->gplan && plansource->gplan->is_valid) { - /* Have to check the per-PlannedStmt inval-item lists */ - foreach(lc2, plan->stmt_list) + foreach(lc, plansource->gplan->stmt_list) { - PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc); ListCell *lc3; Assert(!IsA(plannedstmt, Query)); @@ -1086,12 +1580,12 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) if (hashvalue == 0 || item->hashValue == hashvalue) { - /* Invalidate the plan! */ - plan->dead = true; + /* Invalidate the generic plan only */ + plansource->gplan->is_valid = false; break; /* out of invalItems scan */ } } - if (plan->dead) + if (!plansource->gplan->is_valid) break; /* out of stmt_list scan */ } } @@ -1111,65 +1605,46 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) } /* - * ResetPlanCache: drop all cached plans. + * ResetPlanCache: invalidate all cached plans. */ void ResetPlanCache(void) { - ListCell *lc1; + CachedPlanSource *plansource; - foreach(lc1, cached_plans_list) + for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved) { - CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); - CachedPlan *plan = plansource->plan; - ListCell *lc2; + ListCell *lc; + + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); /* No work if it's already invalidated */ - if (!plan || plan->dead) + if (!plansource->is_valid) continue; /* - * We *must not* mark transaction control statements as dead, + * We *must not* mark transaction control statements as invalid, * particularly not ROLLBACK, because they may need to be executed in * aborted transactions when we can't revalidate them (cf bug #5269). * In general there is no point in invalidating utility statements - * since they have no plans anyway. So mark it dead only if it + * since they have no plans anyway. So invalidate it only if it * contains at least one non-utility statement. (EXPLAIN counts as a * non-utility statement, though, since it contains an analyzed query * that might have dependencies.) */ - if (plan->fully_planned) + foreach(lc, plansource->query_list) { - /* Search statement list for non-utility statements */ - foreach(lc2, plan->stmt_list) - { - PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); + Query *query = (Query *) lfirst(lc); - Assert(!IsA(plannedstmt, Query)); - if (IsA(plannedstmt, PlannedStmt) || - IsA(plannedstmt, ExplainStmt)) - { - /* non-utility statement, so invalidate */ - plan->dead = true; - break; /* out of stmt_list scan */ - } - } - } - else - { - /* Search Query list for non-utility statements */ - foreach(lc2, plan->stmt_list) + Assert(IsA(query, Query)); + if (query->commandType != CMD_UTILITY || + IsA(query->utilityStmt, ExplainStmt)) { - Query *query = (Query *) lfirst(lc2); - - Assert(IsA(query, Query)); - if (query->commandType != CMD_UTILITY || - IsA(query->utilityStmt, ExplainStmt)) - { - /* non-utility statement, so invalidate */ - plan->dead = true; - break; /* out of stmt_list scan */ - } + /* non-utility statement, so invalidate */ + plansource->is_valid = false; + if (plansource->gplan) + plansource->gplan->is_valid = false; + break; } } } diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 4eab24c46c7..466e9334cfb 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -343,6 +343,18 @@ GetMemoryChunkContext(void *pointer) } /* + * MemoryContextGetParent + * Get the parent context (if any) of the specified context + */ +MemoryContext +MemoryContextGetParent(MemoryContext context) +{ + AssertArg(MemoryContextIsValid(context)); + + return context->parent; +} + +/* * MemoryContextIsEmpty * Is a memory context empty of any allocated space? */ diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 609758ecd23..7fbf1a5efad 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -280,9 +280,9 @@ CreateNewPortal(void) * (before rewriting) was an empty string. Also, the passed commandTag must * be a pointer to a constant string, since it is not copied. * - * If cplan is provided, then it is a cached plan containing the stmts, - * and the caller must have done RevalidateCachedPlan(), causing a refcount - * increment. The refcount will be released when the portal is destroyed. + * If cplan is provided, then it is a cached plan containing the stmts, and + * the caller must have done GetCachedPlan(), causing a refcount increment. + * The refcount will be released when the portal is destroyed. * * If cplan is NULL, then it is the caller's responsibility to ensure that * the passed plan trees have adequate lifetime. Typically this is done by diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 4bcbc20497f..904c6fd97d8 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -118,6 +118,7 @@ extern Oid GetTempToastNamespace(void); extern void ResetTempTableNamespace(void); extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context); +extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path); extern void PushOverrideSearchPath(OverrideSearchPath *newpath); extern void PopOverrideSearchPath(void); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index 63c06ad8d69..52362fa933d 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -44,13 +44,7 @@ extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, /* Low-level access to stored prepared statements */ extern void StorePreparedStatement(const char *stmt_name, - Node *raw_parse_tree, - const char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, + CachedPlanSource *plansource, bool from_sql); extern PreparedStatement *FetchPreparedStatement(const char *stmt_name, bool throwError); diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 7199debb27a..3b1b27ee49e 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -93,6 +93,7 @@ extern SPIPlanPtr SPI_prepare_params(const char *src, ParserSetupHook parserSetup, void *parserSetupArg, int cursorOptions); +extern int SPI_keepplan(SPIPlanPtr plan); extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan); extern int SPI_freeplan(SPIPlanPtr plan); diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h index 5865f532802..3e7bf860948 100644 --- a/src/include/executor/spi_priv.h +++ b/src/include/executor/spi_priv.h @@ -32,27 +32,32 @@ typedef struct } _SPI_connection; /* - * SPI plans have two states: saved or unsaved. + * SPI plans have three states: saved, unsaved, or temporary. * - * 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. + * Ordinarily, the _SPI_plan struct itself as well as the argtypes array + * are in a dedicated memory context identified by 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). * - * 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. + * In an unsaved plan, the plancxt as well as the plancache entries' contexts + * are children of the SPI procedure context, so they'll all disappear at + * function exit. plancache.c also knows that the plancache entries are + * "unsaved", so it doesn't link them into its global list; hence they do + * not respond to inval events. This is OK since we are presumably holding + * adequate locks to prevent other backends from messing with the tables. * - * 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. + * For a saved plan, the plancxt is made a child of CacheMemoryContext + * since it should persist until explicitly destroyed. Likewise, the + * plancache entries will be under CacheMemoryContext since we tell + * plancache.c to save them. We rely on plancache.c to keep the cache + * entries up-to-date as needed in the face of invalidation events. + * + * There are also "temporary" SPI plans, in which the _SPI_plan struct is + * not even palloc'd but just exists in some function's local variable. + * The plancache entries are unsaved and exist under the SPI executor context, + * while additional data such as argtypes and list cells is loose in the SPI + * executor context. Such plans can be identified by having plancxt == NULL. * * 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 diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index a4fb3b5f7f6..9998e2f24d6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1996,7 +1996,10 @@ typedef struct SecLabelStmt #define CURSOR_OPT_NO_SCROLL 0x0004 /* NO SCROLL explicitly given */ #define CURSOR_OPT_INSENSITIVE 0x0008 /* INSENSITIVE */ #define CURSOR_OPT_HOLD 0x0010 /* WITH HOLD */ +/* these planner-control flags do not correspond to any SQL grammar: */ #define CURSOR_OPT_FAST_PLAN 0x0020 /* prefer fast-start plan */ +#define CURSOR_OPT_GENERIC_PLAN 0x0040 /* force use of generic plan */ +#define CURSOR_OPT_CUSTOM_PLAN 0x0080 /* force use of custom plan */ typedef struct DeclareCursorStmt { diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 94c78289b6e..4796f5c7057 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -94,6 +94,7 @@ extern void MemoryContextSetParent(MemoryContext context, MemoryContext new_parent); extern Size GetMemoryChunkSpace(void *pointer); extern MemoryContext GetMemoryChunkContext(void *pointer); +extern MemoryContext MemoryContextGetParent(MemoryContext context); extern bool MemoryContextIsEmpty(MemoryContext context); extern void MemoryContextStats(MemoryContext context); diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h index b8639a59a0e..c8c27bbb207 100644 --- a/src/include/utils/plancache.h +++ b/src/include/utils/plancache.h @@ -18,26 +18,47 @@ #include "access/tupdesc.h" #include "nodes/params.h" +#define CACHEDPLANSOURCE_MAGIC 195726186 +#define CACHEDPLAN_MAGIC 953717834 + /* - * CachedPlanSource represents the portion of a cached plan that persists - * across invalidation/replan cycles. It stores a raw parse tree (required), - * the original source text (also required, as of 8.4), and adjunct data. + * CachedPlanSource (which might better have been called CachedQuery) + * represents a SQL query that we expect to use multiple times. It stores + * the query source text, the raw parse tree, and the analyzed-and-rewritten + * query tree, as well as adjunct data. Cache invalidation can happen as a + * result of DDL affecting objects used by the query. In that case we discard + * the analyzed-and-rewritten query tree, and rebuild it when next needed. + * + * An actual execution plan, represented by CachedPlan, is derived from the + * CachedPlanSource when we need to execute the query. The plan could be + * either generic (usable with any set of plan parameters) or custom (for a + * specific set of parameters). plancache.c contains the logic that decides + * which way to do it for any particular execution. If we are using a generic + * cached plan then it is meant to be re-used across multiple executions, so + * callers must always treat CachedPlans as read-only. + * + * Once successfully built and "saved", CachedPlanSources typically live + * for the life of the backend, although they can be dropped explicitly. + * CachedPlans are reference-counted and go away automatically when the last + * reference is dropped. A CachedPlan can outlive the CachedPlanSource it + * was created from. * - * Normally, both the struct itself and the subsidiary data live in the - * context denoted by the context field, while the linked-to CachedPlan, if - * any, has its own context. Thus an invalidated CachedPlan can be dropped - * when no longer needed, and conversely a CachedPlanSource can be dropped - * without worrying whether any portals depend on particular instances of - * its plan. + * An "unsaved" CachedPlanSource can be used for generating plans, but it + * lives in transient storage and will not be updated in response to sinval + * events. * - * But for entries created by FastCreateCachedPlan, the CachedPlanSource - * and the initial version of the CachedPlan share the same memory context. - * In this case, we treat the memory context as belonging to the CachedPlan. - * The CachedPlanSource has an extra reference-counted link (orig_plan) - * to the CachedPlan, and the memory context goes away when the CachedPlan's - * reference count goes to zero. This arrangement saves overhead for plans - * that aren't expected to live long enough to need replanning, while not - * losing any flexibility if a replan turns out to be necessary. + * CachedPlans made from saved CachedPlanSources are likewise in permanent + * storage, so to avoid memory leaks, the reference-counted references to them + * must be held in permanent data structures or ResourceOwners. CachedPlans + * made from unsaved CachedPlanSources are in children of the caller's + * memory context, so references to them should not be longer-lived than + * that context. (Reference counting is somewhat pro forma in that case, + * though it may be useful if the CachedPlan can be discarded early.) + * + * A CachedPlanSource has two associated memory contexts: one that holds the + * struct itself, the query source text and the raw parse tree, and another + * context that holds the rewritten query tree and associated data. This + * allows the query tree to be discarded easily when it is invalidated. * * Note: the string referenced by commandTag is not subsidiary storage; * it is assumed to be a compile-time-constant string. As with portals, @@ -46,78 +67,93 @@ */ typedef struct CachedPlanSource { + int magic; /* should equal CACHEDPLANSOURCE_MAGIC */ Node *raw_parse_tree; /* output of raw_parser() */ - char *query_string; /* text of query (as of 8.4, never NULL) */ + char *query_string; /* source text of query */ const char *commandTag; /* command tag (a constant!), or NULL */ Oid *param_types; /* array of parameter type OIDs, or NULL */ int num_params; /* length of param_types array */ ParserSetupHook parserSetup; /* alternative parameter spec method */ void *parserSetupArg; int cursor_options; /* cursor options used for planning */ - bool fully_planned; /* do we cache planner or rewriter output? */ bool fixed_result; /* disallow change in result tupdesc? */ - struct OverrideSearchPath *search_path; /* saved search_path */ - int generation; /* counter, starting at 1, for replans */ TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */ - struct CachedPlan *plan; /* link to plan, or NULL if not valid */ - MemoryContext context; /* context containing this CachedPlanSource */ - struct CachedPlan *orig_plan; /* link to plan owning my context */ + struct OverrideSearchPath *search_path; /* saved search_path */ + MemoryContext context; /* memory context holding all above */ + /* These fields describe the current analyzed-and-rewritten query tree: */ + List *query_list; /* list of Query nodes, or NIL if not valid */ + List *relationOids; /* OIDs of relations the queries depend on */ + List *invalItems; /* other dependencies, as PlanInvalItems */ + MemoryContext query_context; /* context holding the above, or NULL */ + /* If we have a generic plan, this is a reference-counted link to it: */ + struct CachedPlan *gplan; /* generic plan, or NULL if not valid */ + /* Some state flags: */ + bool is_complete; /* has CompleteCachedPlan been done? */ + bool is_saved; /* has CachedPlanSource been "saved"? */ + bool is_valid; /* is the query_list currently valid? */ + int generation; /* increments each time we create a plan */ + /* If CachedPlanSource has been saved, it is a member of a global list */ + struct CachedPlanSource *next_saved; /* list link, if so */ + /* State kept to help decide whether to use custom or generic plans: */ + double generic_cost; /* cost of generic plan, or -1 if not known */ + double total_custom_cost; /* total cost of custom plans so far */ + int num_custom_plans; /* number of plans included in total */ } CachedPlanSource; /* - * CachedPlan represents the portion of a cached plan that is discarded when - * invalidation occurs. The reference count includes both the link(s) from the - * parent CachedPlanSource, and any active plan executions, so the plan can be - * discarded exactly when refcount goes to zero. Both the struct itself and - * the subsidiary data live in the context denoted by the context field. + * CachedPlan represents an execution plan derived from a CachedPlanSource. + * The reference count includes both the link from the parent CachedPlanSource + * (if any), and any active plan executions, so the plan can be discarded + * exactly when refcount goes to zero. Both the struct itself and the + * subsidiary data live in the context denoted by the context field. * This makes it easy to free a no-longer-needed cached plan. */ typedef struct CachedPlan { - List *stmt_list; /* list of statement or Query nodes */ - bool fully_planned; /* do we cache planner or rewriter output? */ - bool dead; /* if true, do not use */ + int magic; /* should equal CACHEDPLAN_MAGIC */ + List *stmt_list; /* list of statement nodes (PlannedStmts + * and bare utility statements) */ + bool is_saved; /* is CachedPlan in a long-lived context? */ + bool is_valid; /* is the stmt_list currently valid? */ TransactionId saved_xmin; /* if valid, replan when TransactionXmin * changes from this value */ + int generation; /* parent's generation number for this plan */ int refcount; /* count of live references to this struct */ - int generation; /* counter, starting at 1, for replans */ MemoryContext context; /* context containing this CachedPlan */ - /* These fields are used only in the not-fully-planned case: */ - List *relationOids; /* OIDs of relations the stmts depend on */ - List *invalItems; /* other dependencies, as PlanInvalItems */ } CachedPlan; extern void InitPlanCache(void); +extern void ResetPlanCache(void); + extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, const char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, - bool fully_planned, - bool fixed_result); -extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree, - char *query_string, - const char *commandTag, - Oid *param_types, - int num_params, - int cursor_options, - List *stmt_list, - bool fully_planned, - bool fixed_result, - MemoryContext context); -extern void CachedPlanSetParserHook(CachedPlanSource *plansource, + const char *commandTag); +extern void CompleteCachedPlan(CachedPlanSource *plansource, + List *querytree_list, + MemoryContext querytree_context, + Oid *param_types, + int num_params, ParserSetupHook parserSetup, - void *parserSetupArg); + void *parserSetupArg, + int cursor_options, + bool fixed_result); + +extern void SaveCachedPlan(CachedPlanSource *plansource); extern void DropCachedPlan(CachedPlanSource *plansource); -extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource, - bool useResOwner); -extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); + +extern void CachedPlanSetParentContext(CachedPlanSource *plansource, + MemoryContext newcontext); + +extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource); + extern bool CachedPlanIsValid(CachedPlanSource *plansource); -extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list); -extern void ResetPlanCache(void); +extern List *CachedPlanGetTargetList(CachedPlanSource *plansource); + +extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource, + ParamListInfo boundParams, + bool useResOwner); +extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); #endif /* PLANCACHE_H */ diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 8b5d4dc1915..784e137976c 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -165,7 +165,7 @@ typedef struct plperl_call_data typedef struct plperl_query_desc { char qname[24]; - void *plan; + SPIPlanPtr plan; int nargs; Oid *argtypes; FmgrInfo *arginfuncs; @@ -2951,7 +2951,7 @@ plperl_spi_query(char *query) PG_TRY(); { - void *plan; + SPIPlanPtr plan; Portal portal; /* Make sure the query is validly encoded */ @@ -3118,7 +3118,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv) plperl_query_desc *qdesc; plperl_query_entry *hash_entry; bool found; - void *plan; + SPIPlanPtr plan; int i; MemoryContext oldcontext = CurrentMemoryContext; @@ -3182,13 +3182,9 @@ plperl_spi_prepare(char *query, int argc, SV **argv) * Save the plan into permanent memory (right now it's in the * SPI procCxt, which will go away at function end). ************************************************************/ - qdesc->plan = SPI_saveplan(plan); - if (qdesc->plan == NULL) - elog(ERROR, "SPI_saveplan() failed: %s", - SPI_result_code_string(SPI_result)); - - /* Release the procCxt copy to avoid within-function memory leak */ - SPI_freeplan(plan); + if (SPI_keepplan(plan)) + elog(ERROR, "SPI_keepplan() failed"); + qdesc->plan = plan; /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); @@ -3516,7 +3512,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv) void plperl_spi_freeplan(char *query) { - void *plan; + SPIPlanPtr plan; plperl_query_desc *qdesc; plperl_query_entry *hash_entry; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index de1aece92a1..df785c98511 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -142,6 +142,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, int cursorOptions); static bool exec_simple_check_node(Node *node); static void exec_simple_check_plan(PLpgSQL_expr *expr); +static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan); static bool exec_eval_simple_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, Datum *result, @@ -2020,8 +2021,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt) exec_prepare_plan(estate, query, curvar->cursor_options); /* - * Set up ParamListInfo (note this is only carrying a hook function, not - * any actual data values, at this point) + * Set up ParamListInfo (hook function and possibly data values) */ paramLI = setup_param_list(estate, query); @@ -2991,8 +2991,10 @@ exec_prepare_plan(PLpgSQL_execstate *estate, expr->query, SPI_result_code_string(SPI_result)); } } - expr->plan = SPI_saveplan(plan); - SPI_freeplan(plan); + SPI_keepplan(plan); + expr->plan = plan; + + /* Check to see if it's a simple expression */ exec_simple_check_plan(expr); } @@ -3011,6 +3013,11 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, PLpgSQL_expr *expr = stmt->sqlstmt; /* + * Set up ParamListInfo (hook function and possibly data values) + */ + paramLI = setup_param_list(estate, expr); + + /* * On the first call for this statement generate the plan, and detect * whether the statement is INSERT/UPDATE/DELETE */ @@ -3025,16 +3032,17 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l); ListCell *l2; - foreach(l2, plansource->plan->stmt_list) + Assert(plansource->is_valid); + foreach(l2, plansource->query_list) { - PlannedStmt *p = (PlannedStmt *) lfirst(l2); + Query *q = (Query *) lfirst(l2); - if (IsA(p, PlannedStmt) && - p->canSetTag) + Assert(IsA(q, Query)); + if (q->canSetTag) { - if (p->commandType == CMD_INSERT || - p->commandType == CMD_UPDATE || - p->commandType == CMD_DELETE) + if (q->commandType == CMD_INSERT || + q->commandType == CMD_UPDATE || + q->commandType == CMD_DELETE) stmt->mod_stmt = true; } } @@ -3042,12 +3050,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, } /* - * Set up ParamListInfo (note this is only carrying a hook function, not - * any actual data values, at this point) - */ - paramLI = setup_param_list(estate, expr); - - /* * If we have INTO, then we only need one row back ... but if we have INTO * STRICT, ask for two rows, so that we can verify the statement returns * only one. INSERT/UPDATE/DELETE are always treated strictly. Without @@ -3520,8 +3522,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) } /* - * Set up ParamListInfo (note this is only carrying a hook function, not - * any actual data values, at this point) + * Set up ParamListInfo (hook function and possibly data values) */ paramLI = setup_param_list(estate, query); @@ -4613,8 +4614,7 @@ exec_run_select(PLpgSQL_execstate *estate, exec_prepare_plan(estate, expr, 0); /* - * Set up ParamListInfo (note this is only carrying a hook function, not - * any actual data values, at this point) + * Set up ParamListInfo (hook function and possibly data values) */ paramLI = setup_param_list(estate, expr); @@ -4833,11 +4833,10 @@ loop_exit: * * 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. + * correctness; fortunately it's normally inexpensive to do GetCachedPlan 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 @@ -4873,17 +4872,21 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, /* * 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. + * need to hold a refcount while using the plan, anyway.) 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); + + /* Get the generic plan for the query */ + cplan = GetCachedPlan(plansource, NULL, true); + Assert(cplan == plansource->gplan); + if (cplan->generation != expr->expr_simple_generation) { /* It got replanned ... is it still simple? */ - exec_simple_check_plan(expr); + exec_simple_recheck_plan(expr, cplan); if (expr->expr_simple_expr == NULL) { /* Ooops, release refcount and fail */ @@ -4900,7 +4903,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, /* * Prepare the expression for execution, if it's not been done already in * the current transaction. (This will be forced to happen if we called - * exec_simple_check_plan above.) + * exec_simple_recheck_plan above.) */ if (expr->expr_simple_lxid != curlxid) { @@ -4931,9 +4934,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, * need to free it explicitly, since it will go away at the next reset of * that context. * - * XXX think about avoiding repeated palloc's for param lists? It should - * be possible --- this routine isn't re-entrant anymore. - * * Just for paranoia's sake, save and restore the prior value of * estate->cur_expr, which setup_param_list() sets. */ @@ -4982,10 +4982,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, /* * Create a ParamListInfo to pass to SPI * - * The ParamListInfo array is initially all zeroes, in particular the - * ptype values are all InvalidOid. This causes the executor to call the - * paramFetch hook each time it wants a value. We thus evaluate only the - * parameters actually demanded. + * We fill in the values for any expression parameters that are plain + * PLpgSQL_var datums; these are cheap and safe to evaluate, and by setting + * them with PARAM_FLAG_CONST flags, we allow the planner to use those values + * in custom plans. However, parameters that are not plain PLpgSQL_vars + * should not be evaluated here, because they could throw errors (for example + * "no such record field") and we do not want that to happen in a part of + * the expression that might never be evaluated at runtime. To handle those + * parameters, we set up a paramFetch hook for the executor to call when it + * wants a not-presupplied value. * * The result is a locally palloc'd array that should be pfree'd after use; * but note it can be NULL. @@ -4997,21 +5002,42 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) /* * Could we re-use these arrays instead of palloc'ing a new one each time? - * However, we'd have to zero the array each time anyway, since new values - * might have been assigned to the variables. + * However, we'd have to re-fill the array each time anyway, since new + * values might have been assigned to the variables. */ if (estate->ndatums > 0) { - /* sizeof(ParamListInfoData) includes the first array element */ + Bitmapset *tmpset; + int dno; + paramLI = (ParamListInfo) - palloc0(sizeof(ParamListInfoData) + - (estate->ndatums - 1) * sizeof(ParamExternData)); + palloc0(offsetof(ParamListInfoData, params) + + estate->ndatums * sizeof(ParamExternData)); paramLI->paramFetch = plpgsql_param_fetch; paramLI->paramFetchArg = (void *) estate; paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup; paramLI->parserSetupArg = (void *) expr; paramLI->numParams = estate->ndatums; + /* Instantiate values for "safe" parameters of the expression */ + tmpset = bms_copy(expr->paramnos); + while ((dno = bms_first_member(tmpset)) >= 0) + { + PLpgSQL_datum *datum = estate->datums[dno]; + + if (datum->dtype == PLPGSQL_DTYPE_VAR) + { + PLpgSQL_var *var = (PLpgSQL_var *) datum; + ParamExternData *prm = ¶mLI->params[dno]; + + prm->value = var->value; + prm->isnull = var->isnull; + prm->pflags = PARAM_FLAG_CONST; + prm->ptype = var->datatype->typoid; + } + } + bms_free(tmpset); + /* * Set up link to active expr where the hook functions can find it. * Callers must save and restore cur_expr if there is any chance that @@ -5628,30 +5654,113 @@ static void exec_simple_check_plan(PLpgSQL_expr *expr) { CachedPlanSource *plansource; - PlannedStmt *stmt; - Plan *plan; - TargetEntry *tle; + Query *query; + CachedPlan *cplan; /* * Initialize to "not simple", and remember the plan generation number we - * last checked. (If the query produces more or less than one parsetree + * last checked. (If we don't get as far as obtaining a plan to check, * 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 + * We can only test queries that resulted in exactly one CachedPlanSource */ if (list_length(expr->plan->plancache_list) != 1) return; plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list); - expr->expr_simple_generation = plansource->generation; - if (list_length(plansource->plan->stmt_list) != 1) + + /* + * Do some checking on the analyzed-and-rewritten form of the query. + * These checks are basically redundant with the tests in + * exec_simple_recheck_plan, but the point is to avoid building a plan if + * possible. Since this function is only + * called immediately after creating the CachedPlanSource, we need not + * worry about the query being stale. + */ + + /* + * 1. There must be one single querytree. + */ + if (list_length(plansource->query_list) != 1) return; + query = (Query *) linitial(plansource->query_list); - stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list); + /* + * 2. It must be a plain SELECT query without any input tables + */ + if (!IsA(query, Query)) + return; + if (query->commandType != CMD_SELECT || query->intoClause) + return; + if (query->rtable != NIL) + return; + + /* + * 3. Can't have any subplans, aggregates, qual clauses either + */ + if (query->hasAggs || + query->hasWindowFuncs || + query->hasSubLinks || + query->hasForUpdate || + query->cteList || + query->jointree->quals || + query->groupClause || + query->havingQual || + query->windowClause || + query->distinctClause || + query->sortClause || + query->limitOffset || + query->limitCount || + query->setOperations) + return; + + /* + * 4. The query must have a single attribute as result + */ + if (list_length(query->targetList) != 1) + return; + + /* + * OK, it seems worth constructing a plan for more careful checking. + */ + + /* Get the generic plan for the query */ + cplan = GetCachedPlan(plansource, NULL, true); + Assert(cplan == plansource->gplan); + + /* Share the remaining work with recheck code path */ + exec_simple_recheck_plan(expr, cplan); + + /* Release our plan refcount */ + ReleaseCachedPlan(cplan, true); +} + +/* + * exec_simple_recheck_plan --- check for simple plan once we have CachedPlan + */ +static void +exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan) +{ + PlannedStmt *stmt; + Plan *plan; + TargetEntry *tle; + + /* + * Initialize to "not simple", and remember the plan generation number we + * last checked. + */ + expr->expr_simple_expr = NULL; + expr->expr_simple_generation = cplan->generation; + + /* + * 1. There must be one single plantree + */ + if (list_length(cplan->stmt_list) != 1) + return; + stmt = (PlannedStmt *) linitial(cplan->stmt_list); /* * 2. It must be a RESULT plan --> no scan's required diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 17c583ec357..93e8043284e 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -287,7 +287,7 @@ typedef struct PLySubtransactionData typedef struct PLyPlanObject { PyObject_HEAD - void *plan; /* return of an SPI_saveplan */ + SPIPlanPtr plan; int nargs; Oid *types; Datum *values; @@ -3327,7 +3327,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args) PyObject *list = NULL; PyObject *volatile optr = NULL; char *query; - void *tmpplan; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; volatile int nargs; @@ -3431,12 +3430,8 @@ PLy_spi_prepare(PyObject *self, PyObject *args) SPI_result_code_string(SPI_result)); /* transfer plan from procCxt to topCxt */ - tmpplan = plan->plan; - plan->plan = SPI_saveplan(tmpplan); - SPI_freeplan(tmpplan); - if (plan->plan == NULL) - elog(ERROR, "SPI_saveplan failed: %s", - SPI_result_code_string(SPI_result)); + if (SPI_keepplan(plan->plan)) + elog(ERROR, "SPI_keepplan failed"); /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index be8fe7a0f21..ecde90626bf 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -128,7 +128,7 @@ typedef struct pltcl_proc_desc typedef struct pltcl_query_desc { char qname[20]; - void *plan; + SPIPlanPtr plan; int nargs; Oid *argtypes; FmgrInfo *arginfuncs; @@ -2024,7 +2024,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp, * pltcl_SPI_prepare() - Builtin support for prepared plans * The Tcl command SPI_prepare * always saves the plan using - * SPI_saveplan and returns a key for + * SPI_keepplan and returns a key for * access. There is no chance to prepare * and not save the plan currently. **********************************************************************/ @@ -2035,7 +2035,6 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, int nargs; CONST84 char **args; pltcl_query_desc *qdesc; - void *plan; int i; Tcl_HashEntry *hashent; int hashnew; @@ -2103,22 +2102,18 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, * Prepare the plan and check for errors ************************************************************/ UTF_BEGIN; - plan = SPI_prepare(UTF_U2E(argv[1]), nargs, qdesc->argtypes); + qdesc->plan = SPI_prepare(UTF_U2E(argv[1]), nargs, qdesc->argtypes); UTF_END; - if (plan == NULL) + if (qdesc->plan == NULL) elog(ERROR, "SPI_prepare() failed"); /************************************************************ * Save the plan into permanent memory (right now it's in the * SPI procCxt, which will go away at function end). ************************************************************/ - qdesc->plan = SPI_saveplan(plan); - if (qdesc->plan == NULL) - elog(ERROR, "SPI_saveplan() failed"); - - /* Release the procCxt copy to avoid within-function memory leak */ - SPI_freeplan(plan); + if (SPI_keepplan(qdesc->plan)) + elog(ERROR, "SPI_keepplan() failed"); pltcl_subtrans_commit(oldcontext, oldowner); } diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index daee5af49e4..e5136cfa7c8 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -622,9 +622,8 @@ ttdummy(PG_FUNCTION_ARGS) if (pplan == NULL) elog(ERROR, "ttdummy (%s): SPI_prepare returned %d", relname, SPI_result); - pplan = SPI_saveplan(pplan); - if (pplan == NULL) - elog(ERROR, "ttdummy (%s): SPI_saveplan returned %d", relname, SPI_result); + if (SPI_keepplan(pplan)) + elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname); splan = pplan; } |