1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Redesign the plancache mechanism for more flexibility and efficiency.

Rewrite plancache.c so that a "cached plan" (which is rather a misnomer
at this point) can support generation of custom, parameter-value-dependent
plans, and can make an intelligent choice between using custom plans and
the traditional generic-plan approach.  The specific choice algorithm
implemented here can probably be improved in future, but this commit is
all about getting the mechanism in place, not the policy.

In addition, restructure the API to greatly reduce the amount of extraneous
data copying needed.  The main compromise needed to make that possible was
to split the initial creation of a CachedPlanSource into two steps.  It's
worth noting in particular that SPI_saveplan is now deprecated in favor of
SPI_keepplan, which accomplishes the same end result with zero data
copying, and no need to then spend even more cycles throwing away the
original SPIPlan.  The risk of long-term memory leaks while manipulating
SPIPlans has also been greatly reduced.  Most of this improvement is based
on use of the recently-added MemoryContextSetParent primitive.
This commit is contained in:
Tom Lane
2011-09-16 00:42:53 -04:00
parent 09e98a3e17
commit e6faf910d7
27 changed files with 1994 additions and 1424 deletions

View File

@@ -2940,6 +2940,24 @@ GetOverrideSearchPath(MemoryContext context)
return result;
}
/*
* 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
*

View File

@@ -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 = &paramLI->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;
/* Get the plan's primary targetlist */
tlist = CachedPlanGetTargetList(stmt->plansource);
/* 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);
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 */

View File

@@ -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;
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
foreach(lc, plan->plancache_list)
{
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 = GetCachedPlan(plansource, paramLI, false);
stmt_list = cplan->stmt_list;
if (!plan->saved)
{
/* Replan if needed, and increment plan refcount for portal */
cplan = RevalidateCachedPlan(plansource, false);
stmt_list = cplan->stmt_list;
}
else
{
/* No replan, but copy the plan into the portal's context */
/*
* 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)
{
ListCell *lc;
Assert(plan->magic == _SPI_PLAN_MAGIC);
if (plan->saved)
{
ListCell *lc;
foreach(lc, plan->plancache_list)
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
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,23 +2266,98 @@ _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 the input is a temporary SPIPlan */
Assert(plan->magic == _SPI_PLAN_MAGIC);
Assert(plan->plancxt == NULL);
/*
* 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",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(plancxt);
/* 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;
newplan->plancache_list = NIL;
newplan->plancxt = plancxt;
newplan->cursor_options = plan->cursor_options;
newplan->nargs = plan->nargs;
if (plan->nargs > 0)
{
newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
}
else
newplan->argtypes = NULL;
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);
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.
*/
static SPIPlanPtr
_SPI_save_plan(SPIPlanPtr plan)
{
SPIPlanPtr newplan;
MemoryContext plancxt;
MemoryContext oldcxt;
ListCell *lc;
Assert(!plan->saved); /* not currently supported */
/*
* Create a memory context for the plan. We don't expect the plan to be
* very large, so use smaller-than-default alloc parameters.
* very large, so use smaller-than-default alloc parameters. It's a
* transient context until we finish copying everything.
*/
plancxt = AllocSetContextCreate(parentcxt,
plancxt = AllocSetContextCreate(CurrentMemoryContext,
"SPI Plan",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
@@ -2324,113 +2382,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
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;
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;
newsource = CopyCachedPlan(plansource);
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
}
MemoryContextSwitchTo(oldcxt);
return newplan;
}
/*
* Make a "saved" copy of the given plan, entrusting everything to plancache.c
*/
static SPIPlanPtr
_SPI_save_plan(SPIPlanPtr plan)
{
SPIPlanPtr newplan;
MemoryContext plancxt;
MemoryContext oldcxt;
ListCell *lc;
Assert(!plan->saved); /* not currently supported */
/*
* Create a memory context for the plan. We don't expect the plan to be
* very large, so use smaller-than-default alloc parameters.
* 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.
*/
plancxt = AllocSetContextCreate(CacheMemoryContext,
"SPI Plan",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(plancxt);
/* Copy the SPI plan into its own context */
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC;
newplan->saved = true;
newplan->plancache_list = NIL;
newplan->plancxt = plancxt;
newplan->cursor_options = plan->cursor_options;
newplan->nargs = plan->nargs;
if (plan->nargs > 0)
{
newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
}
else
newplan->argtypes = NULL;
newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg;
MemoryContextSetParent(newplan->plancxt, CacheMemoryContext);
foreach(lc, plan->plancache_list)
foreach(lc, newplan->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);
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
SaveCachedPlan(plansource);
}
MemoryContextSwitchTo(oldcxt);
return newplan;
}

View File

@@ -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,
&paramTypes,
&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 */
/*
* 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();
/*
* Store the query as a prepared statement. See above comments.
*/
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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
/*

File diff suppressed because it is too large Load Diff

View File

@@ -342,6 +342,18 @@ GetMemoryChunkContext(void *pointer)
return header->context;
}
/*
* 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?

View File

@@ -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