Commit e6faf910 authored by Tom Lane's avatar Tom Lane

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.
parent 09e98a3e
......@@ -190,12 +190,11 @@ check_primary_key(PG_FUNCTION_ARGS)
/*
* Remember that SPI_prepare places plan in current memory context -
* so, we have to save plan in Top memory context for latter use.
* so, we have to save plan in Top memory context for later use.
*/
pplan = SPI_saveplan(pplan);
if (pplan == NULL)
if (SPI_keepplan(pplan))
/* internal error */
elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result);
elog(ERROR, "check_primary_key: SPI_keepplan failed");
plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
*(plan->splan) = pplan;
plan->nplans = 1;
......@@ -537,13 +536,12 @@ check_foreign_key(PG_FUNCTION_ARGS)
/*
* Remember that SPI_prepare places plan in current memory context
* - so, we have to save plan in Top memory context for latter
* - so, we have to save plan in Top memory context for later
* use.
*/
pplan = SPI_saveplan(pplan);
if (pplan == NULL)
if (SPI_keepplan(pplan))
/* internal error */
elog(ERROR, "check_foreign_key: SPI_saveplan returned %d", SPI_result);
elog(ERROR, "check_foreign_key: SPI_keepplan failed");
plan->splan[r] = pplan;
......
......@@ -345,11 +345,10 @@ timetravel(PG_FUNCTION_ARGS)
/*
* Remember that SPI_prepare places plan in current memory context -
* so, we have to save plan in Top memory context for latter use.
* so, we have to save plan in Top memory context for later use.
*/
pplan = SPI_saveplan(pplan);
if (pplan == NULL)
elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
if (SPI_keepplan(pplan))
elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname);
plan->splan = pplan;
}
......
This diff is collapsed.
......@@ -125,9 +125,8 @@
into multiple steps. The state retained between steps is represented
by two types of objects: <firstterm>prepared statements</> and
<firstterm>portals</>. A prepared statement represents the result of
parsing, semantic analysis, and (optionally) planning of a textual query
string.
A prepared statement is not necessarily ready to execute, because it might
parsing and semantic analysis of a textual query string.
A prepared statement is not in itself ready to execute, because it might
lack specific values for <firstterm>parameters</>. A portal represents
a ready-to-execute or already-partially-executed statement, with any
missing parameter values filled in. (For <command>SELECT</> statements,
......@@ -692,7 +691,7 @@
the unnamed statement as destination is issued. (Note that a simple
Query message also destroys the unnamed statement.) Named prepared
statements must be explicitly closed before they can be redefined by
a Parse message, but this is not required for the unnamed statement.
another Parse message, but this is not required for the unnamed statement.
Named prepared statements can also be created and accessed at the SQL
command level, using <command>PREPARE</> and <command>EXECUTE</>.
</para>
......@@ -722,44 +721,23 @@
</note>
<para>
Query planning for named prepared-statement objects occurs when the Parse
message is processed. If a query will be repeatedly executed with
different parameters, it might be beneficial to send a single Parse message
containing a parameterized query, followed by multiple Bind
and Execute messages. This will avoid replanning the query on each
execution.
Query planning typically occurs when the Bind message is processed.
If the prepared statement has no parameters, or is executed repeatedly,
the server might save the created plan and re-use it during subsequent
Bind messages for the same prepared statement. However, it will do so
only if it finds that a generic plan can be created that is not much
less efficient than a plan that depends on the specific parameter values
supplied. This happens transparently so far as the protocol is concerned.
</para>
<para>
The unnamed prepared statement is likewise planned during Parse processing
if the Parse message defines no parameters. But if there are parameters,
query planning occurs every time Bind parameters are supplied. This allows the
planner to make use of the actual values of the parameters provided by
each Bind message, rather than use generic estimates.
</para>
<note>
<para>
Query plans generated from a parameterized query might be less
efficient than query plans generated from an equivalent query with actual
parameter values substituted. The query planner cannot make decisions
based on actual parameter values (for example, index selectivity) when
planning a parameterized query assigned to a named prepared-statement
object. This possible penalty is avoided when using the unnamed
statement, since it is not planned until actual parameter values are
available. The cost is that planning must occur afresh for each Bind,
even if the query stays the same.
</para>
</note>
<para>
If successfully created, a named portal object lasts till the end of the
current transaction, unless explicitly destroyed. An unnamed portal is
destroyed at the end of the transaction, or as soon as the next Bind
statement specifying the unnamed portal as destination is issued. (Note
that a simple Query message also destroys the unnamed portal.) Named
portals must be explicitly closed before they can be redefined by a Bind
message, but this is not required for the unnamed portal.
portals must be explicitly closed before they can be redefined by another
Bind message, but this is not required for the unnamed portal.
Named portals can also be created and accessed at the SQL
command level, using <command>DECLARE CURSOR</> and <command>FETCH</>.
</para>
......@@ -1280,7 +1258,9 @@
The frontend should also be prepared to handle an ErrorMessage
response to SSLRequest from the server. This would only occur if
the server predates the addition of <acronym>SSL</acronym> support
to <productname>PostgreSQL</>. In this case the connection must
to <productname>PostgreSQL</>. (Such servers are now very ancient,
and likely do not exist in the wild anymore.)
In this case the connection must
be closed, but the frontend might choose to open a fresh connection
and proceed without requesting <acronym>SSL</acronym>.
</para>
......
......@@ -37,11 +37,11 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
<command>PREPARE</command> creates a prepared statement. A prepared
statement is a server-side object that can be used to optimize
performance. When the <command>PREPARE</command> statement is
executed, the specified statement is parsed, rewritten, and
planned. When an <command>EXECUTE</command> command is subsequently
issued, the prepared statement need only be executed. Thus, the
parsing, rewriting, and planning stages are only performed once,
instead of every time the statement is executed.
executed, the specified statement is parsed, analyzed, and rewritten.
When an <command>EXECUTE</command> command is subsequently
issued, the prepared statement is planned and executed. This division
of labor avoids repetitive parse analysis work, while allowing
the execution plan to depend on the specific parameter values supplied.
</para>
<para>
......@@ -65,7 +65,7 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
forgotten, so it must be recreated before being used again. This
also means that a single prepared statement cannot be used by
multiple simultaneous database clients; however, each client can create
their own prepared statement to use. The prepared statement can be
their own prepared statement to use. Prepared statements can be
manually cleaned up using the <xref linkend="sql-deallocate"> command.
</para>
......@@ -127,20 +127,22 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
<title>Notes</title>
<para>
In some situations, the query plan produced for a prepared
statement will be inferior to the query plan that would have been
chosen if the statement had been submitted and executed
normally. This is because when the statement is planned and the
planner attempts to determine the optimal query plan, the actual
values of any parameters specified in the statement are
unavailable. <productname>PostgreSQL</productname> collects
statistics on the distribution of data in the table, and can use
constant values in a statement to make guesses about the likely
result of executing the statement. Since this data is unavailable
when planning prepared statements with parameters, the chosen plan
might be suboptimal. To examine the query plan
<productname>PostgreSQL</productname> has chosen for a prepared
statement, use <xref linkend="sql-explain">.
If a prepared statement is executed enough times, the server may eventually
decide to save and re-use a generic plan rather than re-planning each time.
This will occur immediately if the prepared statement has no parameters;
otherwise it occurs only if the generic plan appears to be not much more
expensive than a plan that depends on specific parameter values.
Typically, a generic plan will be selected only if the query's performance
is estimated to be fairly insensitive to the specific parameter values
supplied.
</para>
<para>
To examine the query plan <productname>PostgreSQL</productname> is using
for a prepared statement, use <xref linkend="sql-explain">.
If a generic plan is in use, it will contain parameter symbols
<literal>$<replaceable>n</></literal>, while a custom plan will have the
current actual parameter values substituted into it.
</para>
<para>
......@@ -151,7 +153,7 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
</para>
<para>
You can see all available prepared statements of a session by querying the
You can see all prepared statements available in the session by querying the
<link linkend="view-pg-prepared-statements"><structname>pg_prepared_statements</structname></link>
system view.
</para>
......
This diff is collapsed.
......@@ -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
*
......
......@@ -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;
/* 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));
/* Get the plan's primary targetlist */
tlist = CachedPlanGetTargetList(stmt->plansource);
/* 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 */
......
This diff is collapsed.
This diff is collapsed.
......@@ -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);
}
......
......@@ -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;
}
/*
......
This diff is collapsed.
......@@ -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?
......
......@@ -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
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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
......
......@@ -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
{
......
......@@ -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);
......
This diff is collapsed.
......@@ -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;
......
This diff is collapsed.
......@@ -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();
......
......@@ -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);
}
......
......@@ -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;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment