mirror of
https://github.com/postgres/postgres.git
synced 2025-04-20 00:42:27 +03:00
Redesign the plancache mechanism for more flexibility and efficiency.
Rewrite plancache.c so that a "cached plan" (which is rather a misnomer at this point) can support generation of custom, parameter-value-dependent plans, and can make an intelligent choice between using custom plans and the traditional generic-plan approach. The specific choice algorithm implemented here can probably be improved in future, but this commit is all about getting the mechanism in place, not the policy. In addition, restructure the API to greatly reduce the amount of extraneous data copying needed. The main compromise needed to make that possible was to split the initial creation of a CachedPlanSource into two steps. It's worth noting in particular that SPI_saveplan is now deprecated in favor of SPI_keepplan, which accomplishes the same end result with zero data copying, and no need to then spend even more cycles throwing away the original SPIPlan. The risk of long-term memory leaks while manipulating SPIPlans has also been greatly reduced. Most of this improvement is based on use of the recently-added MemoryContextSetParent primitive.
This commit is contained in:
parent
09e98a3e17
commit
e6faf910d7
@ -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)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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 < $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;
|
|||||||
|
|
||||||
<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
|
|||||||
|
|
||||||
<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 '
|
|||||||
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;
|
|||||||
</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;
|
|||||||
</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 — at the price of constructing a new execution plan on
|
statement — 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;
|
|||||||
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 — that is, there is a cache
|
independently for each such table — 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;
|
|||||||
|
|
||||||
<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;
|
|||||||
<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;
|
|||||||
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 @@
|
|||||||
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 @@
|
|||||||
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 @@
|
|||||||
</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 @@
|
|||||||
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
|
|||||||
<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
|
|||||||
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
|
|||||||
<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
|
|||||||
</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>,
|
|||||||
<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>,
|
|||||||
|
|
||||||
<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
|
|||||||
<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
|
|||||||
</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
|
|||||||
|
|
||||||
<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
|
|||||||
<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
|
|||||||
</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
|
|||||||
|
|
||||||
<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 <
|
|||||||
<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 <
|
|||||||
|
|
||||||
<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>,
|
|||||||
<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>,
|
|||||||
|
|
||||||
<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>)
|
|||||||
|
|
||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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
|
|||||||
|
|
||||||
<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
|
|||||||
<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
|
|||||||
|
|
||||||
<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>)
|
|||||||
|
|
||||||
<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>)
|
|||||||
<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>)
|
|||||||
|
|
||||||
<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>
|
|||||||
<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>
|
|||||||
<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>
|
|||||||
<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>
|
|||||||
<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>
|
|||||||
|
|
||||||
<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>,
|
|||||||
<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>,
|
|||||||
<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>,
|
|||||||
|
|
||||||
<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<
|
|||||||
<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<
|
|||||||
<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<
|
|||||||
<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<
|
|||||||
|
|
||||||
<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
|
|||||||
|
|
||||||
<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
|
|||||||
<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
|
|||||||
<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
|
|||||||
<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>,
|
|||||||
|
|
||||||
<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>,
|
|||||||
<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>)
|
|||||||
|
|
||||||
<!-- *********************************************** -->
|
<!-- *********************************************** -->
|
||||||
|
|
||||||
|
<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>)
|
|||||||
|
|
||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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>)
|
|||||||
|
|
||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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>)
|
|||||||
<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)
|
|||||||
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);
|
|||||||
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)
|
|||||||
(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)
|
|||||||
* 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)
|
|||||||
/* 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,
|
|||||||
/* 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,
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
ParamExternData *prm = ¶mLI->params[i];
|
ParamExternData *prm = ¶mLI->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)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
|
|
||||||
/* 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)
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* 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 *
|
|||||||
FetchPreparedStatementTargetList(PreparedStatement *stmt)
|
FetchPreparedStatementTargetList(PreparedStatement *stmt)
|
||||||
{
|
{
|
||||||
List *tlist;
|
List *tlist;
|
||||||
CachedPlan *cplan;
|
|
||||||
|
|
||||||
/* No point in looking if it doesn't return tuples */
|
/* Get the plan's primary targetlist */
|
||||||
if (stmt->plansource->resultDesc == NULL)
|
tlist = CachedPlanGetTargetList(stmt->plansource);
|
||||||
return NIL;
|
|
||||||
|
|
||||||
/* Make sure the plan is up to date */
|
/* Copy into caller's context in case plan gets invalidated */
|
||||||
cplan = RevalidateCachedPlan(stmt->plansource, true);
|
return (List *) copyObject(tlist);
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -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,
|
|||||||
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,
|
|||||||
}
|
}
|
||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
|
|
||||||
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,
|
|||||||
|
|
||||||
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,
|
|||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
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,
|
|||||||
_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,
|
|||||||
_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)
|
|||||||
|
|
||||||
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,21 +644,18 @@ 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)
|
|
||||||
{
|
|
||||||
ListCell *lc;
|
|
||||||
|
|
||||||
foreach(lc, plan->plancache_list)
|
foreach(lc, plan->plancache_list)
|
||||||
{
|
{
|
||||||
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
||||||
|
|
||||||
DropCachedPlan(plansource);
|
DropCachedPlan(plansource);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Now get rid of the _SPI_plan and subsidiary data in its plancxt */
|
/* Now get rid of the _SPI_plan and subsidiary data in its plancxt */
|
||||||
MemoryContextDelete(plan->plancxt);
|
MemoryContextDelete(plan->plancxt);
|
||||||
@ -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,
|
|||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
|
|
||||||
/* 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,
|
|||||||
/* 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,
|
|||||||
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,
|
|||||||
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 = RevalidateCachedPlan(plansource, false);
|
cplan = GetCachedPlan(plansource, paramLI, false);
|
||||||
stmt_list = cplan->stmt_list;
|
stmt_list = cplan->stmt_list;
|
||||||
}
|
|
||||||
else
|
if (!plan->saved)
|
||||||
{
|
{
|
||||||
/* No replan, but copy the plan into the portal's context */
|
/*
|
||||||
|
* We don't want the portal to depend on an unsaved CachedPlanSource,
|
||||||
|
* so must copy the plan into the portal's context. An error here
|
||||||
|
* will result in leaking our refcount on the plan, but it doesn't
|
||||||
|
* matter because the plan is unsaved and hence transient anyway.
|
||||||
|
*/
|
||||||
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
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,
|
|||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
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
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
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,11 +1467,10 @@ 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);
|
|
||||||
if (plan->saved)
|
|
||||||
{
|
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
|
Assert(plan->magic == _SPI_PLAN_MAGIC);
|
||||||
|
|
||||||
foreach(lc, plan->plancache_list)
|
foreach(lc, plan->plancache_list)
|
||||||
{
|
{
|
||||||
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
||||||
@ -1479,12 +1479,6 @@ SPI_plan_is_valid(SPIPlanPtr plan)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* An unsaved plan is assumed valid for its (short) lifetime */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1646,7 +1640,7 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse and plan a querystring.
|
* Parse and analyze a querystring.
|
||||||
*
|
*
|
||||||
* At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
|
* 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)
|
|||||||
* 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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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);
|
|
||||||
|
|
||||||
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
|
/* Finish filling in the CachedPlanSource */
|
||||||
cplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
|
CompleteCachedPlan(plansource,
|
||||||
|
stmt_list,
|
||||||
plansource->raw_parse_tree = parsetree;
|
NULL,
|
||||||
/* cast-away-const here is a bit ugly, but there's no reason to copy */
|
plan->argtypes,
|
||||||
plansource->query_string = (char *) src;
|
plan->nargs,
|
||||||
plansource->commandTag = CreateCommandTag(parsetree);
|
plan->parserSetup,
|
||||||
plansource->param_types = plan->argtypes;
|
plan->parserSetupArg,
|
||||||
plansource->num_params = plan->nargs;
|
cursor_options,
|
||||||
plansource->parserSetup = plan->parserSetup;
|
false); /* not fixed result */
|
||||||
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,
|
|||||||
|
|
||||||
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);
|
*/
|
||||||
|
cplan = GetCachedPlan(plansource, paramLI, plan->saved);
|
||||||
stmt_list = cplan->stmt_list;
|
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,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 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:
|
|||||||
|
|
||||||
/* 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:
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
|
|
||||||
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,23 +2266,98 @@ _SPI_checktuples(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make an "unsaved" copy of the given plan, in a child context of parentcxt.
|
* Convert a "temporary" SPIPlan into an "unsaved" plan.
|
||||||
|
*
|
||||||
|
* The passed _SPI_plan struct is on the stack, and all its subsidiary data
|
||||||
|
* is in or under the current SPI executor context. Copy the plan into the
|
||||||
|
* SPI procedure context so it will survive _SPI_end_call(). To minimize
|
||||||
|
* data copying, this destructively modifies the input plan, by taking the
|
||||||
|
* plancache entries away from it and reparenting them to the new SPIPlan.
|
||||||
*/
|
*/
|
||||||
static SPIPlanPtr
|
static SPIPlanPtr
|
||||||
_SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
|
_SPI_make_plan_non_temp(SPIPlanPtr plan)
|
||||||
|
{
|
||||||
|
SPIPlanPtr newplan;
|
||||||
|
MemoryContext parentcxt = _SPI_current->procCxt;
|
||||||
|
MemoryContext plancxt;
|
||||||
|
MemoryContext oldcxt;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
/* Assert the input is a temporary SPIPlan */
|
||||||
|
Assert(plan->magic == _SPI_PLAN_MAGIC);
|
||||||
|
Assert(plan->plancxt == NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a memory context for the plan, underneath the procedure context.
|
||||||
|
* We don't expect the plan to be very large, so use smaller-than-default
|
||||||
|
* alloc parameters.
|
||||||
|
*/
|
||||||
|
plancxt = AllocSetContextCreate(parentcxt,
|
||||||
|
"SPI Plan",
|
||||||
|
ALLOCSET_SMALL_MINSIZE,
|
||||||
|
ALLOCSET_SMALL_INITSIZE,
|
||||||
|
ALLOCSET_SMALL_MAXSIZE);
|
||||||
|
oldcxt = MemoryContextSwitchTo(plancxt);
|
||||||
|
|
||||||
|
/* Copy the SPI_plan struct and subsidiary data into the new context */
|
||||||
|
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
|
||||||
|
newplan->magic = _SPI_PLAN_MAGIC;
|
||||||
|
newplan->saved = false;
|
||||||
|
newplan->plancache_list = NIL;
|
||||||
|
newplan->plancxt = plancxt;
|
||||||
|
newplan->cursor_options = plan->cursor_options;
|
||||||
|
newplan->nargs = plan->nargs;
|
||||||
|
if (plan->nargs > 0)
|
||||||
|
{
|
||||||
|
newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
|
||||||
|
memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
newplan->argtypes = NULL;
|
||||||
|
newplan->parserSetup = plan->parserSetup;
|
||||||
|
newplan->parserSetupArg = plan->parserSetupArg;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reparent all the CachedPlanSources into the procedure context. In
|
||||||
|
* theory this could fail partway through due to the pallocs, but we
|
||||||
|
* don't care too much since both the procedure context and the executor
|
||||||
|
* context would go away on error.
|
||||||
|
*/
|
||||||
|
foreach(lc, plan->plancache_list)
|
||||||
|
{
|
||||||
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
||||||
|
|
||||||
|
CachedPlanSetParentContext(plansource, parentcxt);
|
||||||
|
|
||||||
|
/* Build new list, with list cells in plancxt */
|
||||||
|
newplan->plancache_list = lappend(newplan->plancache_list, plansource);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldcxt);
|
||||||
|
|
||||||
|
/* For safety, unlink the CachedPlanSources from the temporary plan */
|
||||||
|
plan->plancache_list = NIL;
|
||||||
|
|
||||||
|
return newplan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a "saved" copy of the given plan.
|
||||||
|
*/
|
||||||
|
static SPIPlanPtr
|
||||||
|
_SPI_save_plan(SPIPlanPtr plan)
|
||||||
{
|
{
|
||||||
SPIPlanPtr newplan;
|
SPIPlanPtr newplan;
|
||||||
MemoryContext plancxt;
|
MemoryContext plancxt;
|
||||||
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(parentcxt,
|
plancxt = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"SPI Plan",
|
"SPI Plan",
|
||||||
ALLOCSET_SMALL_MINSIZE,
|
ALLOCSET_SMALL_MINSIZE,
|
||||||
ALLOCSET_SMALL_INITSIZE,
|
ALLOCSET_SMALL_INITSIZE,
|
||||||
@ -2324,113 +2382,32 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
|
|||||||
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;
|
|
||||||
CachedPlan *newcplan;
|
|
||||||
|
|
||||||
/* Note: we assume we don't need to revalidate the plan */
|
|
||||||
cplan = plansource->plan;
|
|
||||||
|
|
||||||
newsource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
|
|
||||||
newcplan = (CachedPlan *) palloc0(sizeof(CachedPlan));
|
|
||||||
|
|
||||||
newsource->raw_parse_tree = copyObject(plansource->raw_parse_tree);
|
|
||||||
newsource->query_string = pstrdup(plansource->query_string);
|
|
||||||
newsource->commandTag = plansource->commandTag;
|
|
||||||
newsource->param_types = newplan->argtypes;
|
|
||||||
newsource->num_params = newplan->nargs;
|
|
||||||
newsource->parserSetup = newplan->parserSetup;
|
|
||||||
newsource->parserSetupArg = newplan->parserSetupArg;
|
|
||||||
newsource->fully_planned = plansource->fully_planned;
|
|
||||||
newsource->fixed_result = plansource->fixed_result;
|
|
||||||
/* no need to worry about seach_path, generation or saved_xmin */
|
|
||||||
if (plansource->resultDesc)
|
|
||||||
newsource->resultDesc = CreateTupleDescCopy(plansource->resultDesc);
|
|
||||||
newsource->plan = newcplan;
|
|
||||||
|
|
||||||
newcplan->stmt_list = copyObject(cplan->stmt_list);
|
|
||||||
newcplan->fully_planned = cplan->fully_planned;
|
|
||||||
|
|
||||||
|
newsource = CopyCachedPlan(plansource);
|
||||||
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
|
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcxt);
|
MemoryContextSwitchTo(oldcxt);
|
||||||
|
|
||||||
return newplan;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make a "saved" copy of the given plan, entrusting everything to plancache.c
|
|
||||||
*/
|
|
||||||
static SPIPlanPtr
|
|
||||||
_SPI_save_plan(SPIPlanPtr plan)
|
|
||||||
{
|
|
||||||
SPIPlanPtr newplan;
|
|
||||||
MemoryContext plancxt;
|
|
||||||
MemoryContext oldcxt;
|
|
||||||
ListCell *lc;
|
|
||||||
|
|
||||||
Assert(!plan->saved); /* not currently supported */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a memory context for the plan. We don't expect the plan to be
|
* Mark it saved, reparent it under CacheMemoryContext, and mark all the
|
||||||
* very large, so use smaller-than-default alloc parameters.
|
* component CachedPlanSources as saved. This sequence cannot fail
|
||||||
|
* partway through, so there's no risk of long-term memory leakage.
|
||||||
*/
|
*/
|
||||||
plancxt = AllocSetContextCreate(CacheMemoryContext,
|
|
||||||
"SPI Plan",
|
|
||||||
ALLOCSET_SMALL_MINSIZE,
|
|
||||||
ALLOCSET_SMALL_INITSIZE,
|
|
||||||
ALLOCSET_SMALL_MAXSIZE);
|
|
||||||
oldcxt = MemoryContextSwitchTo(plancxt);
|
|
||||||
|
|
||||||
/* Copy the SPI plan into its own context */
|
|
||||||
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
|
|
||||||
newplan->magic = _SPI_PLAN_MAGIC;
|
|
||||||
newplan->saved = true;
|
newplan->saved = true;
|
||||||
newplan->plancache_list = NIL;
|
MemoryContextSetParent(newplan->plancxt, CacheMemoryContext);
|
||||||
newplan->plancxt = plancxt;
|
|
||||||
newplan->cursor_options = plan->cursor_options;
|
|
||||||
newplan->nargs = plan->nargs;
|
|
||||||
if (plan->nargs > 0)
|
|
||||||
{
|
|
||||||
newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid));
|
|
||||||
memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
newplan->argtypes = NULL;
|
|
||||||
newplan->parserSetup = plan->parserSetup;
|
|
||||||
newplan->parserSetupArg = plan->parserSetupArg;
|
|
||||||
|
|
||||||
foreach(lc, plan->plancache_list)
|
foreach(lc, newplan->plancache_list)
|
||||||
{
|
{
|
||||||
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc);
|
||||||
CachedPlanSource *newsource;
|
|
||||||
CachedPlan *cplan;
|
|
||||||
|
|
||||||
/* Note: we assume we don't need to revalidate the plan */
|
SaveCachedPlan(plansource);
|
||||||
cplan = plansource->plan;
|
|
||||||
|
|
||||||
newsource = CreateCachedPlan(plansource->raw_parse_tree,
|
|
||||||
plansource->query_string,
|
|
||||||
plansource->commandTag,
|
|
||||||
newplan->argtypes,
|
|
||||||
newplan->nargs,
|
|
||||||
newplan->cursor_options,
|
|
||||||
cplan->stmt_list,
|
|
||||||
true,
|
|
||||||
false);
|
|
||||||
if (newplan->parserSetup != NULL)
|
|
||||||
CachedPlanSetParserHook(newsource,
|
|
||||||
newplan->parserSetup,
|
|
||||||
newplan->parserSetupArg);
|
|
||||||
|
|
||||||
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcxt);
|
|
||||||
|
|
||||||
return newplan;
|
return newplan;
|
||||||
}
|
}
|
||||||
|
@ -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 */
|
|||||||
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 */
|
|||||||
* 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 */
|
|||||||
{
|
{
|
||||||
/* 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 */
|
|||||||
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 */
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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,
|
||||||
¶mTypes,
|
¶mTypes,
|
||||||
&numParams);
|
&numParams);
|
||||||
@ -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 */
|
|||||||
/* 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 (is_named)
|
if (unnamed_stmt_context)
|
||||||
{
|
MemoryContextSetParent(psrc->context, MessageContext);
|
||||||
StorePreparedStatement(stmt_name,
|
|
||||||
raw_parse_tree,
|
/* Finish filling in the CachedPlanSource */
|
||||||
query_string,
|
CompleteCachedPlan(psrc,
|
||||||
commandTag,
|
querytree_list,
|
||||||
|
unnamed_stmt_context,
|
||||||
paramTypes,
|
paramTypes,
|
||||||
numParams,
|
numParams,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
0, /* default cursor options */
|
0, /* default cursor options */
|
||||||
stmt_list,
|
true); /* fixed result */
|
||||||
false);
|
|
||||||
|
/* If we got a cancel signal during analysis, quit */
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
|
if (is_named)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Store the query as a prepared statement.
|
||||||
|
*/
|
||||||
|
StorePreparedStatement(stmt_name, psrc, 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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
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)
|
|||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
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)
|
|||||||
|
|
||||||
pq_getmsgend(input_message);
|
pq_getmsgend(input_message);
|
||||||
|
|
||||||
if (psrc->fully_planned)
|
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
* Revalidate the cached plan; this may result in replanning. Any
|
* Obtain a plan from the CachedPlanSource. Any cruft from (re)planning
|
||||||
* cruft will be generated in MessageContext. The plan refcount will
|
* will be generated in MessageContext. The plan refcount will be
|
||||||
* be assigned to the Portal, so it will be released at portal
|
* assigned to the Portal, so it will be released at portal destruction.
|
||||||
* destruction.
|
|
||||||
*/
|
*/
|
||||||
cplan = RevalidateCachedPlan(psrc, false);
|
cplan = GetCachedPlan(psrc, params, 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)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
*/
|
*/
|
||||||
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)
|
|||||||
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 @@
|
|||||||
* 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,
|
|||||||
/* 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)
|
|||||||
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)
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
1503
src/backend/utils/cache/plancache.c
vendored
1503
src/backend/utils/cache/plancache.c
vendored
File diff suppressed because it is too large
Load Diff
@ -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)
|
|||||||
* (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);
|
|||||||
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,
|
|||||||
|
|
||||||
/* 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,
|
|||||||
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
|
|||||||
} _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
|
|||||||
#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,
|
|||||||
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 @@
|
|||||||
#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.
|
||||||
*
|
*
|
||||||
* Normally, both the struct itself and the subsidiary data live in the
|
* An actual execution plan, represented by CachedPlan, is derived from the
|
||||||
* context denoted by the context field, while the linked-to CachedPlan, if
|
* CachedPlanSource when we need to execute the query. The plan could be
|
||||||
* any, has its own context. Thus an invalidated CachedPlan can be dropped
|
* either generic (usable with any set of plan parameters) or custom (for a
|
||||||
* when no longer needed, and conversely a CachedPlanSource can be dropped
|
* specific set of parameters). plancache.c contains the logic that decides
|
||||||
* without worrying whether any portals depend on particular instances of
|
* which way to do it for any particular execution. If we are using a generic
|
||||||
* its plan.
|
* cached plan then it is meant to be re-used across multiple executions, so
|
||||||
|
* callers must always treat CachedPlans as read-only.
|
||||||
*
|
*
|
||||||
* But for entries created by FastCreateCachedPlan, the CachedPlanSource
|
* Once successfully built and "saved", CachedPlanSources typically live
|
||||||
* and the initial version of the CachedPlan share the same memory context.
|
* for the life of the backend, although they can be dropped explicitly.
|
||||||
* In this case, we treat the memory context as belonging to the CachedPlan.
|
* CachedPlans are reference-counted and go away automatically when the last
|
||||||
* The CachedPlanSource has an extra reference-counted link (orig_plan)
|
* reference is dropped. A CachedPlan can outlive the CachedPlanSource it
|
||||||
* to the CachedPlan, and the memory context goes away when the CachedPlan's
|
* was created from.
|
||||||
* reference count goes to zero. This arrangement saves overhead for plans
|
*
|
||||||
* that aren't expected to live long enough to need replanning, while not
|
* An "unsaved" CachedPlanSource can be used for generating plans, but it
|
||||||
* losing any flexibility if a replan turns out to be necessary.
|
* lives in transient storage and will not be updated in response to sinval
|
||||||
|
* events.
|
||||||
|
*
|
||||||
|
* CachedPlans made from saved CachedPlanSources are likewise in permanent
|
||||||
|
* storage, so to avoid memory leaks, the reference-counted references to them
|
||||||
|
* must be held in permanent data structures or ResourceOwners. CachedPlans
|
||||||
|
* made from unsaved CachedPlanSources are in children of the caller's
|
||||||
|
* memory context, so references to them should not be longer-lived than
|
||||||
|
* that context. (Reference counting is somewhat pro forma in that case,
|
||||||
|
* though it may be useful if the CachedPlan can be discarded early.)
|
||||||
|
*
|
||||||
|
* A CachedPlanSource has two associated memory contexts: one that holds the
|
||||||
|
* struct itself, the query source text and the raw parse tree, and another
|
||||||
|
* context that holds the rewritten query tree and associated data. This
|
||||||
|
* allows the query tree to be discarded easily when it is invalidated.
|
||||||
*
|
*
|
||||||
* Note: the string referenced by commandTag is not subsidiary storage;
|
* 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 @@
|
|||||||
*/
|
*/
|
||||||
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 CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
|
|
||||||
const char *query_string,
|
|
||||||
const char *commandTag,
|
|
||||||
Oid *param_types,
|
|
||||||
int num_params,
|
|
||||||
int cursor_options,
|
|
||||||
List *stmt_list,
|
|
||||||
bool fully_planned,
|
|
||||||
bool fixed_result);
|
|
||||||
extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree,
|
|
||||||
char *query_string,
|
|
||||||
const char *commandTag,
|
|
||||||
Oid *param_types,
|
|
||||||
int num_params,
|
|
||||||
int cursor_options,
|
|
||||||
List *stmt_list,
|
|
||||||
bool fully_planned,
|
|
||||||
bool fixed_result,
|
|
||||||
MemoryContext context);
|
|
||||||
extern void CachedPlanSetParserHook(CachedPlanSource *plansource,
|
|
||||||
ParserSetupHook parserSetup,
|
|
||||||
void *parserSetupArg);
|
|
||||||
extern void DropCachedPlan(CachedPlanSource *plansource);
|
|
||||||
extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
|
|
||||||
bool useResOwner);
|
|
||||||
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
|
|
||||||
extern bool CachedPlanIsValid(CachedPlanSource *plansource);
|
|
||||||
extern TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
|
|
||||||
|
|
||||||
extern void ResetPlanCache(void);
|
extern void ResetPlanCache(void);
|
||||||
|
|
||||||
|
extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
|
||||||
|
const char *query_string,
|
||||||
|
const char *commandTag);
|
||||||
|
extern void CompleteCachedPlan(CachedPlanSource *plansource,
|
||||||
|
List *querytree_list,
|
||||||
|
MemoryContext querytree_context,
|
||||||
|
Oid *param_types,
|
||||||
|
int num_params,
|
||||||
|
ParserSetupHook parserSetup,
|
||||||
|
void *parserSetupArg,
|
||||||
|
int cursor_options,
|
||||||
|
bool fixed_result);
|
||||||
|
|
||||||
|
extern void SaveCachedPlan(CachedPlanSource *plansource);
|
||||||
|
extern void DropCachedPlan(CachedPlanSource *plansource);
|
||||||
|
|
||||||
|
extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
|
||||||
|
MemoryContext newcontext);
|
||||||
|
|
||||||
|
extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource);
|
||||||
|
|
||||||
|
extern bool CachedPlanIsValid(CachedPlanSource *plansource);
|
||||||
|
|
||||||
|
extern List *CachedPlanGetTargetList(CachedPlanSource *plansource);
|
||||||
|
|
||||||
|
extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
|
||||||
|
ParamListInfo boundParams,
|
||||||
|
bool useResOwner);
|
||||||
|
extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
|
||||||
|
|
||||||
#endif /* PLANCACHE_H */
|
#endif /* PLANCACHE_H */
|
||||||
|
@ -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)
|
|||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
* 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)
|
|||||||
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,
|
|||||||
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)
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
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:
|
|||||||
*
|
*
|
||||||
* 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,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
/*
|
/*
|
||||||
* 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,
|
|||||||
* 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,
|
|||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* 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 = ¶mLI->params[dno];
|
||||||
|
|
||||||
|
prm->value = var->value;
|
||||||
|
prm->isnull = var->isnull;
|
||||||
|
prm->pflags = PARAM_FLAG_CONST;
|
||||||
|
prm->ptype = var->datatype->typoid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bms_free(tmpset);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up link to active expr where the hook functions can find it.
|
* 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
|
|||||||
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;
|
||||||
|
query = (Query *) linitial(plansource->query_list);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2. It must be a plain SELECT query without any input tables
|
||||||
|
*/
|
||||||
|
if (!IsA(query, Query))
|
||||||
|
return;
|
||||||
|
if (query->commandType != CMD_SELECT || query->intoClause)
|
||||||
|
return;
|
||||||
|
if (query->rtable != NIL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
|
/*
|
||||||
|
* 3. Can't have any subplans, aggregates, qual clauses either
|
||||||
|
*/
|
||||||
|
if (query->hasAggs ||
|
||||||
|
query->hasWindowFuncs ||
|
||||||
|
query->hasSubLinks ||
|
||||||
|
query->hasForUpdate ||
|
||||||
|
query->cteList ||
|
||||||
|
query->jointree->quals ||
|
||||||
|
query->groupClause ||
|
||||||
|
query->havingQual ||
|
||||||
|
query->windowClause ||
|
||||||
|
query->distinctClause ||
|
||||||
|
query->sortClause ||
|
||||||
|
query->limitOffset ||
|
||||||
|
query->limitCount ||
|
||||||
|
query->setOperations)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 4. The query must have a single attribute as result
|
||||||
|
*/
|
||||||
|
if (list_length(query->targetList) != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OK, it seems worth constructing a plan for more careful checking.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Get the generic plan for the query */
|
||||||
|
cplan = GetCachedPlan(plansource, NULL, true);
|
||||||
|
Assert(cplan == plansource->gplan);
|
||||||
|
|
||||||
|
/* Share the remaining work with recheck code path */
|
||||||
|
exec_simple_recheck_plan(expr, cplan);
|
||||||
|
|
||||||
|
/* Release our plan refcount */
|
||||||
|
ReleaseCachedPlan(cplan, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* exec_simple_recheck_plan --- check for simple plan once we have CachedPlan
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
|
||||||
|
{
|
||||||
|
PlannedStmt *stmt;
|
||||||
|
Plan *plan;
|
||||||
|
TargetEntry *tle;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize to "not simple", and remember the plan generation number we
|
||||||
|
* last checked.
|
||||||
|
*/
|
||||||
|
expr->expr_simple_expr = NULL;
|
||||||
|
expr->expr_simple_generation = cplan->generation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. There must be one single plantree
|
||||||
|
*/
|
||||||
|
if (list_length(cplan->stmt_list) != 1)
|
||||||
|
return;
|
||||||
|
stmt = (PlannedStmt *) linitial(cplan->stmt_list);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 2. It must be a RESULT plan --> no scan's required
|
* 2. It must be a RESULT plan --> no scan's required
|
||||||
|
@ -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)
|
|||||||
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)
|
|||||||
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
|
|||||||
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,
|
|||||||
* 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,
|
|||||||
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,
|
|||||||
* 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)
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user