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

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

View File

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

View File

@ -839,12 +839,10 @@ PREPARE <replaceable>statement_name</>(integer, integer) AS SELECT $1 &lt; $2;
and then this prepared statement is <command>EXECUTE</>d for each
execution of the <command>IF</> statement, with the current values
of the <application>PL/pgSQL</application> variables supplied as
parameter values.
The query plan prepared in this way is saved for the life of the database
connection, as described in
<xref linkend="plpgsql-plan-caching">. Normally these details are
parameter values. Normally these details are
not important to a <application>PL/pgSQL</application> user, but
they are useful to know when trying to diagnose a problem.
More information appears in <xref linkend="plpgsql-plan-caching">.
</para>
</sect1>
@ -919,10 +917,9 @@ my_record.user_id := 20;
<para>
When executing a SQL command in this way,
<application>PL/pgSQL</application> plans the command just once
and re-uses the plan on subsequent executions, for the life of
the database connection. The implications of this are discussed
in detail in <xref linkend="plpgsql-plan-caching">.
<application>PL/pgSQL</application> may cache and re-use the execution
plan for the command, as discussed in
<xref linkend="plpgsql-plan-caching">.
</para>
<para>
@ -1137,8 +1134,8 @@ EXECUTE <replaceable class="command">command-string</replaceable> <optional> INT
<para>
Also, there is no plan caching for commands executed via
<command>EXECUTE</command>. Instead, the
command is prepared each time the statement is run. Thus the command
<command>EXECUTE</command>. Instead, the command is always planned
each time the statement is run. Thus the command
string can be dynamically created within the function to perform
actions on different tables and columns.
</para>
@ -1206,11 +1203,11 @@ EXECUTE 'SELECT count(*) FROM '
The important difference is that <command>EXECUTE</> will re-plan
the command on each execution, generating a plan that is specific
to the current parameter values; whereas
<application>PL/pgSQL</application> normally creates a generic plan
and caches it for re-use. In situations where the best plan depends
strongly on the parameter values, <command>EXECUTE</> can be
significantly faster; while when the plan is not sensitive to parameter
values, re-planning will be a waste.
<application>PL/pgSQL</application> may otherwise create a generic plan
and cache it for re-use. In situations where the best plan depends
strongly on the parameter values, it can be helpful to use
<command>EXECUTE</> to positively ensure that a generic plan is not
selected.
</para>
<para>
@ -4103,17 +4100,14 @@ $$ LANGUAGE plpgsql;
</indexterm>
As each expression and <acronym>SQL</acronym> command is first
executed in the function, the <application>PL/pgSQL</> interpreter
creates a prepared execution plan (using the
<acronym>SPI</acronym> manager's <function>SPI_prepare</function>
and <function>SPI_saveplan</function> functions).
parses and analyzes the command to create a prepared statement,
using the <acronym>SPI</acronym> manager's
<function>SPI_prepare</function> function.
Subsequent visits to that expression or command
reuse the prepared plan. Thus, a function with conditional code
that contains many statements for which execution plans might be
required will only prepare and save those plans that are really
used during the lifetime of the database connection. This can
substantially reduce the total amount of time required to parse
and generate execution plans for the statements in a
<application>PL/pgSQL</> function. A disadvantage is that errors
reuse the prepared statement. Thus, a function with conditional code
paths that are seldom visited will never incur the overhead of
analyzing those commands that are never executed within the current
session. A disadvantage is that errors
in a specific expression or command cannot be detected until that
part of the function is reached in execution. (Trivial syntax
errors will be detected during the initial parsing pass, but
@ -4121,46 +4115,31 @@ $$ LANGUAGE plpgsql;
</para>
<para>
A saved plan will be re-planned automatically if there is any schema
change to any table used in the query, or if any user-defined function
used in the query is redefined. This makes the re-use of prepared plans
transparent in most cases, but there are corner cases where a stale plan
might be re-used. An example is that dropping and re-creating a
user-defined operator won't affect already-cached plans; they'll continue
to call the original operator's underlying function, if that has not been
changed. When necessary, the cache can be flushed by starting a fresh
database session.
<application>PL/pgSQL</> (or more precisely, the SPI manager) can
furthermore attempt to cache the execution plan associated with any
particular prepared statement. If a cached plan is not used, then
a fresh execution plan is generated on each visit to the statement,
and the current parameter values (that is, <application>PL/pgSQL</>
variable values) can be used to optimize the selected plan. If the
statement has no parameters, or is executed many times, the SPI manager
will consider creating a <firstterm>generic</> plan that is not dependent
on specific parameter values, and caching that for re-use. Typically
this will happen only if the execution plan is not very sensitive to
the values of the <application>PL/pgSQL</> variables referenced in it.
If it is, generating a plan each time is a net win.
</para>
<para>
Because <application>PL/pgSQL</application> saves execution plans
in this way, SQL commands that appear directly in a
Because <application>PL/pgSQL</application> saves prepared statements
and sometimes execution plans in this way,
SQL commands that appear directly in a
<application>PL/pgSQL</application> function must refer to the
same tables and columns on every execution; that is, you cannot use
a parameter as the name of a table or column in an SQL command. To get
around this restriction, you can construct dynamic commands using
the <application>PL/pgSQL</application> <command>EXECUTE</command>
statement &mdash; at the price of constructing a new execution plan on
every execution.
</para>
<para>
Another important point is that the prepared plans are parameterized
to allow the values of <application>PL/pgSQL</application> variables
to change from one use to the next, as discussed in detail above.
Sometimes this means that a plan is less efficient than it would be
if generated for a specific variable value. As an example, consider
<programlisting>
SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term;
</programlisting>
where <literal>search_term</> is a <application>PL/pgSQL</application>
variable. The cached plan for this query will never use an index on
<structfield>word</>, since the planner cannot assume that the
<literal>LIKE</> pattern will be left-anchored at run time. To use
an index the query must be planned with a specific constant
<literal>LIKE</> pattern provided. This is another situation where
<command>EXECUTE</command> can be used to force a new plan to be
generated for each execution.
statement &mdash; at the price of performing new parse analysis and
constructing a new execution plan on every execution.
</para>
<para>
@ -4168,14 +4147,14 @@ SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term;
connection. When fields of a record variable are used in
expressions or statements, the data types of the fields must not
change from one call of the function to the next, since each
expression will be planned using the data type that is present
expression will be analyzed using the data type that is present
when the expression is first reached. <command>EXECUTE</command> can be
used to get around this problem when necessary.
</para>
<para>
If the same function is used as a trigger for more than one table,
<application>PL/pgSQL</application> prepares and caches plans
<application>PL/pgSQL</application> prepares and caches statements
independently for each such table &mdash; that is, there is a cache
for each trigger function and table combination, not just for each
function. This alleviates some of the problems with varying
@ -4186,14 +4165,14 @@ SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term;
<para>
Likewise, functions having polymorphic argument types have a separate
plan cache for each combination of actual argument types they have been
invoked for, so that data type differences do not cause unexpected
statement cache for each combination of actual argument types they have
been invoked for, so that data type differences do not cause unexpected
failures.
</para>
<para>
Plan caching can sometimes have surprising effects on the interpretation
of time-sensitive values. For example there
Statement caching can sometimes have surprising effects on the
interpretation of time-sensitive values. For example there
is a difference between what these two functions do:
<programlisting>
@ -4221,15 +4200,17 @@ $$ LANGUAGE plpgsql;
<para>
In the case of <function>logfunc1</function>, the
<productname>PostgreSQL</productname> main parser knows when
preparing the plan for the <command>INSERT</command> that the
analyzing the <command>INSERT</command> that the
string <literal>'now'</literal> should be interpreted as
<type>timestamp</type>, because the target column of
<classname>logtable</classname> is of that type. Thus,
<literal>'now'</literal> will be converted to a constant when the
<command>INSERT</command> is planned, and then used in all
<literal>'now'</literal> will be converted to a <type>timestamp</type>
constant when the
<command>INSERT</command> is analyzed, and then used in all
invocations of <function>logfunc1</function> during the lifetime
of the session. Needless to say, this isn't what the programmer
wanted.
wanted. A better idea is to use the <literal>now()</> or
<literal>current_timestamp</> function.
</para>
<para>
@ -4243,7 +4224,9 @@ $$ LANGUAGE plpgsql;
string to the <type>timestamp</type> type by calling the
<function>text_out</function> and <function>timestamp_in</function>
functions for the conversion. So, the computed time stamp is updated
on each execution as the programmer expects.
on each execution as the programmer expects. Even though this
happens to work as expected, it's not terribly efficient, so
use of the <literal>now()</> function would still be a better idea.
</para>
</sect2>

View File

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

View File

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

View File

@ -733,7 +733,8 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>,
<para>
Similar results can be achieved with <function>SPI_prepare</> followed by
<function>SPI_execute_plan</function>; however, when using this function
the query plan is customized to the specific parameter values provided.
the query plan is always customized to the specific parameter values
provided.
For one-time query execution, this function should be preferred.
If the same command is to be executed with many different parameters,
either method might be faster, depending on the cost of re-planning
@ -840,7 +841,7 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>,
<refnamediv>
<refname>SPI_prepare</refname>
<refpurpose>prepare a plan for a command, without executing it yet</refpurpose>
<refpurpose>prepare a statement, without executing it yet</refpurpose>
</refnamediv>
<indexterm><primary>SPI_prepare</primary></indexterm>
@ -855,17 +856,22 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<title>Description</title>
<para>
<function>SPI_prepare</function> creates and returns an execution
plan for the specified command, but doesn't execute the command.
This function should only be called from a connected procedure.
<function>SPI_prepare</function> creates and returns a prepared
statement for the specified command, but doesn't execute the command.
The prepared statement can later be executed repeatedly using
<function>SPI_execute_plan</function>.
</para>
<para>
When the same or a similar command is to be executed repeatedly, it
might be advantageous to perform the planning only once.
<function>SPI_prepare</function> converts a command string into an
execution plan that can be executed repeatedly using
<function>SPI_execute_plan</function>.
is generally advantageous to perform parse analysis only once, and
might furthermore be advantageous to re-use an execution plan for the
command.
<function>SPI_prepare</function> converts a command string into a
prepared statement that encapsulates the results of parse analysis.
The prepared statement also provides a place for caching an execution plan
if it is found that generating a custom plan for each execution is not
helpful.
</para>
<para>
@ -878,11 +884,11 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
</para>
<para>
The plan returned by <function>SPI_prepare</function> can be used
The statement returned by <function>SPI_prepare</function> can be used
only in the current invocation of the procedure, since
<function>SPI_finish</function> frees memory allocated for a plan.
But a plan can be saved for longer using the function
<function>SPI_saveplan</function>.
<function>SPI_finish</function> frees memory allocated for such a
statement. But the statement can be saved for longer using the functions
<function>SPI_keepplan</function> or <function>SPI_saveplan</function>.
</para>
</refsect1>
@ -925,7 +931,8 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<para>
<function>SPI_prepare</function> returns a non-null pointer to an
execution plan. On error, <symbol>NULL</symbol> will be returned,
<type>SPIPlan</>, which is an opaque struct representing a prepared
statement. On error, <symbol>NULL</symbol> will be returned,
and <varname>SPI_result</varname> will be set to one of the same
error codes used by <function>SPI_execute</function>, except that
it is set to <symbol>SPI_ERROR_ARGUMENT</symbol> if
@ -938,6 +945,26 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<refsect1>
<title>Notes</title>
<para>
If no parameters are defined, a generic plan will be created at the
first use of <function>SPI_execute_plan</function>, and used for all
subsequent executions as well. If there are parameters, the first few uses
of <function>SPI_execute_plan</function> will generate custom plans
that are specific to the supplied parameter values. After enough uses
of the same prepared statement, <function>SPI_execute_plan</function> will
build a generic plan, and if that is not too much more expensive than the
custom plans, it will start using the generic plan instead of re-planning
each time. If this default behavior is unsuitable, you can alter it by
passing the <literal>CURSOR_OPT_GENERIC_PLAN</> or
<literal>CURSOR_OPT_CUSTOM_PLAN</> flag to
<function>SPI_prepare_cursor</function>, to force use of generic or custom
plans respectively.
</para>
<para>
This function should only be called from a connected procedure.
</para>
<para>
<type>SPIPlanPtr</> is declared as a pointer to an opaque struct type in
<filename>spi.h</>. It is unwise to try to access its contents
@ -946,10 +973,8 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
</para>
<para>
There is a disadvantage to using parameters: since the planner does
not know the values that will be supplied for the parameters, it
might make worse planning choices than it would make for a normal
command with all constants visible.
The name <type>SPIPlanPtr</> is somewhat historical, since the data
structure no longer necessarily contains an execution plan.
</para>
</refsect1>
</refentry>
@ -964,7 +989,7 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<refnamediv>
<refname>SPI_prepare_cursor</refname>
<refpurpose>prepare a plan for a command, without executing it yet</refpurpose>
<refpurpose>prepare a statement, without executing it yet</refpurpose>
</refnamediv>
<indexterm><primary>SPI_prepare_cursor</primary></indexterm>
@ -1047,8 +1072,10 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
<para>
Useful bits to set in <parameter>cursorOptions</> include
<symbol>CURSOR_OPT_SCROLL</symbol>,
<symbol>CURSOR_OPT_NO_SCROLL</symbol>, and
<symbol>CURSOR_OPT_FAST_PLAN</symbol>. Note in particular that
<symbol>CURSOR_OPT_NO_SCROLL</symbol>,
<symbol>CURSOR_OPT_FAST_PLAN</symbol>,
<symbol>CURSOR_OPT_GENERIC_PLAN</symbol>, and
<symbol>CURSOR_OPT_CUSTOM_PLAN</symbol>. Note in particular that
<symbol>CURSOR_OPT_HOLD</symbol> is ignored.
</para>
</refsect1>
@ -1064,7 +1091,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
<refnamediv>
<refname>SPI_prepare_params</refname>
<refpurpose>prepare a plan for a command, without executing it yet</refpurpose>
<refpurpose>prepare a statement, without executing it yet</refpurpose>
</refnamediv>
<indexterm><primary>SPI_prepare_params</primary></indexterm>
@ -1082,8 +1109,8 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
<title>Description</title>
<para>
<function>SPI_prepare_params</function> creates and returns an execution
plan for the specified command, but doesn't execute the command.
<function>SPI_prepare_params</function> creates and returns a prepared
statement for the specified command, but doesn't execute the command.
This function is equivalent to <function>SPI_prepare_cursor</function>,
with the addition that the caller can specify parser hook functions
to control the parsing of external parameter references.
@ -1152,7 +1179,7 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
<refnamediv>
<refname>SPI_getargcount</refname>
<refpurpose>return the number of arguments needed by a plan
<refpurpose>return the number of arguments needed by a statement
prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
@ -1169,7 +1196,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
<para>
<function>SPI_getargcount</function> returns the number of arguments needed
to execute a plan prepared by <function>SPI_prepare</function>.
to execute a statement prepared by <function>SPI_prepare</function>.
</para>
</refsect1>
@ -1181,7 +1208,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1210,7 +1237,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
<refnamediv>
<refname>SPI_getargtypeid</refname>
<refpurpose>return the data type OID for an argument of
a plan prepared by <function>SPI_prepare</function></refpurpose>
a statement prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_getargtypeid</primary></indexterm>
@ -1226,7 +1253,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI
<para>
<function>SPI_getargtypeid</function> returns the OID representing the type
for the <parameter>argIndex</parameter>'th argument of a plan prepared by
for the <parameter>argIndex</parameter>'th argument of a statement prepared by
<function>SPI_prepare</function>. First argument is at index zero.
</para>
</refsect1>
@ -1239,7 +1266,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1279,7 +1306,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI
<refnamediv>
<refname>SPI_is_cursor_plan</refname>
<refpurpose>return <symbol>true</symbol> if a plan
<refpurpose>return <symbol>true</symbol> if a statement
prepared by <function>SPI_prepare</function> can be used with
<function>SPI_cursor_open</function></refpurpose>
</refnamediv>
@ -1297,7 +1324,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
<para>
<function>SPI_is_cursor_plan</function> returns <symbol>true</symbol>
if a plan prepared by <function>SPI_prepare</function> can be passed
if a statement prepared by <function>SPI_prepare</function> can be passed
as an argument to <function>SPI_cursor_open</function>, or
<symbol>false</symbol> if that is not the case. The criteria are that the
<parameter>plan</parameter> represents one single command and that this
@ -1316,7 +1343,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1348,7 +1375,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
<refnamediv>
<refname>SPI_execute_plan</refname>
<refpurpose>execute a plan prepared by <function>SPI_prepare</function></refpurpose>
<refpurpose>execute a statement prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_execute_plan</primary></indexterm>
@ -1364,8 +1391,9 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<title>Description</title>
<para>
<function>SPI_execute_plan</function> executes a plan prepared by
<function>SPI_prepare</function>. <parameter>read_only</parameter> and
<function>SPI_execute_plan</function> executes a statement prepared by
<function>SPI_prepare</function> or one of its siblings.
<parameter>read_only</parameter> and
<parameter>count</parameter> have the same interpretation as in
<function>SPI_execute</function>.
</para>
@ -1379,7 +1407,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1389,7 +1417,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<listitem>
<para>
An array of actual parameter values. Must have same length as the
plan's number of arguments.
statement's number of arguments.
</para>
</listitem>
</varlistentry>
@ -1399,7 +1427,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<listitem>
<para>
An array describing which parameters are null. Must have same length as
the plan's number of arguments.
the statement's number of arguments.
<literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid).
@ -1479,7 +1507,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<refnamediv>
<refname>SPI_execute_plan_with_paramlist</refname>
<refpurpose>execute a plan prepared by <function>SPI_prepare</function></refpurpose>
<refpurpose>execute a statement prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
@ -1497,7 +1525,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<title>Description</title>
<para>
<function>SPI_execute_plan_with_paramlist</function> executes a plan
<function>SPI_execute_plan_with_paramlist</function> executes a statement
prepared by <function>SPI_prepare</function>.
This function is equivalent to <function>SPI_execute_plan</function>
except that information about the parameter values to be passed to the
@ -1516,7 +1544,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1573,7 +1601,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<refnamediv>
<refname>SPI_execp</refname>
<refpurpose>execute a plan in read/write mode</refpurpose>
<refpurpose>execute a statement in read/write mode</refpurpose>
</refnamediv>
<indexterm><primary>SPI_execp</primary></indexterm>
@ -1603,7 +1631,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1613,7 +1641,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<listitem>
<para>
An array of actual parameter values. Must have same length as the
plan's number of arguments.
statement's number of arguments.
</para>
</listitem>
</varlistentry>
@ -1623,7 +1651,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<listitem>
<para>
An array describing which parameters are null. Must have same length as
the plan's number of arguments.
the statement's number of arguments.
<literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid).
@ -1673,7 +1701,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<refnamediv>
<refname>SPI_cursor_open</refname>
<refpurpose>set up a cursor using a plan created with <function>SPI_prepare</function></refpurpose>
<refpurpose>set up a cursor using a statement created with <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_cursor_open</primary></indexterm>
@ -1691,14 +1719,14 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<para>
<function>SPI_cursor_open</function> sets up a cursor (internally,
a portal) that will execute a plan prepared by
a portal) that will execute a statement prepared by
<function>SPI_prepare</function>. The parameters have the same
meanings as the corresponding parameters to
<function>SPI_execute_plan</function>.
</para>
<para>
Using a cursor instead of executing the plan directly has two
Using a cursor instead of executing the statement directly has two
benefits. First, the result rows can be retrieved a few at a time,
avoiding memory overrun for queries that return many rows. Second,
a portal can outlive the current procedure (it can, in fact, live
@ -1731,7 +1759,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -1741,7 +1769,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<listitem>
<para>
An array of actual parameter values. Must have same length as the
plan's number of arguments.
statement's number of arguments.
</para>
</listitem>
</varlistentry>
@ -1751,7 +1779,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<listitem>
<para>
An array describing which parameters are null. Must have same length as
the plan's number of arguments.
the statement's number of arguments.
<literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid).
@ -1958,7 +1986,7 @@ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
<para>
<function>SPI_cursor_open_with_paramlist</function> sets up a cursor
(internally, a portal) that will execute a plan prepared by
(internally, a portal) that will execute a statement prepared by
<function>SPI_prepare</function>.
This function is equivalent to <function>SPI_cursor_open</function>
except that information about the parameter values to be passed to the
@ -1992,7 +2020,7 @@ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
prepared statement (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
@ -2495,6 +2523,75 @@ void SPI_cursor_close(Portal <parameter>portal</parameter>)
<!-- *********************************************** -->
<refentry id="spi-spi-keepplan">
<refmeta>
<refentrytitle>SPI_keepplan</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_keepplan</refname>
<refpurpose>save a prepared statement</refpurpose>
</refnamediv>
<indexterm><primary>SPI_keepplan</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_keepplan(SPIPlanPtr <parameter>plan</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_keepplan</function> saves a passed statement (prepared by
<function>SPI_prepare</function>) so that it will not be freed
by <function>SPI_finish</function> nor by the transaction manager.
This gives you the ability to reuse prepared statements in the subsequent
invocations of your procedure in the current session.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
the prepared statement to be saved
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
0 on success;
<symbol>SPI_ERROR_ARGUMENT</symbol> if <parameter>plan</parameter>
is <symbol>NULL</symbol> or invalid
</para>
</refsect1>
<refsect1>
<title>Notes</title>
<para>
The passed-in statement is relocated to permanent storage by means
of pointer adjustment (no data copying is required). If you later
wish to delete it, use <function>SPI_freeplan</function> on it.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-saveplan">
<refmeta>
<refentrytitle>SPI_saveplan</refentrytitle>
@ -2503,7 +2600,7 @@ void SPI_cursor_close(Portal <parameter>portal</parameter>)
<refnamediv>
<refname>SPI_saveplan</refname>
<refpurpose>save a plan</refpurpose>
<refpurpose>save a prepared statement</refpurpose>
</refnamediv>
<indexterm><primary>SPI_saveplan</primary></indexterm>
@ -2518,11 +2615,11 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Description</title>
<para>
<function>SPI_saveplan</function> saves a passed plan (prepared by
<function>SPI_prepare</function>) in memory that will not be freed
<function>SPI_saveplan</function> copies a passed statement (prepared by
<function>SPI_prepare</function>) into memory that will not be freed
by <function>SPI_finish</function> nor by the transaction manager,
and returns a pointer to the saved plan. This gives you the
ability to reuse prepared plans in the subsequent invocations of
and returns a pointer to the copied statement. This gives you the
ability to reuse prepared statements in the subsequent invocations of
your procedure in the current session.
</para>
</refsect1>
@ -2535,7 +2632,7 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
the plan to be saved
the prepared statement to be saved
</para>
</listitem>
</varlistentry>
@ -2546,7 +2643,7 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Return Value</title>
<para>
Pointer to the saved plan; <symbol>NULL</symbol> if unsuccessful.
Pointer to the copied statement; or <symbol>NULL</symbol> if unsuccessful.
On error, <varname>SPI_result</varname> is set thus:
<variablelist>
@ -2575,16 +2672,15 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Notes</title>
<para>
The passed-in plan is not freed, so you might wish to do
The originally passed-in statement is not freed, so you might wish to do
<function>SPI_freeplan</function> on it to avoid leaking memory
until <function>SPI_finish</>.
</para>
<para>
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped or redefined, then future executions of
<function>SPI_execute_plan</function> may fail or return different
results than the plan initially indicates.
In most cases, <function>SPI_keepplan</function> is preferred to this
function, since it accomplishes largely the same result without needing
to physically copy the prepared statement's data structures.
</para>
</refsect1>
</refentry>
@ -3809,7 +3905,7 @@ void SPI_freetuptable(SPITupleTable * <parameter>tuptable</parameter>)
<refnamediv>
<refname>SPI_freeplan</refname>
<refpurpose>free a previously saved plan</refpurpose>
<refpurpose>free a previously saved prepared statement</refpurpose>
</refnamediv>
<indexterm><primary>SPI_freeplan</primary></indexterm>
@ -3824,9 +3920,9 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Description</title>
<para>
<function>SPI_freeplan</function> releases a command execution plan
<function>SPI_freeplan</function> releases a prepared statement
previously returned by <function>SPI_prepare</function> or saved by
<function>SPI_saveplan</function>.
<function>SPI_keepplan</function> or <function>SPI_saveplan</function>.
</para>
</refsect1>
@ -3838,7 +3934,7 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
pointer to plan to free
pointer to statement to free
</para>
</listitem>
</varlistentry>
@ -3849,6 +3945,7 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Return Value</title>
<para>
0 on success;
<symbol>SPI_ERROR_ARGUMENT</symbol> if <parameter>plan</parameter>
is <symbol>NULL</symbol> or invalid
</para>

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
*
* 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 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.
*
* 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.
* 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.
*
* 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.
*
* 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 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,
ParserSetupHook parserSetup,
void *parserSetupArg);
extern void DropCachedPlan(CachedPlanSource *plansource);
extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
bool useResOwner);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
extern bool CachedPlanIsValid(CachedPlanSource *plansource);
extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
extern void ResetPlanCache(void);
extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
const char *query_string,
const char *commandTag);
extern 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);
extern void SaveCachedPlan(CachedPlanSource *plansource);
extern void DropCachedPlan(CachedPlanSource *plansource);
extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
MemoryContext newcontext);
extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource);
extern bool CachedPlanIsValid(CachedPlanSource *plansource);
extern List *CachedPlanGetTargetList(CachedPlanSource *plansource);
extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
ParamListInfo boundParams,
bool useResOwner);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
#endif /* PLANCACHE_H */

View File

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

View File

@ -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);
}
@ -3010,6 +3012,11 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
int rc;
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,28 +3032,23 @@ 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;
}
}
}
}
/*
* 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
@ -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 = &paramLI->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);
/*
* 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;
stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
/*
* 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

View File

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

View File

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

View File

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