Commit e6faf910 authored by Tom Lane's avatar Tom Lane

Redesign the plancache mechanism for more flexibility and efficiency.

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

In addition, restructure the API to greatly reduce the amount of extraneous
data copying needed.  The main compromise needed to make that possible was
to split the initial creation of a CachedPlanSource into two steps.  It's
worth noting in particular that SPI_saveplan is now deprecated in favor of
SPI_keepplan, which accomplishes the same end result with zero data
copying, and no need to then spend even more cycles throwing away the
original SPIPlan.  The risk of long-term memory leaks while manipulating
SPIPlans has also been greatly reduced.  Most of this improvement is based
on use of the recently-added MemoryContextSetParent primitive.
parent 09e98a3e
...@@ -190,12 +190,11 @@ check_primary_key(PG_FUNCTION_ARGS) ...@@ -190,12 +190,11 @@ check_primary_key(PG_FUNCTION_ARGS)
/* /*
* Remember that SPI_prepare places plan in current memory context - * 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 (SPI_keepplan(pplan))
if (pplan == NULL)
/* internal error */ /* 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 = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
*(plan->splan) = pplan; *(plan->splan) = pplan;
plan->nplans = 1; plan->nplans = 1;
...@@ -537,13 +536,12 @@ check_foreign_key(PG_FUNCTION_ARGS) ...@@ -537,13 +536,12 @@ check_foreign_key(PG_FUNCTION_ARGS)
/* /*
* Remember that SPI_prepare places plan in current memory context * 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. * use.
*/ */
pplan = SPI_saveplan(pplan); if (SPI_keepplan(pplan))
if (pplan == NULL)
/* internal error */ /* 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; plan->splan[r] = pplan;
......
...@@ -345,11 +345,10 @@ timetravel(PG_FUNCTION_ARGS) ...@@ -345,11 +345,10 @@ timetravel(PG_FUNCTION_ARGS)
/* /*
* Remember that SPI_prepare places plan in current memory context - * 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 (SPI_keepplan(pplan))
if (pplan == NULL) elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname);
elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
plan->splan = pplan; plan->splan = pplan;
} }
......
...@@ -839,12 +839,10 @@ PREPARE <replaceable>statement_name</>(integer, integer) AS SELECT $1 &lt; $2; ...@@ -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 and then this prepared statement is <command>EXECUTE</>d for each
execution of the <command>IF</> statement, with the current values execution of the <command>IF</> statement, with the current values
of the <application>PL/pgSQL</application> variables supplied as of the <application>PL/pgSQL</application> variables supplied as
parameter values. parameter values. Normally these details are
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
not important to a <application>PL/pgSQL</application> user, but not important to a <application>PL/pgSQL</application> user, but
they are useful to know when trying to diagnose a problem. they are useful to know when trying to diagnose a problem.
More information appears in <xref linkend="plpgsql-plan-caching">.
</para> </para>
</sect1> </sect1>
...@@ -919,10 +917,9 @@ my_record.user_id := 20; ...@@ -919,10 +917,9 @@ my_record.user_id := 20;
<para> <para>
When executing a SQL command in this way, When executing a SQL command in this way,
<application>PL/pgSQL</application> plans the command just once <application>PL/pgSQL</application> may cache and re-use the execution
and re-uses the plan on subsequent executions, for the life of plan for the command, as discussed in
the database connection. The implications of this are discussed <xref linkend="plpgsql-plan-caching">.
in detail in <xref linkend="plpgsql-plan-caching">.
</para> </para>
<para> <para>
...@@ -1137,8 +1134,8 @@ EXECUTE <replaceable class="command">command-string</replaceable> <optional> INT ...@@ -1137,8 +1134,8 @@ EXECUTE <replaceable class="command">command-string</replaceable> <optional> INT
<para> <para>
Also, there is no plan caching for commands executed via Also, there is no plan caching for commands executed via
<command>EXECUTE</command>. Instead, the <command>EXECUTE</command>. Instead, the command is always planned
command is prepared each time the statement is run. Thus the command each time the statement is run. Thus the command
string can be dynamically created within the function to perform string can be dynamically created within the function to perform
actions on different tables and columns. actions on different tables and columns.
</para> </para>
...@@ -1206,11 +1203,11 @@ EXECUTE 'SELECT count(*) FROM ' ...@@ -1206,11 +1203,11 @@ EXECUTE 'SELECT count(*) FROM '
The important difference is that <command>EXECUTE</> will re-plan The important difference is that <command>EXECUTE</> will re-plan
the command on each execution, generating a plan that is specific the command on each execution, generating a plan that is specific
to the current parameter values; whereas to the current parameter values; whereas
<application>PL/pgSQL</application> normally creates a generic plan <application>PL/pgSQL</application> may otherwise create a generic plan
and caches it for re-use. In situations where the best plan depends and cache it for re-use. In situations where the best plan depends
strongly on the parameter values, <command>EXECUTE</> can be strongly on the parameter values, it can be helpful to use
significantly faster; while when the plan is not sensitive to parameter <command>EXECUTE</> to positively ensure that a generic plan is not
values, re-planning will be a waste. selected.
</para> </para>
<para> <para>
...@@ -4103,17 +4100,14 @@ $$ LANGUAGE plpgsql; ...@@ -4103,17 +4100,14 @@ $$ LANGUAGE plpgsql;
</indexterm> </indexterm>
As each expression and <acronym>SQL</acronym> command is first As each expression and <acronym>SQL</acronym> command is first
executed in the function, the <application>PL/pgSQL</> interpreter executed in the function, the <application>PL/pgSQL</> interpreter
creates a prepared execution plan (using the parses and analyzes the command to create a prepared statement,
<acronym>SPI</acronym> manager's <function>SPI_prepare</function> using the <acronym>SPI</acronym> manager's
and <function>SPI_saveplan</function> functions). <function>SPI_prepare</function> function.
Subsequent visits to that expression or command Subsequent visits to that expression or command
reuse the prepared plan. Thus, a function with conditional code reuse the prepared statement. Thus, a function with conditional code
that contains many statements for which execution plans might be paths that are seldom visited will never incur the overhead of
required will only prepare and save those plans that are really analyzing those commands that are never executed within the current
used during the lifetime of the database connection. This can session. A disadvantage is that errors
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
in a specific expression or command cannot be detected until that in a specific expression or command cannot be detected until that
part of the function is reached in execution. (Trivial syntax part of the function is reached in execution. (Trivial syntax
errors will be detected during the initial parsing pass, but errors will be detected during the initial parsing pass, but
...@@ -4121,46 +4115,31 @@ $$ LANGUAGE plpgsql; ...@@ -4121,46 +4115,31 @@ $$ LANGUAGE plpgsql;
</para> </para>
<para> <para>
A saved plan will be re-planned automatically if there is any schema <application>PL/pgSQL</> (or more precisely, the SPI manager) can
change to any table used in the query, or if any user-defined function furthermore attempt to cache the execution plan associated with any
used in the query is redefined. This makes the re-use of prepared plans particular prepared statement. If a cached plan is not used, then
transparent in most cases, but there are corner cases where a stale plan a fresh execution plan is generated on each visit to the statement,
might be re-used. An example is that dropping and re-creating a and the current parameter values (that is, <application>PL/pgSQL</>
user-defined operator won't affect already-cached plans; they'll continue variable values) can be used to optimize the selected plan. If the
to call the original operator's underlying function, if that has not been statement has no parameters, or is executed many times, the SPI manager
changed. When necessary, the cache can be flushed by starting a fresh will consider creating a <firstterm>generic</> plan that is not dependent
database session. 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>
<para> <para>
Because <application>PL/pgSQL</application> saves execution plans Because <application>PL/pgSQL</application> saves prepared statements
in this way, SQL commands that appear directly in a and sometimes execution plans in this way,
SQL commands that appear directly in a
<application>PL/pgSQL</application> function must refer to the <application>PL/pgSQL</application> function must refer to the
same tables and columns on every execution; that is, you cannot use 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 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 around this restriction, you can construct dynamic commands using
the <application>PL/pgSQL</application> <command>EXECUTE</command> the <application>PL/pgSQL</application> <command>EXECUTE</command>
statement &mdash; at the price of constructing a new execution plan on statement &mdash; at the price of performing new parse analysis and
every execution. 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.
</para> </para>
<para> <para>
...@@ -4168,14 +4147,14 @@ SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term; ...@@ -4168,14 +4147,14 @@ SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term;
connection. When fields of a record variable are used in connection. When fields of a record variable are used in
expressions or statements, the data types of the fields must not expressions or statements, the data types of the fields must not
change from one call of the function to the next, since each 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 when the expression is first reached. <command>EXECUTE</command> can be
used to get around this problem when necessary. used to get around this problem when necessary.
</para> </para>
<para> <para>
If the same function is used as a trigger for more than one table, 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 independently for each such table &mdash; that is, there is a cache
for each trigger function and table combination, not just for each for each trigger function and table combination, not just for each
function. This alleviates some of the problems with varying function. This alleviates some of the problems with varying
...@@ -4186,14 +4165,14 @@ SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term; ...@@ -4186,14 +4165,14 @@ SELECT * INTO myrec FROM dictionary WHERE word LIKE search_term;
<para> <para>
Likewise, functions having polymorphic argument types have a separate Likewise, functions having polymorphic argument types have a separate
plan cache for each combination of actual argument types they have been statement cache for each combination of actual argument types they have
invoked for, so that data type differences do not cause unexpected been invoked for, so that data type differences do not cause unexpected
failures. failures.
</para> </para>
<para> <para>
Plan caching can sometimes have surprising effects on the interpretation Statement caching can sometimes have surprising effects on the
of time-sensitive values. For example there interpretation of time-sensitive values. For example there
is a difference between what these two functions do: is a difference between what these two functions do:
<programlisting> <programlisting>
...@@ -4221,15 +4200,17 @@ $$ LANGUAGE plpgsql; ...@@ -4221,15 +4200,17 @@ $$ LANGUAGE plpgsql;
<para> <para>
In the case of <function>logfunc1</function>, the In the case of <function>logfunc1</function>, the
<productname>PostgreSQL</productname> main parser knows when <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 string <literal>'now'</literal> should be interpreted as
<type>timestamp</type>, because the target column of <type>timestamp</type>, because the target column of
<classname>logtable</classname> is of that type. Thus, <classname>logtable</classname> is of that type. Thus,
<literal>'now'</literal> will be converted to a constant when the <literal>'now'</literal> will be converted to a <type>timestamp</type>
<command>INSERT</command> is planned, and then used in all constant when the
<command>INSERT</command> is analyzed, and then used in all
invocations of <function>logfunc1</function> during the lifetime invocations of <function>logfunc1</function> during the lifetime
of the session. Needless to say, this isn't what the programmer 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>
<para> <para>
...@@ -4243,7 +4224,9 @@ $$ LANGUAGE plpgsql; ...@@ -4243,7 +4224,9 @@ $$ LANGUAGE plpgsql;
string to the <type>timestamp</type> type by calling the string to the <type>timestamp</type> type by calling the
<function>text_out</function> and <function>timestamp_in</function> <function>text_out</function> and <function>timestamp_in</function>
functions for the conversion. So, the computed time stamp is updated 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> </para>
</sect2> </sect2>
......
...@@ -125,9 +125,8 @@ ...@@ -125,9 +125,8 @@
into multiple steps. The state retained between steps is represented into multiple steps. The state retained between steps is represented
by two types of objects: <firstterm>prepared statements</> and by two types of objects: <firstterm>prepared statements</> and
<firstterm>portals</>. A prepared statement represents the result of <firstterm>portals</>. A prepared statement represents the result of
parsing, semantic analysis, and (optionally) planning of a textual query parsing and semantic analysis of a textual query string.
string. A prepared statement is not in itself ready to execute, because it might
A prepared statement is not necessarily ready to execute, because it might
lack specific values for <firstterm>parameters</>. A portal represents lack specific values for <firstterm>parameters</>. A portal represents
a ready-to-execute or already-partially-executed statement, with any a ready-to-execute or already-partially-executed statement, with any
missing parameter values filled in. (For <command>SELECT</> statements, missing parameter values filled in. (For <command>SELECT</> statements,
...@@ -692,7 +691,7 @@ ...@@ -692,7 +691,7 @@
the unnamed statement as destination is issued. (Note that a simple the unnamed statement as destination is issued. (Note that a simple
Query message also destroys the unnamed statement.) Named prepared Query message also destroys the unnamed statement.) Named prepared
statements must be explicitly closed before they can be redefined by 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 Named prepared statements can also be created and accessed at the SQL
command level, using <command>PREPARE</> and <command>EXECUTE</>. command level, using <command>PREPARE</> and <command>EXECUTE</>.
</para> </para>
...@@ -722,44 +721,23 @@ ...@@ -722,44 +721,23 @@
</note> </note>
<para> <para>
Query planning for named prepared-statement objects occurs when the Parse Query planning typically occurs when the Bind message is processed.
message is processed. If a query will be repeatedly executed with If the prepared statement has no parameters, or is executed repeatedly,
different parameters, it might be beneficial to send a single Parse message the server might save the created plan and re-use it during subsequent
containing a parameterized query, followed by multiple Bind Bind messages for the same prepared statement. However, it will do so
and Execute messages. This will avoid replanning the query on each only if it finds that a generic plan can be created that is not much
execution. 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>
<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> <para>
If successfully created, a named portal object lasts till the end of the If successfully created, a named portal object lasts till the end of the
current transaction, unless explicitly destroyed. An unnamed portal is current transaction, unless explicitly destroyed. An unnamed portal is
destroyed at the end of the transaction, or as soon as the next Bind destroyed at the end of the transaction, or as soon as the next Bind
statement specifying the unnamed portal as destination is issued. (Note statement specifying the unnamed portal as destination is issued. (Note
that a simple Query message also destroys the unnamed portal.) Named that a simple Query message also destroys the unnamed portal.) Named
portals must be explicitly closed before they can be redefined by a Bind portals must be explicitly closed before they can be redefined by another
message, but this is not required for the unnamed portal. Bind message, but this is not required for the unnamed portal.
Named portals can also be created and accessed at the SQL Named portals can also be created and accessed at the SQL
command level, using <command>DECLARE CURSOR</> and <command>FETCH</>. command level, using <command>DECLARE CURSOR</> and <command>FETCH</>.
</para> </para>
...@@ -1280,7 +1258,9 @@ ...@@ -1280,7 +1258,9 @@
The frontend should also be prepared to handle an ErrorMessage The frontend should also be prepared to handle an ErrorMessage
response to SSLRequest from the server. This would only occur if response to SSLRequest from the server. This would only occur if
the server predates the addition of <acronym>SSL</acronym> support 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 be closed, but the frontend might choose to open a fresh connection
and proceed without requesting <acronym>SSL</acronym>. and proceed without requesting <acronym>SSL</acronym>.
</para> </para>
......
...@@ -37,11 +37,11 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class ...@@ -37,11 +37,11 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
<command>PREPARE</command> creates a prepared statement. A prepared <command>PREPARE</command> creates a prepared statement. A prepared
statement is a server-side object that can be used to optimize statement is a server-side object that can be used to optimize
performance. When the <command>PREPARE</command> statement is performance. When the <command>PREPARE</command> statement is
executed, the specified statement is parsed, rewritten, and executed, the specified statement is parsed, analyzed, and rewritten.
planned. When an <command>EXECUTE</command> command is subsequently When an <command>EXECUTE</command> command is subsequently
issued, the prepared statement need only be executed. Thus, the issued, the prepared statement is planned and executed. This division
parsing, rewriting, and planning stages are only performed once, of labor avoids repetitive parse analysis work, while allowing
instead of every time the statement is executed. the execution plan to depend on the specific parameter values supplied.
</para> </para>
<para> <para>
...@@ -65,7 +65,7 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class ...@@ -65,7 +65,7 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
forgotten, so it must be recreated before being used again. This forgotten, so it must be recreated before being used again. This
also means that a single prepared statement cannot be used by also means that a single prepared statement cannot be used by
multiple simultaneous database clients; however, each client can create 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. manually cleaned up using the <xref linkend="sql-deallocate"> command.
</para> </para>
...@@ -127,20 +127,22 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class ...@@ -127,20 +127,22 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
<title>Notes</title> <title>Notes</title>
<para> <para>
In some situations, the query plan produced for a prepared If a prepared statement is executed enough times, the server may eventually
statement will be inferior to the query plan that would have been decide to save and re-use a generic plan rather than re-planning each time.
chosen if the statement had been submitted and executed This will occur immediately if the prepared statement has no parameters;
normally. This is because when the statement is planned and the otherwise it occurs only if the generic plan appears to be not much more
planner attempts to determine the optimal query plan, the actual expensive than a plan that depends on specific parameter values.
values of any parameters specified in the statement are Typically, a generic plan will be selected only if the query's performance
unavailable. <productname>PostgreSQL</productname> collects is estimated to be fairly insensitive to the specific parameter values
statistics on the distribution of data in the table, and can use supplied.
constant values in a statement to make guesses about the likely </para>
result of executing the statement. Since this data is unavailable
when planning prepared statements with parameters, the chosen plan <para>
might be suboptimal. To examine the query plan To examine the query plan <productname>PostgreSQL</productname> is using
<productname>PostgreSQL</productname> has chosen for a prepared for a prepared statement, use <xref linkend="sql-explain">.
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>
<para> <para>
...@@ -151,7 +153,7 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class ...@@ -151,7 +153,7 @@ PREPARE <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class
</para> </para>
<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> <link linkend="view-pg-prepared-statements"><structname>pg_prepared_statements</structname></link>
system view. system view.
</para> </para>
......
...@@ -733,7 +733,8 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>, ...@@ -733,7 +733,8 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>,
<para> <para>
Similar results can be achieved with <function>SPI_prepare</> followed by Similar results can be achieved with <function>SPI_prepare</> followed by
<function>SPI_execute_plan</function>; however, when using this function <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. For one-time query execution, this function should be preferred.
If the same command is to be executed with many different parameters, If the same command is to be executed with many different parameters,
either method might be faster, depending on the cost of re-planning 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>, ...@@ -840,7 +841,7 @@ int SPI_execute_with_args(const char *<parameter>command</parameter>,
<refnamediv> <refnamediv>
<refname>SPI_prepare</refname> <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> </refnamediv>
<indexterm><primary>SPI_prepare</primary></indexterm> <indexterm><primary>SPI_prepare</primary></indexterm>
...@@ -855,17 +856,22 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet ...@@ -855,17 +856,22 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<title>Description</title> <title>Description</title>
<para> <para>
<function>SPI_prepare</function> creates and returns an execution <function>SPI_prepare</function> creates and returns a prepared
plan for the specified command, but doesn't execute the command. statement for the specified command, but doesn't execute the command.
This function should only be called from a connected procedure. The prepared statement can later be executed repeatedly using
<function>SPI_execute_plan</function>.
</para> </para>
<para> <para>
When the same or a similar command is to be executed repeatedly, it When the same or a similar command is to be executed repeatedly, it
might be advantageous to perform the planning only once. is generally advantageous to perform parse analysis only once, and
<function>SPI_prepare</function> converts a command string into an might furthermore be advantageous to re-use an execution plan for the
execution plan that can be executed repeatedly using command.
<function>SPI_execute_plan</function>. <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>
<para> <para>
...@@ -878,11 +884,11 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet ...@@ -878,11 +884,11 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
</para> </para>
<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 only in the current invocation of the procedure, since
<function>SPI_finish</function> frees memory allocated for a plan. <function>SPI_finish</function> frees memory allocated for such a
But a plan can be saved for longer using the function statement. But the statement can be saved for longer using the functions
<function>SPI_saveplan</function>. <function>SPI_keepplan</function> or <function>SPI_saveplan</function>.
</para> </para>
</refsect1> </refsect1>
...@@ -925,7 +931,8 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet ...@@ -925,7 +931,8 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<para> <para>
<function>SPI_prepare</function> returns a non-null pointer to an <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 and <varname>SPI_result</varname> will be set to one of the same
error codes used by <function>SPI_execute</function>, except that error codes used by <function>SPI_execute</function>, except that
it is set to <symbol>SPI_ERROR_ARGUMENT</symbol> if it is set to <symbol>SPI_ERROR_ARGUMENT</symbol> if
...@@ -938,6 +945,26 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet ...@@ -938,6 +945,26 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<refsect1> <refsect1>
<title>Notes</title> <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> <para>
<type>SPIPlanPtr</> is declared as a pointer to an opaque struct type in <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 <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 ...@@ -946,10 +973,8 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
</para> </para>
<para> <para>
There is a disadvantage to using parameters: since the planner does The name <type>SPIPlanPtr</> is somewhat historical, since the data
not know the values that will be supplied for the parameters, it structure no longer necessarily contains an execution plan.
might make worse planning choices than it would make for a normal
command with all constants visible.
</para> </para>
</refsect1> </refsect1>
</refentry> </refentry>
...@@ -964,7 +989,7 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet ...@@ -964,7 +989,7 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<refnamediv> <refnamediv>
<refname>SPI_prepare_cursor</refname> <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> </refnamediv>
<indexterm><primary>SPI_prepare_cursor</primary></indexterm> <indexterm><primary>SPI_prepare_cursor</primary></indexterm>
...@@ -1047,8 +1072,10 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int < ...@@ -1047,8 +1072,10 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
<para> <para>
Useful bits to set in <parameter>cursorOptions</> include Useful bits to set in <parameter>cursorOptions</> include
<symbol>CURSOR_OPT_SCROLL</symbol>, <symbol>CURSOR_OPT_SCROLL</symbol>,
<symbol>CURSOR_OPT_NO_SCROLL</symbol>, and <symbol>CURSOR_OPT_NO_SCROLL</symbol>,
<symbol>CURSOR_OPT_FAST_PLAN</symbol>. Note in particular that <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. <symbol>CURSOR_OPT_HOLD</symbol> is ignored.
</para> </para>
</refsect1> </refsect1>
...@@ -1064,7 +1091,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int < ...@@ -1064,7 +1091,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
<refnamediv> <refnamediv>
<refname>SPI_prepare_params</refname> <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> </refnamediv>
<indexterm><primary>SPI_prepare_params</primary></indexterm> <indexterm><primary>SPI_prepare_params</primary></indexterm>
...@@ -1082,8 +1109,8 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>, ...@@ -1082,8 +1109,8 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
<title>Description</title> <title>Description</title>
<para> <para>
<function>SPI_prepare_params</function> creates and returns an execution <function>SPI_prepare_params</function> creates and returns a prepared
plan for the specified command, but doesn't execute the command. statement for the specified command, but doesn't execute the command.
This function is equivalent to <function>SPI_prepare_cursor</function>, This function is equivalent to <function>SPI_prepare_cursor</function>,
with the addition that the caller can specify parser hook functions with the addition that the caller can specify parser hook functions
to control the parsing of external parameter references. to control the parsing of external parameter references.
...@@ -1152,7 +1179,7 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>, ...@@ -1152,7 +1179,7 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
<refnamediv> <refnamediv>
<refname>SPI_getargcount</refname> <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> prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv> </refnamediv>
...@@ -1169,7 +1196,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>) ...@@ -1169,7 +1196,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
<para> <para>
<function>SPI_getargcount</function> returns the number of arguments needed <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> </para>
</refsect1> </refsect1>
...@@ -1181,7 +1208,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>) ...@@ -1181,7 +1208,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1210,7 +1237,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>) ...@@ -1210,7 +1237,7 @@ int SPI_getargcount(SPIPlanPtr <parameter>plan</parameter>)
<refnamediv> <refnamediv>
<refname>SPI_getargtypeid</refname> <refname>SPI_getargtypeid</refname>
<refpurpose>return the data type OID for an argument of <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> </refnamediv>
<indexterm><primary>SPI_getargtypeid</primary></indexterm> <indexterm><primary>SPI_getargtypeid</primary></indexterm>
...@@ -1226,7 +1253,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI ...@@ -1226,7 +1253,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI
<para> <para>
<function>SPI_getargtypeid</function> returns the OID representing the type <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. <function>SPI_prepare</function>. First argument is at index zero.
</para> </para>
</refsect1> </refsect1>
...@@ -1239,7 +1266,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI ...@@ -1239,7 +1266,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1279,7 +1306,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI ...@@ -1279,7 +1306,7 @@ Oid SPI_getargtypeid(SPIPlanPtr <parameter>plan</parameter>, int <parameter>argI
<refnamediv> <refnamediv>
<refname>SPI_is_cursor_plan</refname> <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 prepared by <function>SPI_prepare</function> can be used with
<function>SPI_cursor_open</function></refpurpose> <function>SPI_cursor_open</function></refpurpose>
</refnamediv> </refnamediv>
...@@ -1297,7 +1324,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -1297,7 +1324,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
<para> <para>
<function>SPI_is_cursor_plan</function> returns <symbol>true</symbol> <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 as an argument to <function>SPI_cursor_open</function>, or
<symbol>false</symbol> if that is not the case. The criteria are that the <symbol>false</symbol> if that is not the case. The criteria are that the
<parameter>plan</parameter> represents one single command and that this <parameter>plan</parameter> represents one single command and that this
...@@ -1316,7 +1343,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -1316,7 +1343,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1348,7 +1375,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -1348,7 +1375,7 @@ bool SPI_is_cursor_plan(SPIPlanPtr <parameter>plan</parameter>)
<refnamediv> <refnamediv>
<refname>SPI_execute_plan</refname> <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> </refnamediv>
<indexterm><primary>SPI_execute_plan</primary></indexterm> <indexterm><primary>SPI_execute_plan</primary></indexterm>
...@@ -1364,8 +1391,9 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter> ...@@ -1364,8 +1391,9 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<title>Description</title> <title>Description</title>
<para> <para>
<function>SPI_execute_plan</function> executes a plan prepared by <function>SPI_execute_plan</function> executes a statement prepared by
<function>SPI_prepare</function>. <parameter>read_only</parameter> and <function>SPI_prepare</function> or one of its siblings.
<parameter>read_only</parameter> and
<parameter>count</parameter> have the same interpretation as in <parameter>count</parameter> have the same interpretation as in
<function>SPI_execute</function>. <function>SPI_execute</function>.
</para> </para>
...@@ -1379,7 +1407,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter> ...@@ -1379,7 +1407,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1389,7 +1417,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter> ...@@ -1389,7 +1417,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<listitem> <listitem>
<para> <para>
An array of actual parameter values. Must have same length as the An array of actual parameter values. Must have same length as the
plan's number of arguments. statement's number of arguments.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1399,7 +1427,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter> ...@@ -1399,7 +1427,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<listitem> <listitem>
<para> <para>
An array describing which parameters are null. Must have same length as 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 <literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a <parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid). nonnull value (entry in <parameter>values</> is valid).
...@@ -1479,7 +1507,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter> ...@@ -1479,7 +1507,7 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<refnamediv> <refnamediv>
<refname>SPI_execute_plan_with_paramlist</refname> <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> </refnamediv>
<indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm> <indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
...@@ -1497,7 +1525,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>, ...@@ -1497,7 +1525,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<title>Description</title> <title>Description</title>
<para> <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>. prepared by <function>SPI_prepare</function>.
This function is equivalent to <function>SPI_execute_plan</function> This function is equivalent to <function>SPI_execute_plan</function>
except that information about the parameter values to be passed to the 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>, ...@@ -1516,7 +1544,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1573,7 +1601,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>, ...@@ -1573,7 +1601,7 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
<refnamediv> <refnamediv>
<refname>SPI_execp</refname> <refname>SPI_execp</refname>
<refpurpose>execute a plan in read/write mode</refpurpose> <refpurpose>execute a statement in read/write mode</refpurpose>
</refnamediv> </refnamediv>
<indexterm><primary>SPI_execp</primary></indexterm> <indexterm><primary>SPI_execp</primary></indexterm>
...@@ -1603,7 +1631,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values< ...@@ -1603,7 +1631,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1613,7 +1641,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values< ...@@ -1613,7 +1641,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<listitem> <listitem>
<para> <para>
An array of actual parameter values. Must have same length as the An array of actual parameter values. Must have same length as the
plan's number of arguments. statement's number of arguments.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1623,7 +1651,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values< ...@@ -1623,7 +1651,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<listitem> <listitem>
<para> <para>
An array describing which parameters are null. Must have same length as 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 <literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a <parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid). nonnull value (entry in <parameter>values</> is valid).
...@@ -1673,7 +1701,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values< ...@@ -1673,7 +1701,7 @@ int SPI_execp(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>values<
<refnamediv> <refnamediv>
<refname>SPI_cursor_open</refname> <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> </refnamediv>
<indexterm><primary>SPI_cursor_open</primary></indexterm> <indexterm><primary>SPI_cursor_open</primary></indexterm>
...@@ -1691,14 +1719,14 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par ...@@ -1691,14 +1719,14 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<para> <para>
<function>SPI_cursor_open</function> sets up a cursor (internally, <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 <function>SPI_prepare</function>. The parameters have the same
meanings as the corresponding parameters to meanings as the corresponding parameters to
<function>SPI_execute_plan</function>. <function>SPI_execute_plan</function>.
</para> </para>
<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, benefits. First, the result rows can be retrieved a few at a time,
avoiding memory overrun for queries that return many rows. Second, avoiding memory overrun for queries that return many rows. Second,
a portal can outlive the current procedure (it can, in fact, live 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 ...@@ -1731,7 +1759,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1741,7 +1769,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par ...@@ -1741,7 +1769,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<listitem> <listitem>
<para> <para>
An array of actual parameter values. Must have same length as the An array of actual parameter values. Must have same length as the
plan's number of arguments. statement's number of arguments.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1751,7 +1779,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par ...@@ -1751,7 +1779,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
<listitem> <listitem>
<para> <para>
An array describing which parameters are null. Must have same length as 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 <literal>n</literal> indicates a null value (entry in
<parameter>values</> will be ignored); a space indicates a <parameter>values</> will be ignored); a space indicates a
nonnull value (entry in <parameter>values</> is valid). nonnull value (entry in <parameter>values</> is valid).
...@@ -1958,7 +1986,7 @@ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>, ...@@ -1958,7 +1986,7 @@ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
<para> <para>
<function>SPI_cursor_open_with_paramlist</function> sets up a cursor <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>. <function>SPI_prepare</function>.
This function is equivalent to <function>SPI_cursor_open</function> This function is equivalent to <function>SPI_cursor_open</function>
except that information about the parameter values to be passed to the 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>, ...@@ -1992,7 +2020,7 @@ Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
execution plan (returned by <function>SPI_prepare</function>) prepared statement (returned by <function>SPI_prepare</function>)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -2495,6 +2523,75 @@ void SPI_cursor_close(Portal <parameter>portal</parameter>) ...@@ -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"> <refentry id="spi-spi-saveplan">
<refmeta> <refmeta>
<refentrytitle>SPI_saveplan</refentrytitle> <refentrytitle>SPI_saveplan</refentrytitle>
...@@ -2503,7 +2600,7 @@ void SPI_cursor_close(Portal <parameter>portal</parameter>) ...@@ -2503,7 +2600,7 @@ void SPI_cursor_close(Portal <parameter>portal</parameter>)
<refnamediv> <refnamediv>
<refname>SPI_saveplan</refname> <refname>SPI_saveplan</refname>
<refpurpose>save a plan</refpurpose> <refpurpose>save a prepared statement</refpurpose>
</refnamediv> </refnamediv>
<indexterm><primary>SPI_saveplan</primary></indexterm> <indexterm><primary>SPI_saveplan</primary></indexterm>
...@@ -2518,11 +2615,11 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -2518,11 +2615,11 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Description</title> <title>Description</title>
<para> <para>
<function>SPI_saveplan</function> saves a passed plan (prepared by <function>SPI_saveplan</function> copies a passed statement (prepared by
<function>SPI_prepare</function>) in memory that will not be freed <function>SPI_prepare</function>) into memory that will not be freed
by <function>SPI_finish</function> nor by the transaction manager, by <function>SPI_finish</function> nor by the transaction manager,
and returns a pointer to the saved plan. This gives you the and returns a pointer to the copied statement. This gives you the
ability to reuse prepared plans in the subsequent invocations of ability to reuse prepared statements in the subsequent invocations of
your procedure in the current session. your procedure in the current session.
</para> </para>
</refsect1> </refsect1>
...@@ -2535,7 +2632,7 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -2535,7 +2632,7 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
the plan to be saved the prepared statement to be saved
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -2546,7 +2643,7 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -2546,7 +2643,7 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Return Value</title> <title>Return Value</title>
<para> <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: On error, <varname>SPI_result</varname> is set thus:
<variablelist> <variablelist>
...@@ -2575,16 +2672,15 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -2575,16 +2672,15 @@ SPIPlanPtr SPI_saveplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Notes</title> <title>Notes</title>
<para> <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 <function>SPI_freeplan</function> on it to avoid leaking memory
until <function>SPI_finish</>. until <function>SPI_finish</>.
</para> </para>
<para> <para>
If one of the objects (a table, function, etc.) referenced by the In most cases, <function>SPI_keepplan</function> is preferred to this
prepared plan is dropped or redefined, then future executions of function, since it accomplishes largely the same result without needing
<function>SPI_execute_plan</function> may fail or return different to physically copy the prepared statement's data structures.
results than the plan initially indicates.
</para> </para>
</refsect1> </refsect1>
</refentry> </refentry>
...@@ -3809,7 +3905,7 @@ void SPI_freetuptable(SPITupleTable * <parameter>tuptable</parameter>) ...@@ -3809,7 +3905,7 @@ void SPI_freetuptable(SPITupleTable * <parameter>tuptable</parameter>)
<refnamediv> <refnamediv>
<refname>SPI_freeplan</refname> <refname>SPI_freeplan</refname>
<refpurpose>free a previously saved plan</refpurpose> <refpurpose>free a previously saved prepared statement</refpurpose>
</refnamediv> </refnamediv>
<indexterm><primary>SPI_freeplan</primary></indexterm> <indexterm><primary>SPI_freeplan</primary></indexterm>
...@@ -3824,9 +3920,9 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -3824,9 +3920,9 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Description</title> <title>Description</title>
<para> <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 previously returned by <function>SPI_prepare</function> or saved by
<function>SPI_saveplan</function>. <function>SPI_keepplan</function> or <function>SPI_saveplan</function>.
</para> </para>
</refsect1> </refsect1>
...@@ -3838,7 +3934,7 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -3838,7 +3934,7 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term> <term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem> <listitem>
<para> <para>
pointer to plan to free pointer to statement to free
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -3849,6 +3945,7 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>) ...@@ -3849,6 +3945,7 @@ int SPI_freeplan(SPIPlanPtr <parameter>plan</parameter>)
<title>Return Value</title> <title>Return Value</title>
<para> <para>
0 on success;
<symbol>SPI_ERROR_ARGUMENT</symbol> if <parameter>plan</parameter> <symbol>SPI_ERROR_ARGUMENT</symbol> if <parameter>plan</parameter>
is <symbol>NULL</symbol> or invalid is <symbol>NULL</symbol> or invalid
</para> </para>
......
...@@ -2940,6 +2940,24 @@ GetOverrideSearchPath(MemoryContext context) ...@@ -2940,6 +2940,24 @@ GetOverrideSearchPath(MemoryContext context)
return result; 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 * PushOverrideSearchPath - temporarily override the search path
* *
......
...@@ -53,11 +53,11 @@ static Datum build_regtype_array(Oid *param_types, int num_params); ...@@ -53,11 +53,11 @@ static Datum build_regtype_array(Oid *param_types, int num_params);
void void
PrepareQuery(PrepareStmt *stmt, const char *queryString) PrepareQuery(PrepareStmt *stmt, const char *queryString)
{ {
CachedPlanSource *plansource;
Oid *argtypes = NULL; Oid *argtypes = NULL;
int nargs; int nargs;
Query *query; Query *query;
List *query_list, List *query_list;
*plan_list;
int i; int i;
/* /*
...@@ -69,6 +69,13 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -69,6 +69,13 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
errmsg("invalid statement name: must not be empty"))); 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 */ /* Transform list of TypeNames to array of type OIDs */
nargs = list_length(stmt->argtypes); nargs = list_length(stmt->argtypes);
...@@ -102,7 +109,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -102,7 +109,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* information about unknown parameters to be deduced from context. * information about unknown parameters to be deduced from context.
* *
* Because parse analysis scribbles on the raw querytree, we must make a * 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), query = parse_analyze_varparams((Node *) copyObject(stmt->query),
queryString, queryString,
...@@ -143,20 +150,22 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -143,20 +150,22 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
/* Rewrite the query. The result could be 0, 1, or many queries. */ /* Rewrite the query. The result could be 0, 1, or many queries. */
query_list = QueryRewrite(query); query_list = QueryRewrite(query);
/* Generate plans for queries. */ /* Finish filling in the CachedPlanSource */
plan_list = pg_plan_queries(query_list, 0, NULL); CompleteCachedPlan(plansource,
query_list,
NULL,
argtypes,
nargs,
NULL,
NULL,
0, /* default cursor options */
true); /* fixed result */
/* /*
* Save the results. * Save the results.
*/ */
StorePreparedStatement(stmt->name, StorePreparedStatement(stmt->name,
stmt->query, plansource,
queryString,
CreateCommandTag((Node *) query),
argtypes,
nargs,
0, /* default cursor options */
plan_list,
true); true);
} }
...@@ -185,10 +194,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -185,10 +194,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
/* Look it up in the hash table */ /* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true); entry = FetchPreparedStatement(stmt->name, true);
/* Shouldn't have a non-fully-planned plancache entry */ /* Shouldn't find a non-fixed-result cached plan */
if (!entry->plansource->fully_planned)
elog(ERROR, "EXECUTE does not support unplanned prepared statements");
/* Shouldn't get any non-fixed-result cached plan, either */
if (!entry->plansource->fixed_result) if (!entry->plansource->fixed_result)
elog(ERROR, "EXECUTE does not support variable-result cached plans"); elog(ERROR, "EXECUTE does not support variable-result cached plans");
...@@ -197,7 +203,9 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -197,7 +203,9 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
{ {
/* /*
* Need an EState to evaluate parameters; must not delete it till end * 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 = CreateExecutorState();
estate->es_param_list_info = params; estate->es_param_list_info = params;
...@@ -226,7 +234,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -226,7 +234,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
PlannedStmt *pstmt; PlannedStmt *pstmt;
/* Replan if needed, and increment plan refcount transiently */ /* 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 */ /* Copy plan into portal's context, and modify */
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
...@@ -256,7 +264,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -256,7 +264,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
else else
{ {
/* Replan if needed, and increment plan refcount for portal */ /* 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; plan_list = cplan->stmt_list;
} }
...@@ -396,7 +404,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params, ...@@ -396,7 +404,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
ParamExternData *prm = &paramLI->params[i]; ParamExternData *prm = &paramLI->params[i];
prm->ptype = param_types[i]; prm->ptype = param_types[i];
prm->pflags = 0; prm->pflags = PARAM_FLAG_CONST;
prm->value = ExecEvalExprSwitchContext(n, prm->value = ExecEvalExprSwitchContext(n,
GetPerTupleExprContext(estate), GetPerTupleExprContext(estate),
&prm->isnull, &prm->isnull,
...@@ -430,54 +438,24 @@ InitQueryHashTable(void) ...@@ -430,54 +438,24 @@ InitQueryHashTable(void)
/* /*
* Store all the data pertaining to a query in the hash table using * 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 * the specified key. The passed CachedPlanSource should be "unsaved"
* entry or the underlying plancache entry, so the caller can dispose of its * in case we get an error here; we'll save it once we've created the hash
* copy. * table entry.
*
* 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.
*/ */
void void
StorePreparedStatement(const char *stmt_name, StorePreparedStatement(const char *stmt_name,
Node *raw_parse_tree, CachedPlanSource *plansource,
const char *query_string,
const char *commandTag,
Oid *param_types,
int num_params,
int cursor_options,
List *stmt_list,
bool from_sql) bool from_sql)
{ {
PreparedStatement *entry; PreparedStatement *entry;
CachedPlanSource *plansource; TimestampTz cur_ts = GetCurrentStatementStartTimestamp();
bool found; bool found;
/* Initialize the hash table, if necessary */ /* Initialize the hash table, if necessary */
if (!prepared_queries) if (!prepared_queries)
InitQueryHashTable(); InitQueryHashTable();
/* Check for pre-existing entry of same name */ /* Add entry to hash table */
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 */
entry = (PreparedStatement *) hash_search(prepared_queries, entry = (PreparedStatement *) hash_search(prepared_queries,
stmt_name, stmt_name,
HASH_ENTER, HASH_ENTER,
...@@ -485,13 +463,18 @@ StorePreparedStatement(const char *stmt_name, ...@@ -485,13 +463,18 @@ StorePreparedStatement(const char *stmt_name,
/* Shouldn't get a duplicate entry */ /* Shouldn't get a duplicate entry */
if (found) if (found)
elog(ERROR, "duplicate prepared statement \"%s\"", ereport(ERROR,
stmt_name); (errcode(ERRCODE_DUPLICATE_PSTATEMENT),
errmsg("prepared statement \"%s\" already exists",
stmt_name)));
/* Fill in the hash table entry */ /* Fill in the hash table entry */
entry->plansource = plansource; entry->plansource = plansource;
entry->from_sql = from_sql; 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) ...@@ -538,7 +521,7 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt)
{ {
/* /*
* Since we don't allow prepared statements' result tupdescs to change, * 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); Assert(stmt->plansource->fixed_result);
if (stmt->plansource->resultDesc) if (stmt->plansource->resultDesc)
...@@ -560,24 +543,12 @@ List * ...@@ -560,24 +543,12 @@ List *
FetchPreparedStatementTargetList(PreparedStatement *stmt) FetchPreparedStatementTargetList(PreparedStatement *stmt)
{ {
List *tlist; List *tlist;
CachedPlan *cplan;
/* No point in looking if it doesn't return tuples */
if (stmt->plansource->resultDesc == NULL)
return NIL;
/* Make sure the plan is up to date */ /* Get the plan's primary targetlist */
cplan = RevalidateCachedPlan(stmt->plansource, true); tlist = CachedPlanGetTargetList(stmt->plansource);
/* 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, ...@@ -662,26 +633,20 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
/* Look it up in the hash table */ /* Look it up in the hash table */
entry = FetchPreparedStatement(execstmt->name, true); entry = FetchPreparedStatement(execstmt->name, true);
/* Shouldn't have a non-fully-planned plancache entry */ /* Shouldn't find a non-fixed-result cached plan */
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 */
if (!entry->plansource->fixed_result) if (!entry->plansource->fixed_result)
elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans"); elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans");
query_string = entry->plansource->query_string; 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 */ /* Evaluate parameters, if any */
if (entry->plansource->num_params) if (entry->plansource->num_params)
{ {
/* /*
* Need an EState to evaluate parameters; must not delete it till end * 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 = CreateExecutorState();
estate->es_param_list_info = params; estate->es_param_list_info = params;
...@@ -689,6 +654,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, ...@@ -689,6 +654,11 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
queryString, estate); queryString, estate);
} }
/* Replan if needed, and acquire a transient refcount */
cplan = GetCachedPlan(entry->plansource, paramLI, true);
plan_list = cplan->stmt_list;
/* Explain each query */ /* Explain each query */
foreach(p, plan_list) foreach(p, plan_list)
{ {
...@@ -714,7 +684,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, ...@@ -714,7 +684,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
} }
else else
{ {
ExplainOneUtility((Node *) pstmt, es, query_string, params); ExplainOneUtility((Node *) pstmt, es, query_string, paramLI);
} }
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */ /* No need for CommandCounterIncrement, as ExplainOnePlan did it */
......
...@@ -56,8 +56,7 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -56,8 +56,7 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
bool read_only, bool fire_triggers, long tcount); bool read_only, bool fire_triggers, long tcount);
static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes,
Datum *Values, const char *Nulls, Datum *Values, const char *Nulls);
int pflags);
static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount); static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount);
...@@ -67,7 +66,7 @@ static void _SPI_cursor_operation(Portal portal, ...@@ -67,7 +66,7 @@ static void _SPI_cursor_operation(Portal portal,
FetchDirection direction, long count, FetchDirection direction, long count,
DestReceiver *dest); 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 SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan);
static int _SPI_begin_call(bool execmem); static int _SPI_begin_call(bool execmem);
...@@ -391,8 +390,7 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, ...@@ -391,8 +390,7 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
res = _SPI_execute_plan(plan, res = _SPI_execute_plan(plan,
_SPI_convert_params(plan->nargs, plan->argtypes, _SPI_convert_params(plan->nargs, plan->argtypes,
Values, Nulls, Values, Nulls),
0),
InvalidSnapshot, InvalidSnapshot, InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount); read_only, true, tcount);
...@@ -462,8 +460,7 @@ SPI_execute_snapshot(SPIPlanPtr plan, ...@@ -462,8 +460,7 @@ SPI_execute_snapshot(SPIPlanPtr plan,
res = _SPI_execute_plan(plan, res = _SPI_execute_plan(plan,
_SPI_convert_params(plan->nargs, plan->argtypes, _SPI_convert_params(plan->nargs, plan->argtypes,
Values, Nulls, Values, Nulls),
0),
snapshot, crosscheck_snapshot, snapshot, crosscheck_snapshot,
read_only, fire_triggers, tcount); read_only, fire_triggers, tcount);
...@@ -474,11 +471,8 @@ SPI_execute_snapshot(SPIPlanPtr plan, ...@@ -474,11 +471,8 @@ SPI_execute_snapshot(SPIPlanPtr plan,
/* /*
* SPI_execute_with_args -- plan and execute a query with supplied arguments * SPI_execute_with_args -- plan and execute a query with supplied arguments
* *
* This is functionally comparable to SPI_prepare followed by * This is functionally equivalent to SPI_prepare followed by
* SPI_execute_plan, except that since we know the plan will be used only * SPI_execute_plan.
* 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.
*/ */
int int
SPI_execute_with_args(const char *src, SPI_execute_with_args(const char *src,
...@@ -509,13 +503,10 @@ SPI_execute_with_args(const char *src, ...@@ -509,13 +503,10 @@ SPI_execute_with_args(const char *src,
plan.parserSetupArg = NULL; plan.parserSetupArg = NULL;
paramLI = _SPI_convert_params(nargs, argtypes, paramLI = _SPI_convert_params(nargs, argtypes,
Values, Nulls, Values, Nulls);
PARAM_FLAG_CONST);
_SPI_prepare_plan(src, &plan, paramLI); _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, res = _SPI_execute_plan(&plan, paramLI,
InvalidSnapshot, InvalidSnapshot, InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount); read_only, true, tcount);
...@@ -558,7 +549,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, ...@@ -558,7 +549,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
_SPI_prepare_plan(src, &plan, NULL); _SPI_prepare_plan(src, &plan, NULL);
/* copy plan to procedure context */ /* copy plan to procedure context */
result = _SPI_copy_plan(&plan, _SPI_current->procCxt); result = _SPI_make_plan_non_temp(&plan);
_SPI_end_call(true); _SPI_end_call(true);
...@@ -595,20 +586,45 @@ SPI_prepare_params(const char *src, ...@@ -595,20 +586,45 @@ SPI_prepare_params(const char *src,
_SPI_prepare_plan(src, &plan, NULL); _SPI_prepare_plan(src, &plan, NULL);
/* copy plan to procedure context */ /* copy plan to procedure context */
result = _SPI_copy_plan(&plan, _SPI_current->procCxt); result = _SPI_make_plan_non_temp(&plan);
_SPI_end_call(true); _SPI_end_call(true);
return result; 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 SPIPlanPtr
SPI_saveplan(SPIPlanPtr plan) SPI_saveplan(SPIPlanPtr plan)
{ {
SPIPlanPtr newplan; SPIPlanPtr newplan;
/* We don't currently support copying an already-saved plan */ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved)
{ {
SPI_result = SPI_ERROR_ARGUMENT; SPI_result = SPI_ERROR_ARGUMENT;
return NULL; return NULL;
...@@ -620,8 +636,7 @@ SPI_saveplan(SPIPlanPtr plan) ...@@ -620,8 +636,7 @@ SPI_saveplan(SPIPlanPtr plan)
newplan = _SPI_save_plan(plan); newplan = _SPI_save_plan(plan);
_SPI_curid--; SPI_result = _SPI_end_call(false);
SPI_result = 0;
return newplan; return newplan;
} }
...@@ -629,20 +644,17 @@ SPI_saveplan(SPIPlanPtr plan) ...@@ -629,20 +644,17 @@ SPI_saveplan(SPIPlanPtr plan)
int int
SPI_freeplan(SPIPlanPtr plan) SPI_freeplan(SPIPlanPtr plan)
{ {
ListCell *lc;
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
return SPI_ERROR_ARGUMENT; return SPI_ERROR_ARGUMENT;
/* If plancache.c owns the plancache entries, we must release them */ /* Release the plancache entries */
if (plan->saved) 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 */ /* 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, ...@@ -1020,8 +1032,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan,
/* build transient ParamListInfo in caller's context */ /* build transient ParamListInfo in caller's context */
paramLI = _SPI_convert_params(plan->nargs, plan->argtypes, paramLI = _SPI_convert_params(plan->nargs, plan->argtypes,
Values, Nulls, Values, Nulls);
0);
portal = SPI_cursor_open_internal(name, plan, paramLI, read_only); portal = SPI_cursor_open_internal(name, plan, paramLI, read_only);
...@@ -1036,9 +1047,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan, ...@@ -1036,9 +1047,7 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan,
/* /*
* SPI_cursor_open_with_args() * SPI_cursor_open_with_args()
* *
* Parse and plan a query and open it as a portal. Like SPI_execute_with_args, * Parse and plan a query and open it as a portal.
* we can tell the planner to rely on the parameter values as constants,
* because the plan will only be used once.
*/ */
Portal Portal
SPI_cursor_open_with_args(const char *name, SPI_cursor_open_with_args(const char *name,
...@@ -1071,8 +1080,7 @@ 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 */ /* build transient ParamListInfo in executor context */
paramLI = _SPI_convert_params(nargs, argtypes, paramLI = _SPI_convert_params(nargs, argtypes,
Values, Nulls, Values, Nulls);
PARAM_FLAG_CONST);
_SPI_prepare_plan(src, &plan, paramLI); _SPI_prepare_plan(src, &plan, paramLI);
...@@ -1081,9 +1089,6 @@ SPI_cursor_open_with_args(const char *name, ...@@ -1081,9 +1089,6 @@ SPI_cursor_open_with_args(const char *name,
/* Adjust stack so that SPI_cursor_open_internal doesn't complain */ /* Adjust stack so that SPI_cursor_open_internal doesn't complain */
_SPI_curid--; _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); result = SPI_cursor_open_internal(name, &plan, paramLI, read_only);
/* And clean up */ /* And clean up */
...@@ -1148,7 +1153,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1148,7 +1153,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
plansource = (CachedPlanSource *) linitial(plan->plancache_list); plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Push the SPI stack */ /* 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"); elog(ERROR, "SPI_cursor_open called while not connected");
/* Reset SPI result (note we deliberately don't touch lastoid) */ /* Reset SPI result (note we deliberately don't touch lastoid) */
...@@ -1174,22 +1179,27 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1174,22 +1179,27 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
plansource->query_string); plansource->query_string);
/* /*
* Note: we mustn't have any failure occur between RevalidateCachedPlan * Note: for a saved plan, we mustn't have any failure occur between
* and PortalDefineQuery; that would result in leaking our plancache * GetCachedPlan and PortalDefineQuery; that would result in leaking our
* refcount. * plancache refcount.
*/ */
if (plan->saved)
{ /* Replan if needed, and increment plan refcount for portal */
/* Replan if needed, and increment plan refcount for portal */ cplan = GetCachedPlan(plansource, paramLI, false);
cplan = RevalidateCachedPlan(plansource, false); stmt_list = cplan->stmt_list;
stmt_list = cplan->stmt_list;
} if (!plan->saved)
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)); oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
stmt_list = copyObject(plansource->plan->stmt_list); stmt_list = copyObject(stmt_list);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
ReleaseCachedPlan(cplan, false);
cplan = NULL; /* portal shouldn't depend on cplan */ cplan = NULL; /* portal shouldn't depend on cplan */
} }
...@@ -1238,9 +1248,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -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 * 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 * 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 * queries. (In particular, we don't want to do it between GetCachedPlan
* RevalidateCachedPlan and PortalDefineQuery, because throwing an error * and PortalDefineQuery, because throwing an error between those steps
* between those steps would result in leaking our plancache refcount.) * would result in leaking our plancache refcount.)
*/ */
if (read_only) if (read_only)
{ {
...@@ -1288,7 +1298,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1288,7 +1298,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Assert(portal->strategy != PORTAL_MULTI_QUERY); Assert(portal->strategy != PORTAL_MULTI_QUERY);
/* Pop the SPI stack */ /* Pop the SPI stack */
_SPI_end_call(false); _SPI_end_call(true);
/* Return the created portal */ /* Return the created portal */
return portal; return portal;
...@@ -1420,7 +1430,6 @@ bool ...@@ -1420,7 +1430,6 @@ bool
SPI_is_cursor_plan(SPIPlanPtr plan) SPI_is_cursor_plan(SPIPlanPtr plan)
{ {
CachedPlanSource *plansource; CachedPlanSource *plansource;
CachedPlan *cplan;
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
{ {
...@@ -1435,19 +1444,11 @@ SPI_is_cursor_plan(SPIPlanPtr plan) ...@@ -1435,19 +1444,11 @@ SPI_is_cursor_plan(SPIPlanPtr plan)
} }
plansource = (CachedPlanSource *) linitial(plan->plancache_list); plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Need _SPI_begin_call in case replanning invokes SPI-using functions */ /*
SPI_result = _SPI_begin_call(false); * We used to force revalidation of the cached plan here, but that seems
if (SPI_result < 0) * unnecessary: invalidation could mean a change in the rowtype of the
return false; * tuples returned by a plan, but not whether it returns tuples at all.
*/
if (plan->saved)
{
/* Make sure the plan is up to date */
cplan = RevalidateCachedPlan(plansource, true);
ReleaseCachedPlan(cplan, true);
}
_SPI_end_call(false);
SPI_result = 0; SPI_result = 0;
/* Does it return tuples? */ /* Does it return tuples? */
...@@ -1466,25 +1467,18 @@ SPI_is_cursor_plan(SPIPlanPtr plan) ...@@ -1466,25 +1467,18 @@ SPI_is_cursor_plan(SPIPlanPtr plan)
bool bool
SPI_plan_is_valid(SPIPlanPtr plan) SPI_plan_is_valid(SPIPlanPtr plan)
{ {
Assert(plan->magic == _SPI_PLAN_MAGIC); ListCell *lc;
if (plan->saved)
{
ListCell *lc;
foreach(lc, plan->plancache_list) Assert(plan->magic == _SPI_PLAN_MAGIC);
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
if (!CachedPlanIsValid(plansource)) foreach(lc, plan->plancache_list)
return false;
}
return true;
}
else
{ {
/* An unsaved plan is assumed valid for its (short) lifetime */ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
return true;
if (!CachedPlanIsValid(plansource))
return false;
} }
return true;
} }
/* /*
...@@ -1646,7 +1640,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) ...@@ -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 * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
* and plan->parserSetupArg) must be valid, as must plan->cursor_options. * and plan->parserSetupArg) must be valid, as must plan->cursor_options.
...@@ -1656,8 +1650,10 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) ...@@ -1656,8 +1650,10 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
* param type information embedded in the plan! * param type information embedded in the plan!
* *
* Results are stored into *plan (specifically, plan->plancache_list). * Results are stored into *plan (specifically, plan->plancache_list).
* Note however that the result trees are all in CurrentMemoryContext * Note that the result data is all in CurrentMemoryContext or child contexts
* and need to be copied somewhere to survive. * 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 static void
_SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
...@@ -1682,8 +1678,8 @@ _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); raw_parsetree_list = pg_parse_query(src);
/* /*
* Do parse analysis, rule rewrite, and planning for each raw parsetree, * Do parse analysis and rule rewrite for each raw parsetree, storing
* then cons up a phony plancache entry for each one. * the results into unsaved plancache entries.
*/ */
plancache_list = NIL; plancache_list = NIL;
...@@ -1692,7 +1688,14 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) ...@@ -1692,7 +1688,14 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
Node *parsetree = (Node *) lfirst(list_item); Node *parsetree = (Node *) lfirst(list_item);
List *stmt_list; List *stmt_list;
CachedPlanSource *plansource; 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, * Parameter datatypes are driven by parserSetup hook if provided,
...@@ -1701,41 +1704,29 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) ...@@ -1701,41 +1704,29 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
if (plan->parserSetup != NULL) if (plan->parserSetup != NULL)
{ {
Assert(plan->nargs == 0); Assert(plan->nargs == 0);
/* Need a copyObject here to keep parser from modifying raw tree */ stmt_list = pg_analyze_and_rewrite_params(parsetree,
stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree),
src, src,
plan->parserSetup, plan->parserSetup,
plan->parserSetupArg); plan->parserSetupArg);
} }
else else
{ {
/* Need a copyObject here to keep parser from modifying raw tree */ stmt_list = pg_analyze_and_rewrite(parsetree,
stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
src, src,
plan->argtypes, plan->argtypes,
plan->nargs); plan->nargs);
} }
stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams);
/* Finish filling in the CachedPlanSource */
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); CompleteCachedPlan(plansource,
cplan = (CachedPlan *) palloc0(sizeof(CachedPlan)); stmt_list,
NULL,
plansource->raw_parse_tree = parsetree; plan->argtypes,
/* cast-away-const here is a bit ugly, but there's no reason to copy */ plan->nargs,
plansource->query_string = (char *) src; plan->parserSetup,
plansource->commandTag = CreateCommandTag(parsetree); plan->parserSetupArg,
plansource->param_types = plan->argtypes; cursor_options,
plansource->num_params = plan->nargs; false); /* not fixed result */
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;
plancache_list = lappend(plancache_list, plansource); plancache_list = lappend(plancache_list, plansource);
} }
...@@ -1824,18 +1815,12 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -1824,18 +1815,12 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
spierrcontext.arg = (void *) plansource->query_string; spierrcontext.arg = (void *) plansource->query_string;
if (plan->saved) /*
{ * Replan if needed, and increment plan refcount. If it's a saved
/* Replan if needed, and increment plan refcount locally */ * plan, the refcount must be backed by the CurrentResourceOwner.
cplan = RevalidateCachedPlan(plansource, true); */
stmt_list = cplan->stmt_list; cplan = GetCachedPlan(plansource, paramLI, plan->saved);
} stmt_list = cplan->stmt_list;
else
{
/* No replan here */
cplan = NULL;
stmt_list = plansource->plan->stmt_list;
}
/* /*
* In the default non-read-only case, get a new snapshot, replacing * In the default non-read-only case, get a new snapshot, replacing
...@@ -1966,8 +1951,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -1966,8 +1951,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
} }
/* Done with this plan, so release refcount */ /* Done with this plan, so release refcount */
if (cplan) ReleaseCachedPlan(cplan, plan->saved);
ReleaseCachedPlan(cplan, true);
cplan = NULL; cplan = NULL;
/* /*
...@@ -1987,7 +1971,7 @@ fail: ...@@ -1987,7 +1971,7 @@ fail:
/* We no longer need the cached plan refcount, if any */ /* We no longer need the cached plan refcount, if any */
if (cplan) if (cplan)
ReleaseCachedPlan(cplan, true); ReleaseCachedPlan(cplan, plan->saved);
/* /*
* Pop the error context stack * Pop the error context stack
...@@ -2018,8 +2002,7 @@ fail: ...@@ -2018,8 +2002,7 @@ fail:
*/ */
static ParamListInfo static ParamListInfo
_SPI_convert_params(int nargs, Oid *argtypes, _SPI_convert_params(int nargs, Oid *argtypes,
Datum *Values, const char *Nulls, Datum *Values, const char *Nulls)
int pflags)
{ {
ParamListInfo paramLI; ParamListInfo paramLI;
...@@ -2043,7 +2026,7 @@ _SPI_convert_params(int nargs, Oid *argtypes, ...@@ -2043,7 +2026,7 @@ _SPI_convert_params(int nargs, Oid *argtypes,
prm->value = Values[i]; prm->value = Values[i];
prm->isnull = (Nulls && Nulls[i] == 'n'); prm->isnull = (Nulls && Nulls[i] == 'n');
prm->pflags = pflags; prm->pflags = PARAM_FLAG_CONST;
prm->ptype = argtypes[i]; prm->ptype = argtypes[i];
} }
} }
...@@ -2283,21 +2266,31 @@ _SPI_checktuples(void) ...@@ -2283,21 +2266,31 @@ _SPI_checktuples(void)
} }
/* /*
* Make an "unsaved" copy of the given plan, in a child context of parentcxt. * Convert a "temporary" SPIPlan into an "unsaved" plan.
*
* The passed _SPI_plan struct is on the stack, and all its subsidiary data
* is in or under the current SPI executor context. Copy the plan into the
* SPI procedure context so it will survive _SPI_end_call(). To minimize
* data copying, this destructively modifies the input plan, by taking the
* plancache entries away from it and reparenting them to the new SPIPlan.
*/ */
static SPIPlanPtr static SPIPlanPtr
_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) _SPI_make_plan_non_temp(SPIPlanPtr plan)
{ {
SPIPlanPtr newplan; SPIPlanPtr newplan;
MemoryContext parentcxt = _SPI_current->procCxt;
MemoryContext plancxt; MemoryContext plancxt;
MemoryContext oldcxt; MemoryContext oldcxt;
ListCell *lc; ListCell *lc;
Assert(!plan->saved); /* not currently supported */ /* Assert the input is a temporary SPIPlan */
Assert(plan->magic == _SPI_PLAN_MAGIC);
Assert(plan->plancxt == NULL);
/* /*
* Create a memory context for the plan. We don't expect the plan to be * Create a memory context for the plan, underneath the procedure context.
* very large, so use smaller-than-default alloc parameters. * We don't expect the plan to be very large, so use smaller-than-default
* alloc parameters.
*/ */
plancxt = AllocSetContextCreate(parentcxt, plancxt = AllocSetContextCreate(parentcxt,
"SPI Plan", "SPI Plan",
...@@ -2306,7 +2299,7 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) ...@@ -2306,7 +2299,7 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
ALLOCSET_SMALL_MAXSIZE); ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(plancxt); oldcxt = MemoryContextSwitchTo(plancxt);
/* Copy the SPI plan into its own context */ /* Copy the SPI_plan struct and subsidiary data into the new context */
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC; newplan->magic = _SPI_PLAN_MAGIC;
newplan->saved = false; newplan->saved = false;
...@@ -2324,46 +2317,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt) ...@@ -2324,46 +2317,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
newplan->parserSetup = plan->parserSetup; newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg; 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) foreach(lc, plan->plancache_list)
{ {
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
CachedPlanSource *newsource;
CachedPlan *cplan;
CachedPlan *newcplan;
/* Note: we assume we don't need to revalidate the plan */
cplan = plansource->plan;
newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
newcplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
newsource->query_string = pstrdup(plansource->query_string);
newsource->commandTag = plansource->commandTag;
newsource->param_types = newplan->argtypes;
newsource->num_params = newplan->nargs;
newsource->parserSetup = newplan->parserSetup;
newsource->parserSetupArg = newplan->parserSetupArg;
newsource->fully_planned = plansource->fully_planned;
newsource->fixed_result = plansource->fixed_result;
/* no need to worry about seach_path, generation or saved_xmin */
if (plansource->resultDesc)
newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
newsource->plan = newcplan;
newcplan->stmt_list = copyObject(cplan->stmt_list);
newcplan->fully_planned = cplan->fully_planned;
newplan->plancache_list = lappend(newplan->plancache_list, newsource); CachedPlanSetParentContext(plansource, parentcxt);
/* Build new list, with list cells in plancxt */
newplan->plancache_list = lappend(newplan->plancache_list, plansource);
} }
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
/* For safety, unlink the CachedPlanSources from the temporary plan */
plan->plancache_list = NIL;
return newplan; return newplan;
} }
/* /*
* Make a "saved" copy of the given plan, entrusting everything to plancache.c * Make a "saved" copy of the given plan.
*/ */
static SPIPlanPtr static SPIPlanPtr
_SPI_save_plan(SPIPlanPtr plan) _SPI_save_plan(SPIPlanPtr plan)
...@@ -2373,13 +2352,12 @@ _SPI_save_plan(SPIPlanPtr plan) ...@@ -2373,13 +2352,12 @@ _SPI_save_plan(SPIPlanPtr plan)
MemoryContext oldcxt; MemoryContext oldcxt;
ListCell *lc; ListCell *lc;
Assert(!plan->saved); /* not currently supported */
/* /*
* Create a memory context for the plan. We don't expect the plan to be * Create a memory context for the plan. We don't expect the plan to be
* very large, so use smaller-than-default alloc parameters. * very large, so use smaller-than-default alloc parameters. It's a
* transient context until we finish copying everything.
*/ */
plancxt = AllocSetContextCreate(CacheMemoryContext, plancxt = AllocSetContextCreate(CurrentMemoryContext,
"SPI Plan", "SPI Plan",
ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_INITSIZE,
...@@ -2389,7 +2367,7 @@ _SPI_save_plan(SPIPlanPtr plan) ...@@ -2389,7 +2367,7 @@ _SPI_save_plan(SPIPlanPtr plan)
/* Copy the SPI plan into its own context */ /* Copy the SPI plan into its own context */
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC; newplan->magic = _SPI_PLAN_MAGIC;
newplan->saved = true; newplan->saved = false;
newplan->plancache_list = NIL; newplan->plancache_list = NIL;
newplan->plancxt = plancxt; newplan->plancxt = plancxt;
newplan->cursor_options = plan->cursor_options; newplan->cursor_options = plan->cursor_options;
...@@ -2404,33 +2382,32 @@ _SPI_save_plan(SPIPlanPtr plan) ...@@ -2404,33 +2382,32 @@ _SPI_save_plan(SPIPlanPtr plan)
newplan->parserSetup = plan->parserSetup; newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg; newplan->parserSetupArg = plan->parserSetupArg;
/* Copy all the plancache entries */
foreach(lc, plan->plancache_list) foreach(lc, plan->plancache_list)
{ {
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
CachedPlanSource *newsource; CachedPlanSource *newsource;
CachedPlan *cplan;
/* Note: we assume we don't need to revalidate the plan */
cplan = plansource->plan;
newsource = CreateCachedPlan(plansource->raw_parse_tree,
plansource->query_string,
plansource->commandTag,
newplan->argtypes,
newplan->nargs,
newplan->cursor_options,
cplan->stmt_list,
true,
false);
if (newplan->parserSetup != NULL)
CachedPlanSetParserHook(newsource,
newplan->parserSetup,
newplan->parserSetupArg);
newsource = CopyCachedPlan(plansource);
newplan->plancache_list = lappend(newplan->plancache_list, newsource); newplan->plancache_list = lappend(newplan->plancache_list, newsource);
} }
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
/*
* Mark it saved, reparent it under CacheMemoryContext, and mark all the
* component CachedPlanSources as saved. This sequence cannot fail
* partway through, so there's no risk of long-term memory leakage.
*/
newplan->saved = true;
MemoryContextSetParent(newplan->plancxt, CacheMemoryContext);
foreach(lc, newplan->plancache_list)
{
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
SaveCachedPlan(plansource);
}
return newplan; return newplan;
} }
...@@ -161,10 +161,6 @@ static bool ignore_till_sync = false; ...@@ -161,10 +161,6 @@ static bool ignore_till_sync = false;
*/ */
static CachedPlanSource *unnamed_stmt_psrc = NULL; static CachedPlanSource *unnamed_stmt_psrc = NULL;
/* workspace for building a new unnamed statement in */
static MemoryContext unnamed_stmt_context = NULL;
/* assorted command-line switches */ /* assorted command-line switches */
static const char *userDoption = NULL; /* -D switch */ static const char *userDoption = NULL; /* -D switch */
...@@ -1116,14 +1112,14 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1116,14 +1112,14 @@ exec_parse_message(const char *query_string, /* string to execute */
Oid *paramTypes, /* parameter types */ Oid *paramTypes, /* parameter types */
int numParams) /* number of parameters */ int numParams) /* number of parameters */
{ {
MemoryContext unnamed_stmt_context = NULL;
MemoryContext oldcontext; MemoryContext oldcontext;
List *parsetree_list; List *parsetree_list;
Node *raw_parse_tree; Node *raw_parse_tree;
const char *commandTag; const char *commandTag;
List *querytree_list, List *querytree_list;
*stmt_list; CachedPlanSource *psrc;
bool is_named; bool is_named;
bool fully_planned;
bool save_log_statement_stats = log_statement_stats; bool save_log_statement_stats = log_statement_stats;
char msec_str[32]; char msec_str[32];
...@@ -1158,11 +1154,11 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -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 * named or not. For a named prepared statement, we do parsing in
* MessageContext and copy the finished trees into the prepared * MessageContext and copy the finished trees into the prepared
* statement's plancache entry; then the reset of MessageContext releases * 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 * 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 * 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 * copying parse trees. So in this case, we create the plancache entry's
* entry's context here, and do all the parsing work therein. * query_context here, and do all the parsing work therein.
*/ */
is_named = (stmt_name[0] != '\0'); is_named = (stmt_name[0] != '\0');
if (is_named) if (is_named)
...@@ -1174,9 +1170,9 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1174,9 +1170,9 @@ exec_parse_message(const char *query_string, /* string to execute */
{ {
/* Unnamed prepared statement --- release any prior unnamed stmt */ /* Unnamed prepared statement --- release any prior unnamed stmt */
drop_unnamed_stmt(); drop_unnamed_stmt();
/* Create context for parsing/planning */ /* Create context for parsing */
unnamed_stmt_context = unnamed_stmt_context =
AllocSetContextCreate(CacheMemoryContext, AllocSetContextCreate(MessageContext,
"unnamed prepared statement", "unnamed prepared statement",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE,
...@@ -1230,7 +1226,13 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1230,7 +1226,13 @@ exec_parse_message(const char *query_string, /* string to execute */
errdetail_abort())); 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)) if (analyze_requires_snapshot(raw_parse_tree))
{ {
...@@ -1239,18 +1241,14 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -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 * Analyze and rewrite the query. Note that the originally specified
* originally specified parameter set is not required to be complete, * parameter set is not required to be complete, so we have to use
* so we have to use parse_analyze_varparams(). * 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.
*/ */
if (log_parser_stats) if (log_parser_stats)
ResetUsage(); ResetUsage();
query = parse_analyze_varparams(copyObject(raw_parse_tree), query = parse_analyze_varparams(raw_parse_tree,
query_string, query_string,
&paramTypes, &paramTypes,
&numParams); &numParams);
...@@ -1274,22 +1272,7 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1274,22 +1272,7 @@ exec_parse_message(const char *query_string, /* string to execute */
querytree_list = pg_rewrite_query(query); querytree_list = pg_rewrite_query(query);
/* /* Done with the snapshot used for parsing */
* 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 */
if (snapshot_set) if (snapshot_set)
PopActiveSnapshot(); PopActiveSnapshot();
} }
...@@ -1298,56 +1281,47 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1298,56 +1281,47 @@ exec_parse_message(const char *query_string, /* string to execute */
/* Empty input string. This is legal. */ /* Empty input string. This is legal. */
raw_parse_tree = NULL; raw_parse_tree = NULL;
commandTag = NULL; commandTag = NULL;
stmt_list = NIL; psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag);
fully_planned = true; querytree_list = NIL;
} }
/* If we got a cancel signal in analysis or planning, quit */
CHECK_FOR_INTERRUPTS();
/* /*
* Store the query as a prepared statement. See above comments. * CachedPlanSource must be a direct child of MessageContext before we
* reparent unnamed_stmt_context under it, else we have a disconnected
* circular subgraph. Klugy, but less so than flipping contexts even
* more above.
*/ */
if (unnamed_stmt_context)
MemoryContextSetParent(psrc->context, MessageContext);
/* Finish filling in the CachedPlanSource */
CompleteCachedPlan(psrc,
querytree_list,
unnamed_stmt_context,
paramTypes,
numParams,
NULL,
NULL,
0, /* default cursor options */
true); /* fixed result */
/* If we got a cancel signal during analysis, quit */
CHECK_FOR_INTERRUPTS();
if (is_named) if (is_named)
{ {
StorePreparedStatement(stmt_name, /*
raw_parse_tree, * Store the query as a prepared statement.
query_string, */
commandTag, StorePreparedStatement(stmt_name, psrc, false);
paramTypes,
numParams,
0, /* default cursor options */
stmt_list,
false);
} }
else else
{ {
/* /*
* paramTypes and query_string need to be copied into * We just save the CachedPlanSource into unnamed_stmt_psrc.
* unnamed_stmt_context. The rest is there already
*/ */
Oid *newParamTypes; SaveCachedPlan(psrc);
unnamed_stmt_psrc = psrc;
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;
} }
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
...@@ -1412,7 +1386,6 @@ exec_bind_message(StringInfo input_message) ...@@ -1412,7 +1386,6 @@ exec_bind_message(StringInfo input_message)
char *query_string; char *query_string;
char *saved_stmt_name; char *saved_stmt_name;
ParamListInfo params; ParamListInfo params;
List *plan_list;
MemoryContext oldContext; MemoryContext oldContext;
bool save_log_statement_stats = log_statement_stats; bool save_log_statement_stats = log_statement_stats;
bool snapshot_set = false; bool snapshot_set = false;
...@@ -1437,7 +1410,7 @@ exec_bind_message(StringInfo input_message) ...@@ -1437,7 +1410,7 @@ exec_bind_message(StringInfo input_message)
} }
else else
{ {
/* Unnamed statements are re-prepared for every bind */ /* special-case the unnamed statement */
psrc = unnamed_stmt_psrc; psrc = unnamed_stmt_psrc;
if (!psrc) if (!psrc)
ereport(ERROR, ereport(ERROR,
...@@ -1522,7 +1495,7 @@ exec_bind_message(StringInfo input_message) ...@@ -1522,7 +1495,7 @@ exec_bind_message(StringInfo input_message)
/* /*
* Prepare to copy stuff into the portal's memory context. We do all this * 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 * 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. * PortalDefineQuery; that would result in leaking our plancache refcount.
*/ */
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
...@@ -1539,7 +1512,9 @@ exec_bind_message(StringInfo input_message) ...@@ -1539,7 +1512,9 @@ exec_bind_message(StringInfo input_message)
/* /*
* Set a snapshot if we have parameters to fetch (since the input * 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 * 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)) if (numParams > 0 || analyze_requires_snapshot(psrc->raw_parse_tree))
{ {
...@@ -1675,10 +1650,8 @@ exec_bind_message(StringInfo input_message) ...@@ -1675,10 +1650,8 @@ exec_bind_message(StringInfo input_message)
params->params[paramno].isnull = isNull; params->params[paramno].isnull = isNull;
/* /*
* We mark the params as CONST. This has no effect if we already * We mark the params as CONST. This ensures that any custom
* did planning, but if we didn't, it licenses the planner to * plan makes full use of the parameter values.
* substitute the parameters directly into the one-shot plan we
* will generate below.
*/ */
params->params[paramno].pflags = PARAM_FLAG_CONST; params->params[paramno].pflags = PARAM_FLAG_CONST;
params->params[paramno].ptype = ptype; params->params[paramno].ptype = ptype;
...@@ -1703,63 +1676,24 @@ exec_bind_message(StringInfo input_message) ...@@ -1703,63 +1676,24 @@ exec_bind_message(StringInfo input_message)
pq_getmsgend(input_message); pq_getmsgend(input_message);
if (psrc->fully_planned) /*
{ * Obtain a plan from the CachedPlanSource. Any cruft from (re)planning
/* * will be generated in MessageContext. The plan refcount will be
* Revalidate the cached plan; this may result in replanning. Any * assigned to the Portal, so it will be released at portal destruction.
* cruft will be generated in MessageContext. The plan refcount will */
* be assigned to the Portal, so it will be released at portal cplan = GetCachedPlan(psrc, params, false);
* 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;
}
/* /*
* Now we can define the portal. * Now we can define the portal.
* *
* DO NOT put any code that could possibly throw an error between the * 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, PortalDefineQuery(portal,
saved_stmt_name, saved_stmt_name,
query_string, query_string,
psrc->commandTag, psrc->commandTag,
plan_list, cplan->stmt_list,
cplan); cplan);
/* Done with the snapshot used for parameter I/O and parsing/planning */ /* Done with the snapshot used for parameter I/O and parsing/planning */
...@@ -2304,8 +2238,7 @@ exec_describe_statement_message(const char *stmt_name) ...@@ -2304,8 +2238,7 @@ exec_describe_statement_message(const char *stmt_name)
/* /*
* If we are in aborted transaction state, we can't run * If we are in aborted transaction state, we can't run
* SendRowDescriptionMessage(), because that needs catalog accesses. (We * SendRowDescriptionMessage(), because that needs catalog accesses.
* can't do RevalidateCachedPlan, either, but that's a lesser problem.)
* Hence, refuse to Describe statements that return data. (We shouldn't * Hence, refuse to Describe statements that return data. (We shouldn't
* just refuse all Describes, since that might break the ability of some * just refuse all Describes, since that might break the ability of some
* clients to issue COMMIT or ROLLBACK commands, if they use code that * clients to issue COMMIT or ROLLBACK commands, if they use code that
...@@ -2342,18 +2275,12 @@ exec_describe_statement_message(const char *stmt_name) ...@@ -2342,18 +2275,12 @@ exec_describe_statement_message(const char *stmt_name)
*/ */
if (psrc->resultDesc) if (psrc->resultDesc)
{ {
CachedPlan *cplan;
List *tlist; List *tlist;
/* Make sure the plan is up to date */ /* Get the plan's primary targetlist */
cplan = RevalidateCachedPlan(psrc, true); tlist = CachedPlanGetTargetList(psrc);
/* Get the primary statement and find out what it returns */
tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list));
SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL); SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
ReleaseCachedPlan(cplan, true);
} }
else else
pq_putemptymessage('n'); /* NoData */ pq_putemptymessage('n'); /* NoData */
...@@ -2536,19 +2463,14 @@ IsTransactionStmtList(List *parseTrees) ...@@ -2536,19 +2463,14 @@ IsTransactionStmtList(List *parseTrees)
static void static void
drop_unnamed_stmt(void) drop_unnamed_stmt(void)
{ {
/* Release any completed unnamed statement */ /* paranoia to avoid a dangling pointer in case of error */
if (unnamed_stmt_psrc) if (unnamed_stmt_psrc)
DropCachedPlan(unnamed_stmt_psrc); {
unnamed_stmt_psrc = NULL; CachedPlanSource *psrc = unnamed_stmt_psrc;
/* unnamed_stmt_psrc = NULL;
* If we failed while trying to build a prior unnamed statement, we may DropCachedPlan(psrc);
* 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;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* across query and transaction boundaries, in fact they live as long as * across query and transaction boundaries, in fact they live as long as
* the backend does. This works because the hashtable structures * the backend does. This works because the hashtable structures
* themselves are allocated by dynahash.c in its permanent DynaHashCxt, * 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 * There is not currently any provision for throwing away a no-longer-needed
* plan --- consider improving this someday. * plan --- consider improving this someday.
* *
...@@ -3316,7 +3316,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, ...@@ -3316,7 +3316,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
/* Save the plan if requested */ /* Save the plan if requested */
if (cache_plan) if (cache_plan)
{ {
qplan = SPI_saveplan(qplan); SPI_keepplan(qplan);
ri_HashPreparedPlan(qkey, qplan); ri_HashPreparedPlan(qkey, qplan);
} }
......
...@@ -316,7 +316,8 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags) ...@@ -316,7 +316,8 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
plan = SPI_prepare(query_getrulebyoid, 1, argtypes); plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
if (plan == NULL) if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid); 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) ...@@ -450,7 +451,8 @@ pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
plan = SPI_prepare(query_getviewrule, 2, argtypes); plan = SPI_prepare(query_getviewrule, 2, argtypes);
if (plan == NULL) if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule); elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
plan_getviewrule = SPI_saveplan(plan); SPI_keepplan(plan);
plan_getviewrule = plan;
} }
/* /*
......
...@@ -3,19 +3,23 @@ ...@@ -3,19 +3,23 @@
* plancache.c * plancache.c
* Plan cache management. * Plan cache management.
* *
* We can store a cached plan in either fully-planned format, or just * The plan cache manager has two principal responsibilities: deciding when
* parsed-and-rewritten if the caller wishes to postpone planning until * to use a generic plan versus a custom (parameter-value-specific) plan,
* actual parameter values are available. CachedPlanSource has the same * and tracking whether cached plans need to be invalidated because of schema
* contents either way, but CachedPlan contains a list of PlannedStmts * changes in the objects they depend on.
* and bare utility statements in the first case, or a list of Query nodes
* in the second case.
* *
* The plan cache manager itself is principally responsible for tracking * The logic for choosing generic or custom plans is in choose_custom_plan,
* whether cached plans should be invalidated because of schema changes in * which see for comments.
* the objects they depend on. When (and if) the next demand for a cached *
* plan occurs, the query will be replanned. Note that this could result * Cache invalidation is driven off sinval events. Any CachedPlanSource
* in an error, for example if a column referenced by the query is no * that matches the event is marked invalid, as is its generic CachedPlan
* longer present. The creator of a cached plan can specify whether it * if it has one. When (and if) the next demand for a cached plan occurs,
* parse analysis and rewrite is repeated to build a new valid query tree,
* and then planning is performed as normal.
*
* Note that if the sinval was a result of user DDL actions, parse analysis
* could throw an error, for example if a column referenced by the query is
* no longer present. The creator of a cached plan can specify whether it
* is allowable for the query to change output tupdesc on replan (this * is allowable for the query to change output tupdesc on replan (this
* could happen with "SELECT *" for example) --- if so, it's up to the * could happen with "SELECT *" for example) --- if so, it's up to the
* caller to notice changes and cope with them. * caller to notice changes and cope with them.
...@@ -41,6 +45,8 @@ ...@@ -41,6 +45,8 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include <limits.h>
#include "access/transam.h" #include "access/transam.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "executor/executor.h" #include "executor/executor.h"
...@@ -58,15 +64,28 @@ ...@@ -58,15 +64,28 @@
#include "utils/syscache.h" #include "utils/syscache.h"
static List *cached_plans_list = NIL; /*
* This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list, * those that are in long-lived storage and are examined for sinval events).
MemoryContext plan_context); * We thread the structs manually instead of using List cells so that we can
* guarantee to save a CachedPlanSource without error.
*/
static CachedPlanSource *first_saved_plan = NULL;
static void ReleaseGenericPlan(CachedPlanSource *plansource);
static List *RevalidateCachedQuery(CachedPlanSource *plansource);
static bool CheckCachedPlan(CachedPlanSource *plansource);
static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
ParamListInfo boundParams);
static bool choose_custom_plan(CachedPlanSource *plansource,
ParamListInfo boundParams);
static double cached_plan_cost(CachedPlan *plan);
static void AcquireExecutorLocks(List *stmt_list, bool acquire); static void AcquireExecutorLocks(List *stmt_list, bool acquire);
static void AcquirePlannerLocks(List *stmt_list, bool acquire); static void AcquirePlannerLocks(List *stmt_list, bool acquire);
static void ScanQueryForLocks(Query *parsetree, bool acquire); static void ScanQueryForLocks(Query *parsetree, bool acquire);
static bool ScanQueryWalker(Node *node, bool *acquire); static bool ScanQueryWalker(Node *node, bool *acquire);
static bool plan_list_is_transient(List *stmt_list); static bool plan_list_is_transient(List *stmt_list);
static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
static void PlanCacheRelCallback(Datum arg, Oid relid); static void PlanCacheRelCallback(Datum arg, Oid relid);
static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue); static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
...@@ -90,32 +109,33 @@ InitPlanCache(void) ...@@ -90,32 +109,33 @@ InitPlanCache(void)
/* /*
* CreateCachedPlan: initially create a plan cache entry. * CreateCachedPlan: initially create a plan cache entry.
* *
* The caller must already have successfully parsed/planned the query; * Creation of a cached plan is divided into two steps, CreateCachedPlan and
* about all that we do here is copy it into permanent storage. * CompleteCachedPlan. CreateCachedPlan should be called after running the
* query through raw_parser, but before doing parse analysis and rewrite;
* CompleteCachedPlan is called after that. The reason for this arrangement
* is that it can save one round of copying of the raw parse tree, since
* the parser will normally scribble on the raw parse tree. Callers would
* otherwise need to make an extra copy of the parse tree to ensure they
* still had a clean copy to present at plan cache creation time.
*
* All arguments presented to CreateCachedPlan are copied into a memory
* context created as a child of the call-time CurrentMemoryContext, which
* should be a reasonably short-lived working context that will go away in
* event of an error. This ensures that the cached plan data structure will
* likewise disappear if an error occurs before we have fully constructed it.
* Once constructed, the cached plan can be made longer-lived, if needed,
* by calling SaveCachedPlan.
* *
* raw_parse_tree: output of raw_parser() * raw_parse_tree: output of raw_parser()
* query_string: original query text (as of PG 8.4, must not be NULL) * query_string: original query text
* commandTag: compile-time-constant tag for query, or NULL if empty query * commandTag: compile-time-constant tag for query, or NULL if empty query
* param_types: array of fixed parameter type OIDs, or NULL if none
* num_params: number of fixed parameters
* cursor_options: options bitmask that was/will be passed to planner
* stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
* fully_planned: are we caching planner or rewriter output?
* fixed_result: TRUE to disallow changes in result tupdesc
*/ */
CachedPlanSource * CachedPlanSource *
CreateCachedPlan(Node *raw_parse_tree, CreateCachedPlan(Node *raw_parse_tree,
const char *query_string, const char *query_string,
const char *commandTag, const char *commandTag)
Oid *param_types,
int num_params,
int cursor_options,
List *stmt_list,
bool fully_planned,
bool fixed_result)
{ {
CachedPlanSource *plansource; CachedPlanSource *plansource;
OverrideSearchPath *search_path;
MemoryContext source_context; MemoryContext source_context;
MemoryContext oldcxt; MemoryContext oldcxt;
...@@ -123,61 +143,50 @@ CreateCachedPlan(Node *raw_parse_tree, ...@@ -123,61 +143,50 @@ CreateCachedPlan(Node *raw_parse_tree,
/* /*
* Make a dedicated memory context for the CachedPlanSource and its * Make a dedicated memory context for the CachedPlanSource and its
* subsidiary data. We expect it can be pretty small. * permanent subsidiary data. It's probably not going to be large, but
* just in case, use the default maxsize parameter. Initially it's a
* child of the caller's context (which we assume to be transient), so
* that it will be cleaned up on error.
*/ */
source_context = AllocSetContextCreate(CacheMemoryContext, source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource", "CachedPlanSource",
ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE); ALLOCSET_DEFAULT_MAXSIZE);
/*
* Fetch current search_path into new context, but do any recalculation
* work required in caller's context.
*/
search_path = GetOverrideSearchPath(source_context);
/* /*
* Create and fill the CachedPlanSource struct within the new context. * Create and fill the CachedPlanSource struct within the new context.
* Most fields are just left empty for the moment.
*/ */
oldcxt = MemoryContextSwitchTo(source_context); oldcxt = MemoryContextSwitchTo(source_context);
plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = copyObject(raw_parse_tree); plansource->raw_parse_tree = copyObject(raw_parse_tree);
plansource->query_string = pstrdup(query_string); plansource->query_string = pstrdup(query_string);
plansource->commandTag = commandTag; /* no copying needed */ plansource->commandTag = commandTag;
if (num_params > 0) plansource->param_types = NULL;
{ plansource->num_params = 0;
plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
}
else
plansource->param_types = NULL;
plansource->num_params = num_params;
/* these can be set later with CachedPlanSetParserHook: */
plansource->parserSetup = NULL; plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL; plansource->parserSetupArg = NULL;
plansource->cursor_options = cursor_options; plansource->cursor_options = 0;
plansource->fully_planned = fully_planned; plansource->fixed_result = false;
plansource->fixed_result = fixed_result; plansource->resultDesc = NULL;
plansource->search_path = search_path; plansource->search_path = NULL;
plansource->generation = 0; /* StoreCachedPlan will increment */
plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list);
plansource->plan = NULL;
plansource->context = source_context; plansource->context = source_context;
plansource->orig_plan = NULL; plansource->query_list = NIL;
plansource->relationOids = NIL;
/* plansource->invalItems = NIL;
* Copy the current output plans into the plancache entry. plansource->query_context = NULL;
*/ plansource->gplan = NULL;
StoreCachedPlan(plansource, stmt_list, NULL); plansource->is_complete = false;
plansource->is_saved = false;
/* plansource->is_valid = false;
* Now we can add the entry to the list of cached plans. The List nodes plansource->generation = 0;
* live in CacheMemoryContext. plansource->next_saved = NULL;
*/ plansource->generic_cost = -1;
MemoryContextSwitchTo(CacheMemoryContext); plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
cached_plans_list = lappend(cached_plans_list, plansource);
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
...@@ -185,274 +194,465 @@ CreateCachedPlan(Node *raw_parse_tree, ...@@ -185,274 +194,465 @@ CreateCachedPlan(Node *raw_parse_tree,
} }
/* /*
* FastCreateCachedPlan: create a plan cache entry with minimal data copying. * CompleteCachedPlan: second step of creating a plan cache entry.
*
* Pass in the analyzed-and-rewritten form of the query, as well as the
* required subsidiary data about parameters and such. All passed values will
* be copied into the CachedPlanSource's memory, except as specified below.
* After this is called, GetCachedPlan can be called to obtain a plan, and
* optionally the CachedPlanSource can be saved using SaveCachedPlan.
*
* If querytree_context is not NULL, the querytree_list must be stored in that
* context (but the other parameters need not be). The querytree_list is not
* copied, rather the given context is kept as the initial query_context of
* the CachedPlanSource. (It should have been created as a child of the
* caller's working memory context, but it will now be reparented to belong
* to the CachedPlanSource.) The querytree_context is normally the context in
* which the caller did raw parsing and parse analysis. This approach saves
* one tree copying step compared to passing NULL, but leaves lots of extra
* cruft in the query_context, namely whatever extraneous stuff parse analysis
* created, as well as whatever went unused from the raw parse tree. Using
* this option is a space-for-time tradeoff that is appropriate if the
* CachedPlanSource is not expected to survive long.
* *
* For plans that aren't expected to live very long, the copying overhead of * plancache.c cannot know how to copy the data referenced by parserSetupArg,
* CreateCachedPlan is annoying. We provide this variant entry point in which * and it would often be inappropriate to do so anyway. When using that
* the caller has already placed all the data in a suitable memory context. * option, it is caller's responsibility that the referenced data remains
* The source data and completed plan are in the same context, since this * valid for as long as the CachedPlanSource exists.
* avoids extra copy steps during plan construction. If the query ever does
* need replanning, we'll generate a separate new CachedPlan at that time, but
* the CachedPlanSource and the initial CachedPlan share the caller-provided
* context and go away together when neither is needed any longer. (Because
* the parser and planner generate extra cruft in addition to their real
* output, this approach means that the context probably contains a bunch of
* useless junk as well as the useful trees. Hence, this method is a
* space-for-time tradeoff, which is worth making for plans expected to be
* short-lived.)
* *
* raw_parse_tree, query_string, param_types, and stmt_list must reside in the * plansource: structure returned by CreateCachedPlan
* given context, which must have adequate lifespan (recommendation: make it a * querytree_list: analyzed-and-rewritten form of query (list of Query nodes)
* child of CacheMemoryContext). Otherwise the API is the same as * querytree_context: memory context containing querytree_list,
* CreateCachedPlan. * or NULL to copy querytree_list into a fresh context
* param_types: array of fixed parameter type OIDs, or NULL if none
* num_params: number of fixed parameters
* parserSetup: alternate method for handling query parameters
* parserSetupArg: data to pass to parserSetup
* cursor_options: options bitmask to pass to planner
* fixed_result: TRUE to disallow future changes in query's result tupdesc
*/ */
CachedPlanSource * void
FastCreateCachedPlan(Node *raw_parse_tree, CompleteCachedPlan(CachedPlanSource *plansource,
char *query_string, List *querytree_list,
const char *commandTag, MemoryContext querytree_context,
Oid *param_types, Oid *param_types,
int num_params, int num_params,
int cursor_options, ParserSetupHook parserSetup,
List *stmt_list, void *parserSetupArg,
bool fully_planned, int cursor_options,
bool fixed_result, bool fixed_result)
MemoryContext context)
{ {
CachedPlanSource *plansource; MemoryContext source_context = plansource->context;
OverrideSearchPath *search_path; MemoryContext oldcxt = CurrentMemoryContext;
MemoryContext oldcxt;
Assert(query_string != NULL); /* required as of 8.4 */ /* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(!plansource->is_complete);
/*
* If caller supplied a querytree_context, reparent it underneath the
* CachedPlanSource's context; otherwise, create a suitable context and
* copy the querytree_list into it.
*/
if (querytree_context != NULL)
{
MemoryContextSetParent(querytree_context, source_context);
MemoryContextSwitchTo(querytree_context);
}
else
{
/* Again, it's a good bet the querytree_context can be small */
querytree_context = AllocSetContextCreate(source_context,
"CachedPlanQuery",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(querytree_context);
querytree_list = (List *) copyObject(querytree_list);
}
plansource->query_context = querytree_context;
plansource->query_list = querytree_list;
/* /*
* Fetch current search_path into given context, but do any recalculation * Use the planner machinery to extract dependencies. Data is saved in
* work required in caller's context. * query_context. (We assume that not a lot of extra cruft is created
* by this call.)
*/ */
search_path = GetOverrideSearchPath(context); extract_query_dependencies((Node *) querytree_list,
&plansource->relationOids,
&plansource->invalItems);
/* /*
* Create and fill the CachedPlanSource struct within the given context. * Save the final parameter types (or other parameter specification data)
* into the source_context, as well as our other parameters. Also save
* the result tuple descriptor.
*/ */
oldcxt = MemoryContextSwitchTo(context); MemoryContextSwitchTo(source_context);
plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
plansource->raw_parse_tree = raw_parse_tree; if (num_params > 0)
plansource->query_string = query_string; {
plansource->commandTag = commandTag; /* no copying needed */ plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
plansource->param_types = param_types; memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
}
else
plansource->param_types = NULL;
plansource->num_params = num_params; plansource->num_params = num_params;
/* these can be set later with CachedPlanSetParserHook: */ plansource->parserSetup = parserSetup;
plansource->parserSetup = NULL; plansource->parserSetupArg = parserSetupArg;
plansource->parserSetupArg = NULL;
plansource->cursor_options = cursor_options; plansource->cursor_options = cursor_options;
plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result; plansource->fixed_result = fixed_result;
plansource->search_path = search_path; plansource->resultDesc = PlanCacheComputeResultDesc(querytree_list);
plansource->generation = 0; /* StoreCachedPlan will increment */
plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); MemoryContextSwitchTo(oldcxt);
plansource->plan = NULL;
plansource->context = context;
plansource->orig_plan = NULL;
/* /*
* Store the current output plans into the plancache entry. * Fetch current search_path into dedicated context, but do any
* recalculation work required in caller's context.
*/ */
StoreCachedPlan(plansource, stmt_list, context); plansource->search_path = GetOverrideSearchPath(source_context);
plansource->is_complete = true;
plansource->is_valid = true;
}
/*
* SaveCachedPlan: save a cached plan permanently
*
* This function moves the cached plan underneath CacheMemoryContext (making
* it live for the life of the backend, unless explicitly dropped), and adds
* it to the list of cached plans that are checked for invalidation when an
* sinval event occurs.
*
* This is guaranteed not to throw error; callers typically depend on that
* since this is called just before or just after adding a pointer to the
* CachedPlanSource to some permanent data structure of their own. Up until
* this is done, a CachedPlanSource is just transient data that will go away
* automatically on transaction abort.
*/
void
SaveCachedPlan(CachedPlanSource *plansource)
{
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
Assert(!plansource->is_saved);
/* /*
* Since the context is owned by the CachedPlan, advance its refcount. * In typical use, this function would be called before generating any
* plans from the CachedPlanSource. If there is a generic plan, moving
* it into CacheMemoryContext would be pretty risky since it's unclear
* whether the caller has taken suitable care with making references
* long-lived. Best thing to do seems to be to discard the plan.
*/ */
plansource->orig_plan = plansource->plan; ReleaseGenericPlan(plansource);
plansource->orig_plan->refcount++;
/* /*
* Now we can add the entry to the list of cached plans. The List nodes * Reparent the source memory context under CacheMemoryContext so that
* live in CacheMemoryContext. * it will live indefinitely. The query_context follows along since it's
* already a child of the other one.
*/ */
MemoryContextSwitchTo(CacheMemoryContext); MemoryContextSetParent(plansource->context, CacheMemoryContext);
cached_plans_list = lappend(cached_plans_list, plansource);
MemoryContextSwitchTo(oldcxt); /*
* Add the entry to the global list of cached plans.
*/
plansource->next_saved = first_saved_plan;
first_saved_plan = plansource;
return plansource; plansource->is_saved = true;
} }
/* /*
* CachedPlanSetParserHook: set up to use parser callback hooks * DropCachedPlan: destroy a cached plan.
* *
* Use this when a caller wants to manage parameter information via parser * Actually this only destroys the CachedPlanSource: any referenced CachedPlan
* callbacks rather than a fixed parameter-types list. Beware that the * is released, but not destroyed until its refcount goes to zero. That
* information pointed to by parserSetupArg must be valid for as long as * handles the situation where DropCachedPlan is called while the plan is
* the cached plan might be replanned! * still in use.
*/ */
void void
CachedPlanSetParserHook(CachedPlanSource *plansource, DropCachedPlan(CachedPlanSource *plansource)
ParserSetupHook parserSetup,
void *parserSetupArg)
{ {
/* Must not have specified a fixed parameter-types list */ Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->param_types == NULL);
Assert(plansource->num_params == 0); /* If it's been saved, remove it from the list */
/* OK, save hook info */ if (plansource->is_saved)
plansource->parserSetup = parserSetup; {
plansource->parserSetupArg = parserSetupArg; if (first_saved_plan == plansource)
first_saved_plan = plansource->next_saved;
else
{
CachedPlanSource *psrc;
for (psrc = first_saved_plan; psrc; psrc = psrc->next_saved)
{
if (psrc->next_saved == plansource)
{
psrc->next_saved = plansource->next_saved;
break;
}
}
}
plansource->is_saved = false;
}
/* Decrement generic CachePlan's refcount and drop if no longer needed */
ReleaseGenericPlan(plansource);
/*
* Remove the CachedPlanSource and all subsidiary data (including the
* query_context if any).
*/
MemoryContextDelete(plansource->context);
} }
/* /*
* StoreCachedPlan: store a built or rebuilt plan into a plancache entry. * ReleaseGenericPlan: release a CachedPlanSource's generic plan, if any.
*
* Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
*/ */
static void static void
StoreCachedPlan(CachedPlanSource *plansource, ReleaseGenericPlan(CachedPlanSource *plansource)
List *stmt_list,
MemoryContext plan_context)
{ {
CachedPlan *plan; /* Be paranoid about the possibility that ReleaseCachedPlan fails */
if (plansource->gplan)
{
CachedPlan *plan = plansource->gplan;
Assert(plan->magic == CACHEDPLAN_MAGIC);
plansource->gplan = NULL;
ReleaseCachedPlan(plan, false);
}
}
/*
* RevalidateCachedQuery: ensure validity of analyzed-and-rewritten query tree.
*
* What we do here is re-acquire locks and redo parse analysis if necessary.
* On return, the query_list is valid and we have sufficient locks to begin
* planning.
*
* If any parse analysis activity is required, the caller's memory context is
* used for that work.
*
* The result value is the transient analyzed-and-rewritten query tree if we
* had to do re-analysis, and NIL otherwise. (This is returned just to save
* a tree copying step in a subsequent BuildCachedPlan call.)
*/
static List *
RevalidateCachedQuery(CachedPlanSource *plansource)
{
bool snapshot_set;
Node *rawtree;
List *tlist; /* transient query-tree list */
List *qlist; /* permanent query-tree list */
TupleDesc resultDesc;
MemoryContext querytree_context;
MemoryContext oldcxt; MemoryContext oldcxt;
if (plan_context == NULL) /*
* If the query is currently valid, acquire locks on the referenced
* objects; then check again. We need to do it this way to cover the race
* condition that an invalidation message arrives before we get the locks.
*/
if (plansource->is_valid)
{ {
/* AcquirePlannerLocks(plansource->query_list, true);
* Make a dedicated memory context for the CachedPlan and its
* subsidiary data. It's probably not going to be large, but just in
* case, use the default maxsize parameter.
*/
plan_context = AllocSetContextCreate(CacheMemoryContext,
"CachedPlan",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/* /*
* Copy supplied data into the new context. * By now, if any invalidation has happened, the inval callback
* functions will have marked the query invalid.
*/ */
oldcxt = MemoryContextSwitchTo(plan_context); if (plansource->is_valid)
{
/* Successfully revalidated and locked the query. */
return NIL;
}
stmt_list = (List *) copyObject(stmt_list); /* Ooops, the race case happened. Release useless locks. */
AcquirePlannerLocks(plansource->query_list, false);
} }
else
/*
* Discard the no-longer-useful query tree. (Note: we don't want to
* do this any earlier, else we'd not have been able to release locks
* correctly in the race condition case.)
*/
plansource->is_valid = false;
plansource->query_list = NIL;
plansource->relationOids = NIL;
plansource->invalItems = NIL;
/*
* Free the query_context. We don't really expect MemoryContextDelete to
* fail, but just in case, make sure the CachedPlanSource is left in a
* reasonably sane state. (The generic plan won't get unlinked yet,
* but that's acceptable.)
*/
if (plansource->query_context)
{ {
/* Assume subsidiary data is in the given context */ MemoryContext qcxt = plansource->query_context;
oldcxt = MemoryContextSwitchTo(plan_context);
plansource->query_context = NULL;
MemoryContextDelete(qcxt);
} }
/* Drop the generic plan reference if any */
ReleaseGenericPlan(plansource);
/* /*
* Create and fill the CachedPlan struct within the new context. * Now re-do parse analysis and rewrite. This not incidentally acquires
* the locks we need to do planning safely.
*/ */
plan = (CachedPlan *) palloc(sizeof(CachedPlan)); Assert(plansource->is_complete);
plan->stmt_list = stmt_list;
plan->fully_planned = plansource->fully_planned; /*
plan->dead = false; * Restore the search_path that was in use when the plan was made. See
if (plansource->fully_planned && plan_list_is_transient(stmt_list)) * comments for PushOverrideSearchPath about limitations of this.
*
* (XXX is there anything else we really need to restore?)
*/
PushOverrideSearchPath(plansource->search_path);
/*
* If a snapshot is already set (the normal case), we can just use that
* for parsing/planning. But if it isn't, install one. Note: no point in
* checking whether parse analysis requires a snapshot; utility commands
* don't have invalidatable plans, so we'd not get here for such a
* command.
*/
snapshot_set = false;
if (!ActiveSnapshotSet())
{ {
Assert(TransactionIdIsNormal(TransactionXmin)); PushActiveSnapshot(GetTransactionSnapshot());
plan->saved_xmin = TransactionXmin; snapshot_set = true;
} }
/*
* Run parse analysis and rule rewriting. The parser tends to scribble on
* its input, so we must copy the raw parse tree to prevent corruption of
* the cache.
*/
rawtree = copyObject(plansource->raw_parse_tree);
if (plansource->parserSetup != NULL)
tlist = pg_analyze_and_rewrite_params(rawtree,
plansource->query_string,
plansource->parserSetup,
plansource->parserSetupArg);
else else
plan->saved_xmin = InvalidTransactionId; tlist = pg_analyze_and_rewrite(rawtree,
plan->refcount = 1; /* for the parent's link */ plansource->query_string,
plan->generation = ++(plansource->generation); plansource->param_types,
plan->context = plan_context; plansource->num_params);
if (plansource->fully_planned)
{
/*
* Planner already extracted dependencies, we don't have to ... except
* in the case of EXPLAIN. We assume here that EXPLAIN can't appear
* in a list with other commands.
*/
plan->relationOids = plan->invalItems = NIL;
if (list_length(stmt_list) == 1 && /* Release snapshot if we got one */
IsA(linitial(stmt_list), ExplainStmt)) if (snapshot_set)
{ PopActiveSnapshot();
ExplainStmt *estmt = (ExplainStmt *) linitial(stmt_list);
extract_query_dependencies(estmt->query, /* Now we can restore current search path */
&plan->relationOids, PopOverrideSearchPath();
&plan->invalItems);
} /*
* Check or update the result tupdesc. XXX should we use a weaker
* condition than equalTupleDescs() here?
*
* We assume the parameter types didn't change from the first time, so no
* need to update that.
*/
resultDesc = PlanCacheComputeResultDesc(tlist);
if (resultDesc == NULL && plansource->resultDesc == NULL)
{
/* OK, doesn't return tuples */
} }
else else if (resultDesc == NULL || plansource->resultDesc == NULL ||
!equalTupleDescs(resultDesc, plansource->resultDesc))
{ {
/* Use the planner machinery to extract dependencies */ /* can we give a better error message? */
extract_query_dependencies((Node *) stmt_list, if (plansource->fixed_result)
&plan->relationOids, ereport(ERROR,
&plan->invalItems); (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cached plan must not change result type")));
oldcxt = MemoryContextSwitchTo(plansource->context);
if (resultDesc)
resultDesc = CreateTupleDescCopy(resultDesc);
if (plansource->resultDesc)
FreeTupleDesc(plansource->resultDesc);
plansource->resultDesc = resultDesc;
MemoryContextSwitchTo(oldcxt);
} }
Assert(plansource->plan == NULL); /*
plansource->plan = plan; * Allocate new query_context and copy the completed querytree into it.
* It's transient until we complete the copying and dependency extraction.
*/
querytree_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanQuery",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
oldcxt = MemoryContextSwitchTo(querytree_context);
MemoryContextSwitchTo(oldcxt); qlist = (List *) copyObject(tlist);
}
/* /*
* DropCachedPlan: destroy a cached plan. * Use the planner machinery to extract dependencies. Data is saved in
* * query_context. (We assume that not a lot of extra cruft is created
* Actually this only destroys the CachedPlanSource: the referenced CachedPlan * by this call.)
* is released, but not destroyed until its refcount goes to zero. That */
* handles the situation where DropCachedPlan is called while the plan is extract_query_dependencies((Node *) qlist,
* still in use. &plansource->relationOids,
*/ &plansource->invalItems);
void
DropCachedPlan(CachedPlanSource *plansource)
{
/* Validity check that we were given a CachedPlanSource */
Assert(list_member_ptr(cached_plans_list, plansource));
/* Remove it from the list */ MemoryContextSwitchTo(oldcxt);
cached_plans_list = list_delete_ptr(cached_plans_list, plansource);
/* Now reparent the finished query_context and save the links */
MemoryContextSetParent(querytree_context, plansource->context);
/* Decrement child CachePlan's refcount and drop if no longer needed */ plansource->query_context = querytree_context;
if (plansource->plan) plansource->query_list = qlist;
ReleaseCachedPlan(plansource->plan, false);
/* /*
* If CachedPlanSource has independent storage, just drop it. Otherwise * Note: we do not reset generic_cost or total_custom_cost, although
* decrement the refcount on the CachePlan that owns the storage. * we could choose to do so. If the DDL or statistics change that
* prompted the invalidation meant a significant change in the cost
* estimates, it would be better to reset those variables and start
* fresh; but often it doesn't, and we're better retaining our hard-won
* knowledge about the relative costs.
*/ */
if (plansource->orig_plan == NULL)
{ plansource->is_valid = true;
/* Remove the CachedPlanSource and all subsidiary data */
MemoryContextDelete(plansource->context); /* Return transient copy of querytrees for possible use in planning */
} return tlist;
else
{
Assert(plansource->context == plansource->orig_plan->context);
ReleaseCachedPlan(plansource->orig_plan, false);
}
} }
/* /*
* RevalidateCachedPlan: prepare for re-use of a previously cached plan. * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
*
* What we do here is re-acquire locks and rebuild the plan if necessary.
* On return, the plan is valid and we have sufficient locks to begin
* execution (or planning, if not fully_planned).
* *
* On return, the refcount of the plan has been incremented; a later * Caller must have already called RevalidateCachedQuery to verify that the
* ReleaseCachedPlan() call is expected. The refcount has been reported * querytree is up to date.
* to the CurrentResourceOwner if useResOwner is true.
* *
* Note: if any replanning activity is required, the caller's memory context * On a "true" return, we have acquired the locks needed to run the plan.
* is used for that work. * (We must do this for the "true" result to be race-condition-free.)
*/ */
CachedPlan * static bool
RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) CheckCachedPlan(CachedPlanSource *plansource)
{ {
CachedPlan *plan; CachedPlan *plan = plansource->gplan;
/* Assert that caller checked the querytree */
Assert(plansource->is_valid);
/* Validity check that we were given a CachedPlanSource */ /* If there's no generic plan, just say "false" */
Assert(list_member_ptr(cached_plans_list, plansource)); if (!plan)
return false;
Assert(plan->magic == CACHEDPLAN_MAGIC);
/* /*
* If the plan currently appears valid, acquire locks on the referenced * If it appears valid, acquire locks and recheck; this is much the same
* objects; then check again. We need to do it this way to cover the race * logic as in RevalidateCachedQuery, but for a plan.
* condition that an invalidation message arrives before we get the lock.
*/ */
plan = plansource->plan; if (plan->is_valid)
if (plan && !plan->dead)
{ {
/* /*
* Plan must have positive refcount because it is referenced by * Plan must have positive refcount because it is referenced by
...@@ -460,164 +660,348 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) ...@@ -460,164 +660,348 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
*/ */
Assert(plan->refcount > 0); Assert(plan->refcount > 0);
if (plan->fully_planned) AcquireExecutorLocks(plan->stmt_list, true);
AcquireExecutorLocks(plan->stmt_list, true);
else
AcquirePlannerLocks(plan->stmt_list, true);
/* /*
* If plan was transient, check to see if TransactionXmin has * If plan was transient, check to see if TransactionXmin has
* advanced, and if so invalidate it. * advanced, and if so invalidate it.
*/ */
if (!plan->dead && if (plan->is_valid &&
TransactionIdIsValid(plan->saved_xmin) && TransactionIdIsValid(plan->saved_xmin) &&
!TransactionIdEquals(plan->saved_xmin, TransactionXmin)) !TransactionIdEquals(plan->saved_xmin, TransactionXmin))
plan->dead = true; plan->is_valid = false;
/* /*
* By now, if any invalidation has happened, the inval callback * By now, if any invalidation has happened, the inval callback
* functions will have marked the plan dead. * functions will have marked the plan invalid.
*/ */
if (plan->dead) if (plan->is_valid)
{ {
/* Ooops, the race case happened. Release useless locks. */ /* Successfully revalidated and locked the query. */
if (plan->fully_planned) return true;
AcquireExecutorLocks(plan->stmt_list, false);
else
AcquirePlannerLocks(plan->stmt_list, false);
} }
/* Ooops, the race case happened. Release useless locks. */
AcquireExecutorLocks(plan->stmt_list, false);
} }
/* /*
* If plan has been invalidated, unlink it from the parent and release it. * Plan has been invalidated, so unlink it from the parent and release it.
*/ */
if (plan && plan->dead) ReleaseGenericPlan(plansource);
return false;
}
/*
* BuildCachedPlan: construct a new CachedPlan from a CachedPlanSource.
*
* qlist should be the result value from a previous RevalidateCachedQuery.
*
* To build a generic, parameter-value-independent plan, pass NULL for
* boundParams. To build a custom plan, pass the actual parameter values via
* boundParams. For best effect, the PARAM_FLAG_CONST flag should be set on
* each parameter value; otherwise the planner will treat the value as a
* hint rather than a hard constant.
*
* Planning work is done in the caller's memory context. The finished plan
* is in a child memory context, which typically should get reparented.
*/
static CachedPlan *
BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
ParamListInfo boundParams)
{
CachedPlan *plan;
List *plist;
bool snapshot_set;
bool spi_pushed;
MemoryContext plan_context;
MemoryContext oldcxt;
/* Assert that caller checked the querytree */
Assert(plansource->is_valid);
/*
* If we don't already have a copy of the querytree list that can be
* scribbled on by the planner, make one.
*/
if (qlist == NIL)
qlist = (List *) copyObject(plansource->query_list);
/*
* Restore the search_path that was in use when the plan was made. See
* comments for PushOverrideSearchPath about limitations of this.
*
* (XXX is there anything else we really need to restore?)
*
* Note: it's a bit annoying to do this and snapshot-setting twice in the
* case where we have to do both re-analysis and re-planning. However,
* until there's some evidence that the cost is actually meaningful
* compared to parse analysis + planning, I'm not going to contort the
* code enough to avoid that.
*/
PushOverrideSearchPath(plansource->search_path);
/*
* If a snapshot is already set (the normal case), we can just use
* that for parsing/planning. But if it isn't, install one. Note: no
* point in checking whether parse analysis requires a snapshot;
* utility commands don't have invalidatable plans, so we'd not get
* here for such a command.
*/
snapshot_set = false;
if (!ActiveSnapshotSet())
{ {
plansource->plan = NULL; PushActiveSnapshot(GetTransactionSnapshot());
ReleaseCachedPlan(plan, false); snapshot_set = true;
plan = NULL;
} }
/* /*
* Build a new plan if needed. * The planner may try to call SPI-using functions, which causes a
* problem if we're already inside one. Rather than expect all
* SPI-using code to do SPI_push whenever a replan could happen,
* it seems best to take care of the case here.
*/ */
if (!plan) spi_pushed = SPI_push_conditional();
/*
* Generate the plan.
*/
plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
/* Clean up SPI state */
SPI_pop_conditional(spi_pushed);
/* Release snapshot if we got one */
if (snapshot_set)
PopActiveSnapshot();
/* Now we can restore current search path */
PopOverrideSearchPath();
/*
* Make a dedicated memory context for the CachedPlan and its subsidiary
* data. It's probably not going to be large, but just in case, use the
* default maxsize parameter. It's transient for the moment.
*/
plan_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlan",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Copy plan into the new context.
*/
oldcxt = MemoryContextSwitchTo(plan_context);
plist = (List *) copyObject(plist);
/*
* Create and fill the CachedPlan struct within the new context.
*/
plan = (CachedPlan *) palloc(sizeof(CachedPlan));
plan->magic = CACHEDPLAN_MAGIC;
plan->stmt_list = plist;
if (plan_list_is_transient(plist))
{ {
bool snapshot_set = false; Assert(TransactionIdIsNormal(TransactionXmin));
Node *rawtree; plan->saved_xmin = TransactionXmin;
List *slist; }
TupleDesc resultDesc; else
plan->saved_xmin = InvalidTransactionId;
plan->refcount = 0;
plan->context = plan_context;
plan->is_saved = false;
plan->is_valid = true;
/* /* assign generation number to new plan */
* Restore the search_path that was in use when the plan was made. See plan->generation = ++(plansource->generation);
* comments for PushOverrideSearchPath about limitations of this.
*
* (XXX is there anything else we really need to restore?)
*/
PushOverrideSearchPath(plansource->search_path);
/* MemoryContextSwitchTo(oldcxt);
* If a snapshot is already set (the normal case), we can just use
* that for parsing/planning. But if it isn't, install one. Note: no
* point in checking whether parse analysis requires a snapshot;
* utility commands don't have invalidatable plans, so we'd not get
* here for such a command.
*/
if (!ActiveSnapshotSet())
{
PushActiveSnapshot(GetTransactionSnapshot());
snapshot_set = true;
}
/* return plan;
* Run parse analysis and rule rewriting. The parser tends to }
* scribble on its input, so we must copy the raw parse tree to
* prevent corruption of the cache.
*/
rawtree = copyObject(plansource->raw_parse_tree);
if (plansource->parserSetup != NULL)
slist = pg_analyze_and_rewrite_params(rawtree,
plansource->query_string,
plansource->parserSetup,
plansource->parserSetupArg);
else
slist = pg_analyze_and_rewrite(rawtree,
plansource->query_string,
plansource->param_types,
plansource->num_params);
if (plansource->fully_planned) /*
{ * choose_custom_plan: choose whether to use custom or generic plan
/* *
* Generate plans for queries. * This defines the policy followed by GetCachedPlan.
* */
* The planner may try to call SPI-using functions, which causes a static bool
* problem if we're already inside one. Rather than expect all choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
* SPI-using code to do SPI_push whenever a replan could happen, {
* it seems best to take care of the case here. double avg_custom_cost;
*/
bool pushed;
pushed = SPI_push_conditional(); /* Never any point in a custom plan if there's no parameters */
if (boundParams == NULL)
return false;
slist = pg_plan_queries(slist, plansource->cursor_options, NULL); /* See if caller wants to force the decision */
if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN)
return false;
if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN)
return true;
SPI_pop_conditional(pushed); /* Generate custom plans until we have done at least 5 (arbitrary) */
} if (plansource->num_custom_plans < 5)
return true;
/* avg_custom_cost = plansource->total_custom_cost / plansource->num_custom_plans;
* Check or update the result tupdesc. XXX should we use a weaker
* condition than equalTupleDescs() here? /*
*/ * Prefer generic plan if it's less than 10% more expensive than average
resultDesc = PlanCacheComputeResultDesc(slist); * custom plan. This threshold is a bit arbitrary; it'd be better if we
if (resultDesc == NULL && plansource->resultDesc == NULL) * had some means of comparing planning time to the estimated runtime
* cost differential.
*
* Note that if generic_cost is -1 (indicating we've not yet determined
* the generic plan cost), we'll always prefer generic at this point.
*/
if (plansource->generic_cost < avg_custom_cost * 1.1)
return false;
return true;
}
/*
* cached_plan_cost: calculate estimated cost of a plan
*/
static double
cached_plan_cost(CachedPlan *plan)
{
double result = 0;
ListCell *lc;
foreach(lc, plan->stmt_list)
{
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
if (!IsA(plannedstmt, PlannedStmt))
continue; /* Ignore utility statements */
result += plannedstmt->planTree->total_cost;
}
return result;
}
/*
* GetCachedPlan: get a cached plan from a CachedPlanSource.
*
* This function hides the logic that decides whether to use a generic
* plan or a custom plan for the given parameters: the caller does not know
* which it will get.
*
* On return, the plan is valid and we have sufficient locks to begin
* execution.
*
* On return, the refcount of the plan has been incremented; a later
* ReleaseCachedPlan() call is expected. The refcount has been reported
* to the CurrentResourceOwner if useResOwner is true (note that that must
* only be true if it's a "saved" CachedPlanSource).
*
* Note: if any replanning activity is required, the caller's memory context
* is used for that work.
*/
CachedPlan *
GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
bool useResOwner)
{
CachedPlan *plan;
List *qlist;
bool customplan;
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/* This seems worth a real test, though */
if (useResOwner && !plansource->is_saved)
elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
/* Make sure the querytree list is valid and we have parse-time locks */
qlist = RevalidateCachedQuery(plansource);
/* Decide whether to use a custom plan */
customplan = choose_custom_plan(plansource, boundParams);
if (!customplan)
{
if (CheckCachedPlan(plansource))
{ {
/* OK, doesn't return tuples */ /* We want a generic plan, and we already have a valid one */
plan = plansource->gplan;
Assert(plan->magic == CACHEDPLAN_MAGIC);
} }
else if (resultDesc == NULL || plansource->resultDesc == NULL || else
!equalTupleDescs(resultDesc, plansource->resultDesc))
{ {
MemoryContext oldcxt; /* Build a new generic plan */
plan = BuildCachedPlan(plansource, qlist, NULL);
/* can we give a better error message? */ /* Just make real sure plansource->gplan is clear */
if (plansource->fixed_result) ReleaseGenericPlan(plansource);
ereport(ERROR, /* Link the new generic plan into the plansource */
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), plansource->gplan = plan;
errmsg("cached plan must not change result type"))); plan->refcount++;
oldcxt = MemoryContextSwitchTo(plansource->context); /* Immediately reparent into appropriate context */
if (resultDesc) if (plansource->is_saved)
resultDesc = CreateTupleDescCopy(resultDesc); {
if (plansource->resultDesc) /* saved plans all live under CacheMemoryContext */
FreeTupleDesc(plansource->resultDesc); MemoryContextSetParent(plan->context, CacheMemoryContext);
plansource->resultDesc = resultDesc; plan->is_saved = true;
MemoryContextSwitchTo(oldcxt); }
} else
{
/* Release snapshot if we got one */ /* otherwise, it should be a sibling of the plansource */
if (snapshot_set) MemoryContextSetParent(plan->context,
PopActiveSnapshot(); MemoryContextGetParent(plansource->context));
}
/* Now we can restore current search path */ /* Update generic_cost whenever we make a new generic plan */
PopOverrideSearchPath(); plansource->generic_cost = cached_plan_cost(plan);
/* /*
* Store the plans into the plancache entry, advancing the generation * If, based on the now-known value of generic_cost, we'd not have
* count. * chosen to use a generic plan, then forget it and make a custom
*/ * plan. This is a bit of a wart but is necessary to avoid a
StoreCachedPlan(plansource, slist, NULL); * glitch in behavior when the custom plans are consistently big
* winners; at some point we'll experiment with a generic plan and
* find it's a loser, but we don't want to actually execute that
* plan.
*/
customplan = choose_custom_plan(plansource, boundParams);
}
}
plan = plansource->plan; if (customplan)
{
/* Build a custom plan */
plan = BuildCachedPlan(plansource, qlist, boundParams);
/* Accumulate total costs of custom plans, but 'ware overflow */
if (plansource->num_custom_plans < INT_MAX)
{
plansource->total_custom_cost += cached_plan_cost(plan);
plansource->num_custom_plans++;
}
} }
/* /* Flag the plan as in use by caller */
* Last step: flag the plan as in use by caller.
*/
if (useResOwner) if (useResOwner)
ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner); ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
plan->refcount++; plan->refcount++;
if (useResOwner) if (useResOwner)
ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan); ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
/*
* Saved plans should be under CacheMemoryContext so they will not go away
* until their reference count goes to zero. In the generic-plan cases we
* already took care of that, but for a custom plan, do it as soon as we
* have created a reference-counted link.
*/
if (customplan && plansource->is_saved)
{
MemoryContextSetParent(plan->context, CacheMemoryContext);
plan->is_saved = true;
}
return plan; return plan;
} }
...@@ -635,8 +1019,12 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) ...@@ -635,8 +1019,12 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
void void
ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
{ {
Assert(plan->magic == CACHEDPLAN_MAGIC);
if (useResOwner) if (useResOwner)
{
Assert(plan->is_saved);
ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan); ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
}
Assert(plan->refcount > 0); Assert(plan->refcount > 0);
plan->refcount--; plan->refcount--;
if (plan->refcount == 0) if (plan->refcount == 0)
...@@ -644,8 +1032,125 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) ...@@ -644,8 +1032,125 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
} }
/* /*
* CachedPlanIsValid: test whether the plan within a CachedPlanSource is * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
* currently valid (that is, not marked as being in need of revalidation). *
* This can only be applied to unsaved plans; once saved, a plan always
* lives underneath CacheMemoryContext.
*/
void
CachedPlanSetParentContext(CachedPlanSource *plansource,
MemoryContext newcontext)
{
/* Assert caller is doing things in a sane order */
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
/* This seems worth a real test, though */
if (plansource->is_saved)
elog(ERROR, "cannot move a saved cached plan to another context");
/* OK, let the caller keep the plan where he wishes */
MemoryContextSetParent(plansource->context, newcontext);
/*
* The query_context needs no special handling, since it's a child of
* plansource->context. But if there's a generic plan, it should be
* maintained as a sibling of plansource->context.
*/
if (plansource->gplan)
{
Assert(plansource->gplan->magic == CACHEDPLAN_MAGIC);
MemoryContextSetParent(plansource->gplan->context, newcontext);
}
}
/*
* CopyCachedPlan: make a copy of a CachedPlanSource
*
* This is a convenience routine that does the equivalent of
* CreateCachedPlan + CompleteCachedPlan, using the data stored in the
* input CachedPlanSource. The result is therefore "unsaved" (regardless
* of the state of the source), and we don't copy any generic plan either.
* The result will be currently valid, or not, the same as the source.
*/
CachedPlanSource *
CopyCachedPlan(CachedPlanSource *plansource)
{
CachedPlanSource *newsource;
MemoryContext source_context;
MemoryContext querytree_context;
MemoryContext oldcxt;
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
Assert(plansource->is_complete);
source_context = AllocSetContextCreate(CurrentMemoryContext,
"CachedPlanSource",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
oldcxt = MemoryContextSwitchTo(source_context);
newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
newsource->magic = CACHEDPLANSOURCE_MAGIC;
newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
newsource->query_string = pstrdup(plansource->query_string);
newsource->commandTag = plansource->commandTag;
if (plansource->num_params > 0)
{
newsource->param_types = (Oid *)
palloc(plansource->num_params * sizeof(Oid));
memcpy(newsource->param_types, plansource->param_types,
plansource->num_params * sizeof(Oid));
}
else
newsource->param_types = NULL;
newsource->num_params = plansource->num_params;
newsource->parserSetup = plansource->parserSetup;
newsource->parserSetupArg = plansource->parserSetupArg;
newsource->cursor_options = plansource->cursor_options;
newsource->fixed_result = plansource->fixed_result;
if (plansource->resultDesc)
newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
else
newsource->resultDesc = NULL;
newsource->search_path = CopyOverrideSearchPath(plansource->search_path);
newsource->context = source_context;
querytree_context = AllocSetContextCreate(source_context,
"CachedPlanQuery",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(querytree_context);
newsource->query_list = (List *) copyObject(plansource->query_list);
newsource->relationOids = (List *) copyObject(plansource->relationOids);
newsource->invalItems = (List *) copyObject(plansource->invalItems);
newsource->query_context = querytree_context;
newsource->gplan = NULL;
newsource->is_complete = true;
newsource->is_saved = false;
newsource->is_valid = plansource->is_valid;
newsource->generation = plansource->generation;
newsource->next_saved = NULL;
/* We may as well copy any acquired cost knowledge */
newsource->generic_cost = plansource->generic_cost;
newsource->total_custom_cost = plansource->total_custom_cost;
newsource->num_custom_plans = plansource->num_custom_plans;
MemoryContextSwitchTo(oldcxt);
return newsource;
}
/*
* CachedPlanIsValid: test whether the rewritten querytree within a
* CachedPlanSource is currently valid (that is, not marked as being in need
* of revalidation).
* *
* This result is only trustworthy (ie, free from race conditions) if * This result is only trustworthy (ie, free from race conditions) if
* the caller has acquired locks on all the relations used in the plan. * the caller has acquired locks on all the relations used in the plan.
...@@ -653,37 +1158,44 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) ...@@ -653,37 +1158,44 @@ ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
bool bool
CachedPlanIsValid(CachedPlanSource *plansource) CachedPlanIsValid(CachedPlanSource *plansource)
{ {
CachedPlan *plan; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
return plansource->is_valid;
}
/* Validity check that we were given a CachedPlanSource */ /*
Assert(list_member_ptr(cached_plans_list, plansource)); * CachedPlanGetTargetList: return tlist, if any, describing plan's output
*
* The result is guaranteed up-to-date. However, it is local storage
* within the cached plan, and may disappear next time the plan is updated.
*/
List *
CachedPlanGetTargetList(CachedPlanSource *plansource)
{
Node *pstmt;
plan = plansource->plan; /* Assert caller is doing things in a sane order */
if (plan && !plan->dead) Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
{ Assert(plansource->is_complete);
/*
* Plan must have positive refcount because it is referenced by
* plansource; so no need to fear it disappears under us here.
*/
Assert(plan->refcount > 0);
/* /*
* Although we don't want to acquire locks here, it still seems useful * No work needed if statement doesn't return tuples (we assume this
* to check for expiration of a transient plan. * feature cannot be changed by an invalidation)
*/ */
if (TransactionIdIsValid(plan->saved_xmin) && if (plansource->resultDesc == NULL)
!TransactionIdEquals(plan->saved_xmin, TransactionXmin)) return NIL;
plan->dead = true;
else
return true;
}
return false; /* Make sure the querytree list is valid and we have parse-time locks */
RevalidateCachedQuery(plansource);
/* Get the primary statement and find out what it returns */
pstmt = PortalListGetPrimaryStmt(plansource->query_list);
return FetchStatementTargetList(pstmt);
} }
/* /*
* AcquireExecutorLocks: acquire locks needed for execution of a fully-planned * AcquireExecutorLocks: acquire locks needed for execution of a cached plan;
* cached plan; or release them if acquire is false. * or release them if acquire is false.
*/ */
static void static void
AcquireExecutorLocks(List *stmt_list, bool acquire) AcquireExecutorLocks(List *stmt_list, bool acquire)
...@@ -752,8 +1264,8 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) ...@@ -752,8 +1264,8 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
} }
/* /*
* AcquirePlannerLocks: acquire locks needed for planning and execution of a * AcquirePlannerLocks: acquire locks needed for planning of a querytree list;
* not-fully-planned cached plan; or release them if acquire is false. * or release them if acquire is false.
* *
* Note that we don't actually try to open the relations, and hence will not * Note that we don't actually try to open the relations, and hence will not
* fail if one has been dropped entirely --- we'll just transiently acquire * fail if one has been dropped entirely --- we'll just transiently acquire
...@@ -903,64 +1415,36 @@ plan_list_is_transient(List *stmt_list) ...@@ -903,64 +1415,36 @@ plan_list_is_transient(List *stmt_list)
} }
/* /*
* PlanCacheComputeResultDesc: given a list of either fully-planned statements * PlanCacheComputeResultDesc: given a list of analyzed-and-rewritten Queries,
* or Queries, determine the result tupledesc it will produce. Returns NULL * determine the result tupledesc it will produce. Returns NULL if the
* if the execution will not return tuples. * execution will not return tuples.
* *
* Note: the result is created or copied into current memory context. * Note: the result is created or copied into current memory context.
*/ */
TupleDesc static TupleDesc
PlanCacheComputeResultDesc(List *stmt_list) PlanCacheComputeResultDesc(List *stmt_list)
{ {
Node *node;
Query *query; Query *query;
PlannedStmt *pstmt;
switch (ChoosePortalStrategy(stmt_list)) switch (ChoosePortalStrategy(stmt_list))
{ {
case PORTAL_ONE_SELECT: case PORTAL_ONE_SELECT:
case PORTAL_ONE_MOD_WITH: case PORTAL_ONE_MOD_WITH:
node = (Node *) linitial(stmt_list); query = (Query *) linitial(stmt_list);
if (IsA(node, Query)) Assert(IsA(query, Query));
{ return ExecCleanTypeFromTL(query->targetList, false);
query = (Query *) node;
return ExecCleanTypeFromTL(query->targetList, false);
}
if (IsA(node, PlannedStmt))
{
pstmt = (PlannedStmt *) node;
return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
}
/* other cases shouldn't happen, but return NULL */
break;
case PORTAL_ONE_RETURNING: case PORTAL_ONE_RETURNING:
node = PortalListGetPrimaryStmt(stmt_list); query = (Query *) PortalListGetPrimaryStmt(stmt_list);
if (IsA(node, Query)) Assert(IsA(query, Query));
{ Assert(query->returningList);
query = (Query *) node; return ExecCleanTypeFromTL(query->returningList, false);
Assert(query->returningList);
return ExecCleanTypeFromTL(query->returningList, false);
}
if (IsA(node, PlannedStmt))
{
pstmt = (PlannedStmt *) node;
Assert(pstmt->hasReturning);
return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
}
/* other cases shouldn't happen, but return NULL */
break;
case PORTAL_UTIL_SELECT: case PORTAL_UTIL_SELECT:
node = (Node *) linitial(stmt_list); query = (Query *) linitial(stmt_list);
if (IsA(node, Query)) Assert(IsA(query, Query));
{ Assert(query->utilityStmt);
query = (Query *) node; return UtilityTupleDescriptor(query->utilityStmt);
Assert(query->utilityStmt);
return UtilityTupleDescriptor(query->utilityStmt);
}
/* else it's a bare utility statement */
return UtilityTupleDescriptor(node);
case PORTAL_MULTI_QUERY: case PORTAL_MULTI_QUERY:
/* will not return tuples */ /* will not return tuples */
...@@ -979,33 +1463,39 @@ PlanCacheComputeResultDesc(List *stmt_list) ...@@ -979,33 +1463,39 @@ PlanCacheComputeResultDesc(List *stmt_list)
static void static void
PlanCacheRelCallback(Datum arg, Oid relid) PlanCacheRelCallback(Datum arg, Oid relid)
{ {
ListCell *lc1; CachedPlanSource *plansource;
foreach(lc1, cached_plans_list) for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{ {
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
CachedPlan *plan = plansource->plan;
/* No work if it's already invalidated */ /* No work if it's already invalidated */
if (!plan || plan->dead) if (!plansource->is_valid)
continue; continue;
/* /*
* Check the list we built ourselves; this covers unplanned cases * Check the dependency list for the rewritten querytree.
* including EXPLAIN.
*/ */
if ((relid == InvalidOid) ? plan->relationOids != NIL : if ((relid == InvalidOid) ? plansource->relationOids != NIL :
list_member_oid(plan->relationOids, relid)) list_member_oid(plansource->relationOids, relid))
plan->dead = true; {
/* Invalidate the querytree and generic plan */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
}
if (plan->fully_planned && !plan->dead) /*
* The generic plan, if any, could have more dependencies than the
* querytree does, so we have to check it too.
*/
if (plansource->gplan && plansource->gplan->is_valid)
{ {
/* Have to check the per-PlannedStmt relid lists */ ListCell *lc;
ListCell *lc2;
foreach(lc2, plan->stmt_list) foreach(lc, plansource->gplan->stmt_list)
{ {
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
Assert(!IsA(plannedstmt, Query)); Assert(!IsA(plannedstmt, Query));
if (!IsA(plannedstmt, PlannedStmt)) if (!IsA(plannedstmt, PlannedStmt))
...@@ -1013,8 +1503,8 @@ PlanCacheRelCallback(Datum arg, Oid relid) ...@@ -1013,8 +1503,8 @@ PlanCacheRelCallback(Datum arg, Oid relid)
if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL : if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
list_member_oid(plannedstmt->relationOids, relid)) list_member_oid(plannedstmt->relationOids, relid))
{ {
/* Invalidate the plan! */ /* Invalidate the generic plan only */
plan->dead = true; plansource->gplan->is_valid = false;
break; /* out of stmt_list scan */ break; /* out of stmt_list scan */
} }
} }
...@@ -1035,43 +1525,47 @@ PlanCacheRelCallback(Datum arg, Oid relid) ...@@ -1035,43 +1525,47 @@ PlanCacheRelCallback(Datum arg, Oid relid)
static void static void
PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
{ {
ListCell *lc1; CachedPlanSource *plansource;
foreach(lc1, cached_plans_list) for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{ {
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); ListCell *lc;
CachedPlan *plan = plansource->plan;
ListCell *lc2; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */ /* No work if it's already invalidated */
if (!plan || plan->dead) if (!plansource->is_valid)
continue; continue;
/* /*
* Check the list we built ourselves; this covers unplanned cases * Check the dependency list for the rewritten querytree.
* including EXPLAIN.
*/ */
foreach(lc2, plan->invalItems) foreach(lc, plansource->invalItems)
{ {
PlanInvalItem *item = (PlanInvalItem *) lfirst(lc2); PlanInvalItem *item = (PlanInvalItem *) lfirst(lc);
if (item->cacheId != cacheid) if (item->cacheId != cacheid)
continue; continue;
if (hashvalue == 0 || if (hashvalue == 0 ||
item->hashValue == hashvalue) item->hashValue == hashvalue)
{ {
/* Invalidate the plan! */ /* Invalidate the querytree and generic plan */
plan->dead = true; plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
break; break;
} }
} }
if (plan->fully_planned && !plan->dead) /*
* The generic plan, if any, could have more dependencies than the
* querytree does, so we have to check it too.
*/
if (plansource->gplan && plansource->gplan->is_valid)
{ {
/* Have to check the per-PlannedStmt inval-item lists */ foreach(lc, plansource->gplan->stmt_list)
foreach(lc2, plan->stmt_list)
{ {
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc);
ListCell *lc3; ListCell *lc3;
Assert(!IsA(plannedstmt, Query)); Assert(!IsA(plannedstmt, Query));
...@@ -1086,12 +1580,12 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue) ...@@ -1086,12 +1580,12 @@ PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue)
if (hashvalue == 0 || if (hashvalue == 0 ||
item->hashValue == hashvalue) item->hashValue == hashvalue)
{ {
/* Invalidate the plan! */ /* Invalidate the generic plan only */
plan->dead = true; plansource->gplan->is_valid = false;
break; /* out of invalItems scan */ break; /* out of invalItems scan */
} }
} }
if (plan->dead) if (!plansource->gplan->is_valid)
break; /* out of stmt_list scan */ break; /* out of stmt_list scan */
} }
} }
...@@ -1111,65 +1605,46 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue) ...@@ -1111,65 +1605,46 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
} }
/* /*
* ResetPlanCache: drop all cached plans. * ResetPlanCache: invalidate all cached plans.
*/ */
void void
ResetPlanCache(void) ResetPlanCache(void)
{ {
ListCell *lc1; CachedPlanSource *plansource;
foreach(lc1, cached_plans_list) for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
{ {
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); ListCell *lc;
CachedPlan *plan = plansource->plan;
ListCell *lc2; Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
/* No work if it's already invalidated */ /* No work if it's already invalidated */
if (!plan || plan->dead) if (!plansource->is_valid)
continue; continue;
/* /*
* We *must not* mark transaction control statements as dead, * We *must not* mark transaction control statements as invalid,
* particularly not ROLLBACK, because they may need to be executed in * particularly not ROLLBACK, because they may need to be executed in
* aborted transactions when we can't revalidate them (cf bug #5269). * aborted transactions when we can't revalidate them (cf bug #5269).
* In general there is no point in invalidating utility statements * In general there is no point in invalidating utility statements
* since they have no plans anyway. So mark it dead only if it * since they have no plans anyway. So invalidate it only if it
* contains at least one non-utility statement. (EXPLAIN counts as a * contains at least one non-utility statement. (EXPLAIN counts as a
* non-utility statement, though, since it contains an analyzed query * non-utility statement, though, since it contains an analyzed query
* that might have dependencies.) * that might have dependencies.)
*/ */
if (plan->fully_planned) foreach(lc, plansource->query_list)
{ {
/* Search statement list for non-utility statements */ Query *query = (Query *) lfirst(lc);
foreach(lc2, plan->stmt_list)
{
PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
Assert(!IsA(plannedstmt, Query)); Assert(IsA(query, Query));
if (IsA(plannedstmt, PlannedStmt) || if (query->commandType != CMD_UTILITY ||
IsA(plannedstmt, ExplainStmt)) IsA(query->utilityStmt, ExplainStmt))
{
/* non-utility statement, so invalidate */
plan->dead = true;
break; /* out of stmt_list scan */
}
}
}
else
{
/* Search Query list for non-utility statements */
foreach(lc2, plan->stmt_list)
{ {
Query *query = (Query *) lfirst(lc2); /* non-utility statement, so invalidate */
plansource->is_valid = false;
Assert(IsA(query, Query)); if (plansource->gplan)
if (query->commandType != CMD_UTILITY || plansource->gplan->is_valid = false;
IsA(query->utilityStmt, ExplainStmt)) break;
{
/* non-utility statement, so invalidate */
plan->dead = true;
break; /* out of stmt_list scan */
}
} }
} }
} }
......
...@@ -342,6 +342,18 @@ GetMemoryChunkContext(void *pointer) ...@@ -342,6 +342,18 @@ GetMemoryChunkContext(void *pointer)
return header->context; 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 * MemoryContextIsEmpty
* Is a memory context empty of any allocated space? * Is a memory context empty of any allocated space?
......
...@@ -280,9 +280,9 @@ CreateNewPortal(void) ...@@ -280,9 +280,9 @@ CreateNewPortal(void)
* (before rewriting) was an empty string. Also, the passed commandTag must * (before rewriting) was an empty string. Also, the passed commandTag must
* be a pointer to a constant string, since it is not copied. * 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, * If cplan is provided, then it is a cached plan containing the stmts, and
* and the caller must have done RevalidateCachedPlan(), causing a refcount * the caller must have done GetCachedPlan(), causing a refcount increment.
* increment. The refcount will be released when the portal is destroyed. * The refcount will be released when the portal is destroyed.
* *
* If cplan is NULL, then it is the caller's responsibility to ensure that * 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 * the passed plan trees have adequate lifetime. Typically this is done by
......
...@@ -118,6 +118,7 @@ extern Oid GetTempToastNamespace(void); ...@@ -118,6 +118,7 @@ extern Oid GetTempToastNamespace(void);
extern void ResetTempTableNamespace(void); extern void ResetTempTableNamespace(void);
extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context); extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context);
extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path);
extern void PushOverrideSearchPath(OverrideSearchPath *newpath); extern void PushOverrideSearchPath(OverrideSearchPath *newpath);
extern void PopOverrideSearchPath(void); extern void PopOverrideSearchPath(void);
......
...@@ -44,13 +44,7 @@ extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, ...@@ -44,13 +44,7 @@ extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
/* Low-level access to stored prepared statements */ /* Low-level access to stored prepared statements */
extern void StorePreparedStatement(const char *stmt_name, extern void StorePreparedStatement(const char *stmt_name,
Node *raw_parse_tree, CachedPlanSource *plansource,
const char *query_string,
const char *commandTag,
Oid *param_types,
int num_params,
int cursor_options,
List *stmt_list,
bool from_sql); bool from_sql);
extern PreparedStatement *FetchPreparedStatement(const char *stmt_name, extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
bool throwError); bool throwError);
......
...@@ -93,6 +93,7 @@ extern SPIPlanPtr SPI_prepare_params(const char *src, ...@@ -93,6 +93,7 @@ extern SPIPlanPtr SPI_prepare_params(const char *src,
ParserSetupHook parserSetup, ParserSetupHook parserSetup,
void *parserSetupArg, void *parserSetupArg,
int cursorOptions); int cursorOptions);
extern int SPI_keepplan(SPIPlanPtr plan);
extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan); extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
extern int SPI_freeplan(SPIPlanPtr plan); extern int SPI_freeplan(SPIPlanPtr plan);
......
...@@ -32,27 +32,32 @@ typedef struct ...@@ -32,27 +32,32 @@ typedef struct
} _SPI_connection; } _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 * Ordinarily, the _SPI_plan struct itself as well as the argtypes array
* a dedicated memory context identified by plancxt. An unsaved plan is good * are in a dedicated memory context identified by plancxt (which can be
* at most for the current transaction, since the locks that protect it from * really small). All the other subsidiary state is in plancache entries
* schema changes will be lost at end of transaction. Hence the plancxt is * identified by plancache_list (note: the list cells themselves are in
* always a transient one. * plancxt).
* *
* For a saved plan, the _SPI_plan struct and the argument type array are in * In an unsaved plan, the plancxt as well as the plancache entries' contexts
* the plancxt (which can be really small). All the other subsidiary state * are children of the SPI procedure context, so they'll all disappear at
* is in plancache entries identified by plancache_list (note: the list cells * function exit. plancache.c also knows that the plancache entries are
* themselves are in plancxt). We rely on plancache.c to keep the cache * "unsaved", so it doesn't link them into its global list; hence they do
* entries up-to-date as needed. The plancxt is a child of CacheMemoryContext * not respond to inval events. This is OK since we are presumably holding
* since it should persist until explicitly destroyed. * adequate locks to prevent other backends from messing with the tables.
* *
* To avoid redundant coding, the representation of unsaved plans matches * For a saved plan, the plancxt is made a child of CacheMemoryContext
* that of saved plans, ie, plancache_list is a list of CachedPlanSource * since it should persist until explicitly destroyed. Likewise, the
* structs which in turn point to CachedPlan structs. However, in an unsaved * plancache entries will be under CacheMemoryContext since we tell
* plan all these structs are just created by spi.c and are not known to * plancache.c to save them. We rely on plancache.c to keep the cache
* plancache.c. We don't try very hard to make all their fields valid, * entries up-to-date as needed in the face of invalidation events.
* only the ones spi.c actually uses. *
* 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, * 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 * the plancache_list will be NIL and so there is no place to store the
......
...@@ -1996,7 +1996,10 @@ typedef struct SecLabelStmt ...@@ -1996,7 +1996,10 @@ typedef struct SecLabelStmt
#define CURSOR_OPT_NO_SCROLL 0x0004 /* NO SCROLL explicitly given */ #define CURSOR_OPT_NO_SCROLL 0x0004 /* NO SCROLL explicitly given */
#define CURSOR_OPT_INSENSITIVE 0x0008 /* INSENSITIVE */ #define CURSOR_OPT_INSENSITIVE 0x0008 /* INSENSITIVE */
#define CURSOR_OPT_HOLD 0x0010 /* WITH HOLD */ #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_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 typedef struct DeclareCursorStmt
{ {
......
...@@ -94,6 +94,7 @@ extern void MemoryContextSetParent(MemoryContext context, ...@@ -94,6 +94,7 @@ extern void MemoryContextSetParent(MemoryContext context,
MemoryContext new_parent); MemoryContext new_parent);
extern Size GetMemoryChunkSpace(void *pointer); extern Size GetMemoryChunkSpace(void *pointer);
extern MemoryContext GetMemoryChunkContext(void *pointer); extern MemoryContext GetMemoryChunkContext(void *pointer);
extern MemoryContext MemoryContextGetParent(MemoryContext context);
extern bool MemoryContextIsEmpty(MemoryContext context); extern bool MemoryContextIsEmpty(MemoryContext context);
extern void MemoryContextStats(MemoryContext context); extern void MemoryContextStats(MemoryContext context);
......
...@@ -18,26 +18,47 @@ ...@@ -18,26 +18,47 @@
#include "access/tupdesc.h" #include "access/tupdesc.h"
#include "nodes/params.h" #include "nodes/params.h"
#define CACHEDPLANSOURCE_MAGIC 195726186
#define CACHEDPLAN_MAGIC 953717834
/* /*
* CachedPlanSource represents the portion of a cached plan that persists * CachedPlanSource (which might better have been called CachedQuery)
* across invalidation/replan cycles. It stores a raw parse tree (required), * represents a SQL query that we expect to use multiple times. It stores
* the original source text (also required, as of 8.4), and adjunct data. * the query source text, the raw parse tree, and the analyzed-and-rewritten
* query tree, as well as adjunct data. Cache invalidation can happen as a
* result of DDL affecting objects used by the query. In that case we discard
* the analyzed-and-rewritten query tree, and rebuild it when next needed.
*
* An actual execution plan, represented by CachedPlan, is derived from the
* CachedPlanSource when we need to execute the query. The plan could be
* either generic (usable with any set of plan parameters) or custom (for a
* specific set of parameters). plancache.c contains the logic that decides
* which way to do it for any particular execution. If we are using a generic
* cached plan then it is meant to be re-used across multiple executions, so
* callers must always treat CachedPlans as read-only.
*
* Once successfully built and "saved", CachedPlanSources typically live
* for the life of the backend, although they can be dropped explicitly.
* CachedPlans are reference-counted and go away automatically when the last
* reference is dropped. A CachedPlan can outlive the CachedPlanSource it
* was created from.
* *
* Normally, both the struct itself and the subsidiary data live in the * An "unsaved" CachedPlanSource can be used for generating plans, but it
* context denoted by the context field, while the linked-to CachedPlan, if * lives in transient storage and will not be updated in response to sinval
* any, has its own context. Thus an invalidated CachedPlan can be dropped * events.
* when no longer needed, and conversely a CachedPlanSource can be dropped
* without worrying whether any portals depend on particular instances of
* its plan.
* *
* But for entries created by FastCreateCachedPlan, the CachedPlanSource * CachedPlans made from saved CachedPlanSources are likewise in permanent
* and the initial version of the CachedPlan share the same memory context. * storage, so to avoid memory leaks, the reference-counted references to them
* In this case, we treat the memory context as belonging to the CachedPlan. * must be held in permanent data structures or ResourceOwners. CachedPlans
* The CachedPlanSource has an extra reference-counted link (orig_plan) * made from unsaved CachedPlanSources are in children of the caller's
* to the CachedPlan, and the memory context goes away when the CachedPlan's * memory context, so references to them should not be longer-lived than
* reference count goes to zero. This arrangement saves overhead for plans * that context. (Reference counting is somewhat pro forma in that case,
* that aren't expected to live long enough to need replanning, while not * though it may be useful if the CachedPlan can be discarded early.)
* losing any flexibility if a replan turns out to be necessary. *
* 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; * Note: the string referenced by commandTag is not subsidiary storage;
* it is assumed to be a compile-time-constant string. As with portals, * it is assumed to be a compile-time-constant string. As with portals,
...@@ -46,78 +67,93 @@ ...@@ -46,78 +67,93 @@
*/ */
typedef struct CachedPlanSource typedef struct CachedPlanSource
{ {
int magic; /* should equal CACHEDPLANSOURCE_MAGIC */
Node *raw_parse_tree; /* output of raw_parser() */ 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 */ const char *commandTag; /* command tag (a constant!), or NULL */
Oid *param_types; /* array of parameter type OIDs, or NULL */ Oid *param_types; /* array of parameter type OIDs, or NULL */
int num_params; /* length of param_types array */ int num_params; /* length of param_types array */
ParserSetupHook parserSetup; /* alternative parameter spec method */ ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg; void *parserSetupArg;
int cursor_options; /* cursor options used for planning */ 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? */ 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 */ TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */
struct CachedPlan *plan; /* link to plan, or NULL if not valid */ struct OverrideSearchPath *search_path; /* saved search_path */
MemoryContext context; /* context containing this CachedPlanSource */ MemoryContext context; /* memory context holding all above */
struct CachedPlan *orig_plan; /* link to plan owning my context */ /* 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; } CachedPlanSource;
/* /*
* CachedPlan represents the portion of a cached plan that is discarded when * CachedPlan represents an execution plan derived from a CachedPlanSource.
* invalidation occurs. The reference count includes both the link(s) from the * The reference count includes both the link from the parent CachedPlanSource
* parent CachedPlanSource, and any active plan executions, so the plan can be * (if any), and any active plan executions, so the plan can be discarded
* discarded exactly when refcount goes to zero. Both the struct itself and * exactly when refcount goes to zero. Both the struct itself and the
* the subsidiary data live in the context denoted by the context field. * subsidiary data live in the context denoted by the context field.
* This makes it easy to free a no-longer-needed cached plan. * This makes it easy to free a no-longer-needed cached plan.
*/ */
typedef struct CachedPlan typedef struct CachedPlan
{ {
List *stmt_list; /* list of statement or Query nodes */ int magic; /* should equal CACHEDPLAN_MAGIC */
bool fully_planned; /* do we cache planner or rewriter output? */ List *stmt_list; /* list of statement nodes (PlannedStmts
bool dead; /* if true, do not use */ * 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 TransactionId saved_xmin; /* if valid, replan when TransactionXmin
* changes from this value */ * changes from this value */
int generation; /* parent's generation number for this plan */
int refcount; /* count of live references to this struct */ int refcount; /* count of live references to this struct */
int generation; /* counter, starting at 1, for replans */
MemoryContext context; /* context containing this CachedPlan */ 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; } CachedPlan;
extern void InitPlanCache(void); extern void InitPlanCache(void);
extern void ResetPlanCache(void);
extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
const char *query_string, const char *query_string,
const char *commandTag, const char *commandTag);
Oid *param_types, extern void CompleteCachedPlan(CachedPlanSource *plansource,
int num_params, List *querytree_list,
int cursor_options, MemoryContext querytree_context,
List *stmt_list, Oid *param_types,
bool fully_planned, int num_params,
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, ParserSetupHook parserSetup,
void *parserSetupArg); void *parserSetupArg,
int cursor_options,
bool fixed_result);
extern void SaveCachedPlan(CachedPlanSource *plansource);
extern void DropCachedPlan(CachedPlanSource *plansource); extern void DropCachedPlan(CachedPlanSource *plansource);
extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
bool useResOwner); extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); MemoryContext newcontext);
extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource);
extern bool CachedPlanIsValid(CachedPlanSource *plansource); extern bool CachedPlanIsValid(CachedPlanSource *plansource);
extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
extern void ResetPlanCache(void); extern List *CachedPlanGetTargetList(CachedPlanSource *plansource);
extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
ParamListInfo boundParams,
bool useResOwner);
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
#endif /* PLANCACHE_H */ #endif /* PLANCACHE_H */
...@@ -165,7 +165,7 @@ typedef struct plperl_call_data ...@@ -165,7 +165,7 @@ typedef struct plperl_call_data
typedef struct plperl_query_desc typedef struct plperl_query_desc
{ {
char qname[24]; char qname[24];
void *plan; SPIPlanPtr plan;
int nargs; int nargs;
Oid *argtypes; Oid *argtypes;
FmgrInfo *arginfuncs; FmgrInfo *arginfuncs;
...@@ -2951,7 +2951,7 @@ plperl_spi_query(char *query) ...@@ -2951,7 +2951,7 @@ plperl_spi_query(char *query)
PG_TRY(); PG_TRY();
{ {
void *plan; SPIPlanPtr plan;
Portal portal; Portal portal;
/* Make sure the query is validly encoded */ /* Make sure the query is validly encoded */
...@@ -3118,7 +3118,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -3118,7 +3118,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
plperl_query_desc *qdesc; plperl_query_desc *qdesc;
plperl_query_entry *hash_entry; plperl_query_entry *hash_entry;
bool found; bool found;
void *plan; SPIPlanPtr plan;
int i; int i;
MemoryContext oldcontext = CurrentMemoryContext; MemoryContext oldcontext = CurrentMemoryContext;
...@@ -3182,13 +3182,9 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -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 * Save the plan into permanent memory (right now it's in the
* SPI procCxt, which will go away at function end). * SPI procCxt, which will go away at function end).
************************************************************/ ************************************************************/
qdesc->plan = SPI_saveplan(plan); if (SPI_keepplan(plan))
if (qdesc->plan == NULL) elog(ERROR, "SPI_keepplan() failed");
elog(ERROR, "SPI_saveplan() failed: %s", qdesc->plan = plan;
SPI_result_code_string(SPI_result));
/* Release the procCxt copy to avoid within-function memory leak */
SPI_freeplan(plan);
/* Commit the inner transaction, return to outer xact context */ /* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction(); ReleaseCurrentSubTransaction();
...@@ -3516,7 +3512,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv) ...@@ -3516,7 +3512,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
void void
plperl_spi_freeplan(char *query) plperl_spi_freeplan(char *query)
{ {
void *plan; SPIPlanPtr plan;
plperl_query_desc *qdesc; plperl_query_desc *qdesc;
plperl_query_entry *hash_entry; plperl_query_entry *hash_entry;
......
...@@ -142,6 +142,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate, ...@@ -142,6 +142,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions); PLpgSQL_expr *expr, int cursorOptions);
static bool exec_simple_check_node(Node *node); static bool exec_simple_check_node(Node *node);
static void exec_simple_check_plan(PLpgSQL_expr *expr); 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, static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, PLpgSQL_expr *expr,
Datum *result, Datum *result,
...@@ -2020,8 +2021,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt) ...@@ -2020,8 +2021,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
exec_prepare_plan(estate, query, curvar->cursor_options); exec_prepare_plan(estate, query, curvar->cursor_options);
/* /*
* Set up ParamListInfo (note this is only carrying a hook function, not * Set up ParamListInfo (hook function and possibly data values)
* any actual data values, at this point)
*/ */
paramLI = setup_param_list(estate, query); paramLI = setup_param_list(estate, query);
...@@ -2991,8 +2991,10 @@ exec_prepare_plan(PLpgSQL_execstate *estate, ...@@ -2991,8 +2991,10 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
expr->query, SPI_result_code_string(SPI_result)); expr->query, SPI_result_code_string(SPI_result));
} }
} }
expr->plan = SPI_saveplan(plan); SPI_keepplan(plan);
SPI_freeplan(plan); expr->plan = plan;
/* Check to see if it's a simple expression */
exec_simple_check_plan(expr); exec_simple_check_plan(expr);
} }
...@@ -3010,6 +3012,11 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, ...@@ -3010,6 +3012,11 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
int rc; int rc;
PLpgSQL_expr *expr = stmt->sqlstmt; 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 * On the first call for this statement generate the plan, and detect
* whether the statement is INSERT/UPDATE/DELETE * whether the statement is INSERT/UPDATE/DELETE
...@@ -3025,28 +3032,23 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, ...@@ -3025,28 +3032,23 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l); CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
ListCell *l2; 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) && Assert(IsA(q, Query));
p->canSetTag) if (q->canSetTag)
{ {
if (p->commandType == CMD_INSERT || if (q->commandType == CMD_INSERT ||
p->commandType == CMD_UPDATE || q->commandType == CMD_UPDATE ||
p->commandType == CMD_DELETE) q->commandType == CMD_DELETE)
stmt->mod_stmt = true; 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 * 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 * 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) ...@@ -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 * Set up ParamListInfo (hook function and possibly data values)
* any actual data values, at this point)
*/ */
paramLI = setup_param_list(estate, query); paramLI = setup_param_list(estate, query);
...@@ -4613,8 +4614,7 @@ exec_run_select(PLpgSQL_execstate *estate, ...@@ -4613,8 +4614,7 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr, 0); exec_prepare_plan(estate, expr, 0);
/* /*
* Set up ParamListInfo (note this is only carrying a hook function, not * Set up ParamListInfo (hook function and possibly data values)
* any actual data values, at this point)
*/ */
paramLI = setup_param_list(estate, expr); paramLI = setup_param_list(estate, expr);
...@@ -4833,11 +4833,10 @@ loop_exit: ...@@ -4833,11 +4833,10 @@ loop_exit:
* *
* It is possible though unlikely for a simple expression to become non-simple * 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 * (consider for example redefining a trivial view). We must handle that for
* correctness; fortunately it's normally inexpensive to do * correctness; fortunately it's normally inexpensive to do GetCachedPlan on a
* RevalidateCachedPlan on a simple expression. We do not consider the other * simple expression. We do not consider the other direction (non-simple
* direction (non-simple expression becoming simple) because we'll still give * expression becoming simple) because we'll still give correct results if
* correct results if that happens, and it's unlikely to be worth the cycles * that happens, and it's unlikely to be worth the cycles to check.
* to check.
* *
* Note: if pass-by-reference, the result is in the eval_econtext's * Note: if pass-by-reference, the result is in the eval_econtext's
* temporary memory context. It will be freed when exec_eval_cleanup * temporary memory context. It will be freed when exec_eval_cleanup
...@@ -4873,17 +4872,21 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -4873,17 +4872,21 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/* /*
* Revalidate cached plan, so that we will notice if it became stale. (We * 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 * need to hold a refcount while using the plan, anyway.) Note that even
* replanning occurs, the length of plancache_list can't change, since it * if replanning occurs, the length of plancache_list can't change, since
* is a property of the raw parsetree generated from the query text. * it is a property of the raw parsetree generated from the query text.
*/ */
Assert(list_length(expr->plan->plancache_list) == 1); Assert(list_length(expr->plan->plancache_list) == 1);
plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list); 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) if (cplan->generation != expr->expr_simple_generation)
{ {
/* It got replanned ... is it still simple? */ /* It got replanned ... is it still simple? */
exec_simple_check_plan(expr); exec_simple_recheck_plan(expr, cplan);
if (expr->expr_simple_expr == NULL) if (expr->expr_simple_expr == NULL)
{ {
/* Ooops, release refcount and fail */ /* Ooops, release refcount and fail */
...@@ -4900,7 +4903,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -4900,7 +4903,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/* /*
* Prepare the expression for execution, if it's not been done already in * 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 * 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) if (expr->expr_simple_lxid != curlxid)
{ {
...@@ -4931,9 +4934,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -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 * need to free it explicitly, since it will go away at the next reset of
* that context. * 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 * Just for paranoia's sake, save and restore the prior value of
* estate->cur_expr, which setup_param_list() sets. * estate->cur_expr, which setup_param_list() sets.
*/ */
...@@ -4982,10 +4982,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, ...@@ -4982,10 +4982,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/* /*
* Create a ParamListInfo to pass to SPI * Create a ParamListInfo to pass to SPI
* *
* The ParamListInfo array is initially all zeroes, in particular the * We fill in the values for any expression parameters that are plain
* ptype values are all InvalidOid. This causes the executor to call the * PLpgSQL_var datums; these are cheap and safe to evaluate, and by setting
* paramFetch hook each time it wants a value. We thus evaluate only the * them with PARAM_FLAG_CONST flags, we allow the planner to use those values
* parameters actually demanded. * 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; * The result is a locally palloc'd array that should be pfree'd after use;
* but note it can be NULL. * but note it can be NULL.
...@@ -4997,21 +5002,42 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) ...@@ -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? * 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 * However, we'd have to re-fill the array each time anyway, since new
* might have been assigned to the variables. * values might have been assigned to the variables.
*/ */
if (estate->ndatums > 0) if (estate->ndatums > 0)
{ {
/* sizeof(ParamListInfoData) includes the first array element */ Bitmapset *tmpset;
int dno;
paramLI = (ParamListInfo) paramLI = (ParamListInfo)
palloc0(sizeof(ParamListInfoData) + palloc0(offsetof(ParamListInfoData, params) +
(estate->ndatums - 1) * sizeof(ParamExternData)); estate->ndatums * sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch; paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate; paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup; paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
paramLI->parserSetupArg = (void *) expr; paramLI->parserSetupArg = (void *) expr;
paramLI->numParams = estate->ndatums; 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. * 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 * Callers must save and restore cur_expr if there is any chance that
...@@ -5628,30 +5654,113 @@ static void ...@@ -5628,30 +5654,113 @@ static void
exec_simple_check_plan(PLpgSQL_expr *expr) exec_simple_check_plan(PLpgSQL_expr *expr)
{ {
CachedPlanSource *plansource; CachedPlanSource *plansource;
PlannedStmt *stmt; Query *query;
Plan *plan; CachedPlan *cplan;
TargetEntry *tle;
/* /*
* Initialize to "not simple", and remember the plan generation number we * 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.) * we just leave expr_simple_generation set to 0.)
*/ */
expr->expr_simple_expr = NULL; expr->expr_simple_expr = NULL;
expr->expr_simple_generation = 0; expr->expr_simple_generation = 0;
/* /*
* 1. We can only evaluate queries that resulted in one single execution * We can only test queries that resulted in exactly one CachedPlanSource
* plan
*/ */
if (list_length(expr->plan->plancache_list) != 1) if (list_length(expr->plan->plancache_list) != 1)
return; return;
plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list); 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; return;
query = (Query *) linitial(plansource->query_list);
stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list); /*
* 2. It must be a plain SELECT query without any input tables
*/
if (!IsA(query, Query))
return;
if (query->commandType != CMD_SELECT || query->intoClause)
return;
if (query->rtable != NIL)
return;
/*
* 3. Can't have any subplans, aggregates, qual clauses either
*/
if (query->hasAggs ||
query->hasWindowFuncs ||
query->hasSubLinks ||
query->hasForUpdate ||
query->cteList ||
query->jointree->quals ||
query->groupClause ||
query->havingQual ||
query->windowClause ||
query->distinctClause ||
query->sortClause ||
query->limitOffset ||
query->limitCount ||
query->setOperations)
return;
/*
* 4. The query must have a single attribute as result
*/
if (list_length(query->targetList) != 1)
return;
/*
* OK, it seems worth constructing a plan for more careful checking.
*/
/* Get the generic plan for the query */
cplan = GetCachedPlan(plansource, NULL, true);
Assert(cplan == plansource->gplan);
/* Share the remaining work with recheck code path */
exec_simple_recheck_plan(expr, cplan);
/* Release our plan refcount */
ReleaseCachedPlan(cplan, true);
}
/*
* exec_simple_recheck_plan --- check for simple plan once we have CachedPlan
*/
static void
exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
{
PlannedStmt *stmt;
Plan *plan;
TargetEntry *tle;
/*
* Initialize to "not simple", and remember the plan generation number we
* last checked.
*/
expr->expr_simple_expr = NULL;
expr->expr_simple_generation = cplan->generation;
/*
* 1. There must be one single plantree
*/
if (list_length(cplan->stmt_list) != 1)
return;
stmt = (PlannedStmt *) linitial(cplan->stmt_list);
/* /*
* 2. It must be a RESULT plan --> no scan's required * 2. It must be a RESULT plan --> no scan's required
......
...@@ -287,7 +287,7 @@ typedef struct PLySubtransactionData ...@@ -287,7 +287,7 @@ typedef struct PLySubtransactionData
typedef struct PLyPlanObject typedef struct PLyPlanObject
{ {
PyObject_HEAD PyObject_HEAD
void *plan; /* return of an SPI_saveplan */ SPIPlanPtr plan;
int nargs; int nargs;
Oid *types; Oid *types;
Datum *values; Datum *values;
...@@ -3327,7 +3327,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -3327,7 +3327,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PyObject *list = NULL; PyObject *list = NULL;
PyObject *volatile optr = NULL; PyObject *volatile optr = NULL;
char *query; char *query;
void *tmpplan;
volatile MemoryContext oldcontext; volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner; volatile ResourceOwner oldowner;
volatile int nargs; volatile int nargs;
...@@ -3431,12 +3430,8 @@ PLy_spi_prepare(PyObject *self, PyObject *args) ...@@ -3431,12 +3430,8 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
SPI_result_code_string(SPI_result)); SPI_result_code_string(SPI_result));
/* transfer plan from procCxt to topCxt */ /* transfer plan from procCxt to topCxt */
tmpplan = plan->plan; if (SPI_keepplan(plan->plan))
plan->plan = SPI_saveplan(tmpplan); elog(ERROR, "SPI_keepplan failed");
SPI_freeplan(tmpplan);
if (plan->plan == NULL)
elog(ERROR, "SPI_saveplan failed: %s",
SPI_result_code_string(SPI_result));
/* Commit the inner transaction, return to outer xact context */ /* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction(); ReleaseCurrentSubTransaction();
......
...@@ -128,7 +128,7 @@ typedef struct pltcl_proc_desc ...@@ -128,7 +128,7 @@ typedef struct pltcl_proc_desc
typedef struct pltcl_query_desc typedef struct pltcl_query_desc
{ {
char qname[20]; char qname[20];
void *plan; SPIPlanPtr plan;
int nargs; int nargs;
Oid *argtypes; Oid *argtypes;
FmgrInfo *arginfuncs; FmgrInfo *arginfuncs;
...@@ -2024,7 +2024,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp, ...@@ -2024,7 +2024,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
* pltcl_SPI_prepare() - Builtin support for prepared plans * pltcl_SPI_prepare() - Builtin support for prepared plans
* The Tcl command SPI_prepare * The Tcl command SPI_prepare
* always saves the plan using * 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 * access. There is no chance to prepare
* and not save the plan currently. * and not save the plan currently.
**********************************************************************/ **********************************************************************/
...@@ -2035,7 +2035,6 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, ...@@ -2035,7 +2035,6 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int nargs; int nargs;
CONST84 char **args; CONST84 char **args;
pltcl_query_desc *qdesc; pltcl_query_desc *qdesc;
void *plan;
int i; int i;
Tcl_HashEntry *hashent; Tcl_HashEntry *hashent;
int hashnew; int hashnew;
...@@ -2103,22 +2102,18 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, ...@@ -2103,22 +2102,18 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
* Prepare the plan and check for errors * Prepare the plan and check for errors
************************************************************/ ************************************************************/
UTF_BEGIN; 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; UTF_END;
if (plan == NULL) if (qdesc->plan == NULL)
elog(ERROR, "SPI_prepare() failed"); elog(ERROR, "SPI_prepare() failed");
/************************************************************ /************************************************************
* Save the plan into permanent memory (right now it's in the * Save the plan into permanent memory (right now it's in the
* SPI procCxt, which will go away at function end). * SPI procCxt, which will go away at function end).
************************************************************/ ************************************************************/
qdesc->plan = SPI_saveplan(plan); if (SPI_keepplan(qdesc->plan))
if (qdesc->plan == NULL) elog(ERROR, "SPI_keepplan() failed");
elog(ERROR, "SPI_saveplan() failed");
/* Release the procCxt copy to avoid within-function memory leak */
SPI_freeplan(plan);
pltcl_subtrans_commit(oldcontext, oldowner); pltcl_subtrans_commit(oldcontext, oldowner);
} }
......
...@@ -622,9 +622,8 @@ ttdummy(PG_FUNCTION_ARGS) ...@@ -622,9 +622,8 @@ ttdummy(PG_FUNCTION_ARGS)
if (pplan == NULL) if (pplan == NULL)
elog(ERROR, "ttdummy (%s): SPI_prepare returned %d", relname, SPI_result); elog(ERROR, "ttdummy (%s): SPI_prepare returned %d", relname, SPI_result);
pplan = SPI_saveplan(pplan); if (SPI_keepplan(pplan))
if (pplan == NULL) elog(ERROR, "ttdummy (%s): SPI_keepplan failed", relname);
elog(ERROR, "ttdummy (%s): SPI_saveplan returned %d", relname, SPI_result);
splan = pplan; splan = pplan;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment