Improve performance of repeated CALLs within plpgsql procedures.
authorTom Lane <[email protected]>
Tue, 26 Jan 2021 03:28:29 +0000 (22:28 -0500)
committerTom Lane <[email protected]>
Tue, 26 Jan 2021 03:28:29 +0000 (22:28 -0500)
This patch essentially is cleaning up technical debt left behind
by the original implementation of plpgsql procedures, particularly
commit d92bc83c4.  That patch (or more precisely, follow-on patches
fixing its worst bugs) forced us to re-plan CALL and DO statements
each time through, if we're in a non-atomic context.  That wasn't
for any fundamental reason, but just because use of a saved plan
requires having a ResourceOwner to hold a reference count for the
plan, and we had no suitable resowner at hand, nor would the
available APIs support using one if we did.  While it's not that
expensive to create a "plan" for CALL/DO, the cycles do add up
in repeated executions.

This patch therefore makes the following API changes:

* GetCachedPlan/ReleaseCachedPlan are modified to let the caller
specify which resowner to use to pin the plan, rather than forcing
use of CurrentResourceOwner.

* spi.c gains a "SPI_execute_plan_extended" entry point that lets
callers say which resowner to use to pin the plan.  This borrows the
idea of an options struct from the recently added SPI_prepare_extended,
hopefully allowing future options to be added without more API breaks.
This supersedes SPI_execute_plan_with_paramlist (which I've marked
deprecated) as well as SPI_execute_plan_with_receiver (which is new
in v14, so I just took it out altogether).

* I also took the opportunity to remove the crude hack of letting
plpgsql reach into SPI private data structures to mark SPI plans as
"no_snapshot".  It's better to treat that as an option of
SPI_prepare_extended.

Now, when running a non-atomic procedure or DO block that contains
any CALL or DO commands, plpgsql creates a ResourceOwner that
will be used to pin the plans of the CALL/DO commands.  (In an
atomic context, we just use CurrentResourceOwner, as before.)
Having done this, we can just save CALL/DO plans normally,
whether or not they are used across transaction boundaries.
This seems to be good for something like 2X speedup of a CALL
of a trivial procedure with a few simple argument expressions.
By restricting the creation of an extra ResourceOwner like this,
there's essentially zero penalty in cases that can't benefit.

Pavel Stehule, with some further hacking by me

Discussion: https://postgr.es/m/CAFj8pRCLPdDAETvR7Po7gC5y_ibkn_-bOzbeJb39WHms01194Q@mail.gmail.com

15 files changed:
doc/src/sgml/spi.sgml
src/backend/commands/prepare.c
src/backend/executor/spi.c
src/backend/tcop/postgres.c
src/backend/utils/cache/plancache.c
src/backend/utils/mmgr/portalmem.c
src/backend/utils/resowner/resowner.c
src/include/executor/spi.h
src/include/executor/spi_priv.h
src/include/utils/plancache.h
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_gram.y
src/pl/plpgsql/src/pl_handler.c
src/pl/plpgsql/src/plpgsql.h

index f5e0a35da0645e6b20c342d5aa5a68ff1dbebec4..d8c121f5f355f9738bdd408db49cc6a3d03d86e1 100644 (file)
@@ -1722,25 +1722,23 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
 
 <!-- *********************************************** -->
 
-<refentry id="spi-spi-execute-plan-with-paramlist">
- <indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
+<refentry id="spi-spi-execute-plan-extended">
+ <indexterm><primary>SPI_execute_plan_extended</primary></indexterm>
 
  <refmeta>
-  <refentrytitle>SPI_execute_plan_with_paramlist</refentrytitle>
+  <refentrytitle>SPI_execute_plan_extended</refentrytitle>
   <manvolnum>3</manvolnum>
  </refmeta>
 
  <refnamediv>
-  <refname>SPI_execute_plan_with_paramlist</refname>
+  <refname>SPI_execute_plan_extended</refname>
   <refpurpose>execute a statement prepared by <function>SPI_prepare</function></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
-                                    ParamListInfo <parameter>params</parameter>,
-                                    bool <parameter>read_only</parameter>,
-                                    long <parameter>count</parameter>)
+int SPI_execute_plan_extended(SPIPlanPtr <parameter>plan</parameter>,
+                              const SPIExecuteOptions * <parameter>options</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -1748,14 +1746,29 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
   <title>Description</title>
 
   <para>
-   <function>SPI_execute_plan_with_paramlist</function> executes a statement
-   prepared by <function>SPI_prepare</function>.
-   This function is equivalent to <function>SPI_execute_plan</function>
+   <function>SPI_execute_plan_extended</function> executes a statement
+   prepared by <function>SPI_prepare</function> or one of its siblings.
+   This function is equivalent to <function>SPI_execute_plan</function>,
    except that information about the parameter values to be passed to the
-   query is presented differently.  The <literal>ParamListInfo</literal>
-   representation can be convenient for passing down values that are
-   already available in that format.  It also supports use of dynamic
-   parameter sets via hook functions specified in <literal>ParamListInfo</literal>.
+   query is presented differently, and additional execution-controlling
+   options can be passed.
+  </para>
+
+  <para>
+   Query parameter values are represented by
+   a <literal>ParamListInfo</literal> struct, which is convenient for passing
+   down values that are already available in that format.  Dynamic parameter
+   sets can also be used, via hook functions specified
+   in <literal>ParamListInfo</literal>.
+  </para>
+
+  <para>
+   Also, instead of always accumulating the result tuples into a
+   <varname>SPI_tuptable</varname> structure, tuples can be passed to a
+   caller-supplied <literal>DestReceiver</literal> object as they are
+   generated by the executor.  This is particularly helpful for queries
+   that might generate many tuples, since the data can be processed
+   on-the-fly instead of being accumulated in memory.
   </para>
  </refsect1>
 
@@ -1772,11 +1785,30 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>const SPIExecuteOptions * <parameter>options</parameter></literal></term>
+    <listitem>
+     <para>
+      struct containing optional arguments
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <para>
+   Callers should always zero out the entire <parameter>options</parameter>
+   struct, then fill whichever fields they want to set.  This ensures forward
+   compatibility of code, since any fields that are added to the struct in
+   future will be defined to behave backwards-compatibly if they are zero.
+   The currently available <parameter>options</parameter> fields are:
+  </para>
+
+  <variablelist>
    <varlistentry>
     <term><literal>ParamListInfo <parameter>params</parameter></literal></term>
     <listitem>
      <para>
-      data structure containing parameter types and values; NULL if none
+      data structure containing query parameter types and values; NULL if none
      </para>
     </listitem>
    </varlistentry>
@@ -1789,7 +1821,17 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
    </varlistentry>
 
    <varlistentry>
-    <term><literal>long <parameter>count</parameter></literal></term>
+    <term><literal>bool <parameter>no_snapshots</parameter></literal></term>
+    <listitem>
+     <para>
+      <literal>true</literal> prevents SPI from managing snapshots for
+      execution of the query; use with extreme caution
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>uint64 <parameter>tcount</parameter></literal></term>
     <listitem>
      <para>
       maximum number of rows to return,
@@ -1797,6 +1839,29 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>DestReceiver * <parameter>dest</parameter></literal></term>
+    <listitem>
+     <para>
+      <literal>DestReceiver</literal> object that will receive any tuples
+      emitted by the query; if NULL, result tuples are accumulated into
+      a <varname>SPI_tuptable</varname> structure, as
+      in <function>SPI_execute_plan</function>
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ResourceOwner <parameter>owner</parameter></literal></term>
+    <listitem>
+     <para>
+      The resource owner that will hold a reference count on the plan while
+      it is executed.  If NULL, CurrentResourceOwner is used.  Ignored for
+      non-saved plans, as SPI does not acquire reference counts on those.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -1808,35 +1873,40 @@ int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
   </para>
 
   <para>
+   When <parameter>dest</parameter> is NULL,
    <varname>SPI_processed</varname> and
    <varname>SPI_tuptable</varname> are set as in
-   <function>SPI_execute_plan</function> if successful.
+   <function>SPI_execute_plan</function>.
+   When <parameter>dest</parameter> is not NULL,
+   <varname>SPI_processed</varname> is set to zero and
+   <varname>SPI_tuptable</varname> is set to NULL.  If a tuple count
+   is required, the caller's <literal>DestReceiver</literal> object must
+   calculate it.
   </para>
  </refsect1>
 </refentry>
 
 <!-- *********************************************** -->
 
-<refentry id="spi-spi-execute-plan-with-receiver">
- <indexterm><primary>SPI_execute_plan_with_receiver</primary></indexterm>
+<refentry id="spi-spi-execute-plan-with-paramlist">
+ <indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
 
  <refmeta>
-  <refentrytitle>SPI_execute_plan_with_receiver</refentrytitle>
+  <refentrytitle>SPI_execute_plan_with_paramlist</refentrytitle>
   <manvolnum>3</manvolnum>
  </refmeta>
 
  <refnamediv>
-  <refname>SPI_execute_plan_with_receiver</refname>
+  <refname>SPI_execute_plan_with_paramlist</refname>
   <refpurpose>execute a statement prepared by <function>SPI_prepare</function></refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
 <synopsis>
-int SPI_execute_plan_with_receiver(SPIPlanPtr <parameter>plan</parameter>,
-                                   ParamListInfo <parameter>params</parameter>,
-                                   bool <parameter>read_only</parameter>,
-                                   long <parameter>count</parameter>,
-                                   DestReceiver *<parameter>dest</parameter>)
+int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
+                                    ParamListInfo <parameter>params</parameter>,
+                                    bool <parameter>read_only</parameter>,
+                                    long <parameter>count</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -1844,15 +1914,19 @@ int SPI_execute_plan_with_receiver(SPIPlanPtr <parameter>plan</parameter>,
   <title>Description</title>
 
   <para>
-   <function>SPI_execute_plan_with_receiver</function> executes a statement
-   prepared by <function>SPI_prepare</function>.  This function is
-   equivalent to <function>SPI_execute_plan_with_paramlist</function>
-   except that, instead of always accumulating the result tuples into a
-   <varname>SPI_tuptable</varname> structure, tuples can be passed to a
-   caller-supplied <literal>DestReceiver</literal> object as they are
-   generated by the executor.  This is particularly helpful for queries
-   that might generate many tuples, since the data can be processed
-   on-the-fly instead of being accumulated in memory.
+   <function>SPI_execute_plan_with_paramlist</function> executes a statement
+   prepared by <function>SPI_prepare</function>.
+   This function is equivalent to <function>SPI_execute_plan</function>
+   except that information about the parameter values to be passed to the
+   query is presented differently.  The <literal>ParamListInfo</literal>
+   representation can be convenient for passing down values that are
+   already available in that format.  It also supports use of dynamic
+   parameter sets via hook functions specified in <literal>ParamListInfo</literal>.
+  </para>
+
+  <para>
+   This function is now deprecated in favor
+   of <function>SPI_execute_plan_extended</function>.
   </para>
  </refsect1>
 
@@ -1894,17 +1968,6 @@ int SPI_execute_plan_with_receiver(SPIPlanPtr <parameter>plan</parameter>,
      </para>
     </listitem>
    </varlistentry>
-
-   <varlistentry>
-    <term><literal>DestReceiver * <parameter>dest</parameter></literal></term>
-    <listitem>
-     <para>
-      <literal>DestReceiver</literal> object that will receive any tuples
-      emitted by the query; if NULL, this function is exactly equivalent to
-      <function>SPI_execute_plan_with_paramlist</function>
-     </para>
-    </listitem>
-   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -1916,15 +1979,9 @@ int SPI_execute_plan_with_receiver(SPIPlanPtr <parameter>plan</parameter>,
   </para>
 
   <para>
-   When <parameter>dest</parameter> is NULL,
    <varname>SPI_processed</varname> and
    <varname>SPI_tuptable</varname> are set as in
-   <function>SPI_execute_plan</function>.
-   When <parameter>dest</parameter> is not NULL,
-   <varname>SPI_processed</varname> is set to zero and
-   <varname>SPI_tuptable</varname> is set to NULL.  If a tuple count
-   is required, the caller's <literal>DestReceiver</literal> object must
-   calculate it.
+   <function>SPI_execute_plan</function> if successful.
   </para>
  </refsect1>
 </refentry>
index 653ef8e41a69f4d7c6614fa499e291be4d067ab0..f767751c71ae38db0c8a846b9451e8559e381ee1 100644 (file)
@@ -230,7 +230,7 @@ ExecuteQuery(ParseState *pstate,
                                       entry->plansource->query_string);
 
    /* Replan if needed, and increment plan refcount for portal */
-   cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
+   cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL);
    plan_list = cplan->stmt_list;
 
    /*
@@ -651,7 +651,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
    }
 
    /* Replan if needed, and acquire a transient refcount */
-   cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
+   cplan = GetCachedPlan(entry->plansource, paramLI,
+                         CurrentResourceOwner, queryEnv);
 
    INSTR_TIME_SET_CURRENT(planduration);
    INSTR_TIME_SUBTRACT(planduration, planstart);
@@ -687,7 +688,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
    if (estate)
        FreeExecutorState(estate);
 
-   ReleaseCachedPlan(cplan, true);
+   ReleaseCachedPlan(cplan, CurrentResourceOwner);
 }
 
 /*
index e28d2429222f0c588dfb047965606203efbd149b..68a6bcea02d5975248efb5d9e5e204bd38ee5acc 100644 (file)
@@ -66,8 +66,10 @@ static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan);
 
 static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                              Snapshot snapshot, Snapshot crosscheck_snapshot,
-                             bool read_only, bool fire_triggers, uint64 tcount,
-                             DestReceiver *caller_dest);
+                             bool read_only, bool no_snapshots,
+                             bool fire_triggers, uint64 tcount,
+                             DestReceiver *caller_dest,
+                             ResourceOwner plan_owner);
 
 static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes,
                                         Datum *Values, const char *Nulls);
@@ -521,7 +523,9 @@ SPI_execute(const char *src, bool read_only, long tcount)
 
    res = _SPI_execute_plan(&plan, NULL,
                            InvalidSnapshot, InvalidSnapshot,
-                           read_only, true, tcount, NULL);
+                           read_only, false,
+                           true, tcount,
+                           NULL, NULL);
 
    _SPI_end_call(true);
    return res;
@@ -555,7 +559,9 @@ SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
                            _SPI_convert_params(plan->nargs, plan->argtypes,
                                                Values, Nulls),
                            InvalidSnapshot, InvalidSnapshot,
-                           read_only, true, tcount, NULL);
+                           read_only, false,
+                           true, tcount,
+                           NULL, NULL);
 
    _SPI_end_call(true);
    return res;
@@ -570,37 +576,32 @@ SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
 
 /* Execute a previously prepared plan */
 int
-SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
-                               bool read_only, long tcount)
+SPI_execute_plan_extended(SPIPlanPtr plan,
+                         const SPIExecuteOptions *options)
 {
    int         res;
 
-   if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
+   if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || options == NULL)
        return SPI_ERROR_ARGUMENT;
 
    res = _SPI_begin_call(true);
    if (res < 0)
        return res;
 
-   res = _SPI_execute_plan(plan, params,
+   res = _SPI_execute_plan(plan, options->params,
                            InvalidSnapshot, InvalidSnapshot,
-                           read_only, true, tcount, NULL);
+                           options->read_only, options->no_snapshots,
+                           true, options->tcount,
+                           options->dest, options->owner);
 
    _SPI_end_call(true);
    return res;
 }
 
-/*
- * Execute a previously prepared plan.  If dest isn't NULL, we send result
- * tuples to the caller-supplied DestReceiver rather than through the usual
- * SPI output arrangements.  If dest is NULL this is equivalent to
- * SPI_execute_plan_with_paramlist.
- */
+/* Execute a previously prepared plan */
 int
-SPI_execute_plan_with_receiver(SPIPlanPtr plan,
-                              ParamListInfo params,
-                              bool read_only, long tcount,
-                              DestReceiver *dest)
+SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
+                               bool read_only, long tcount)
 {
    int         res;
 
@@ -613,7 +614,9 @@ SPI_execute_plan_with_receiver(SPIPlanPtr plan,
 
    res = _SPI_execute_plan(plan, params,
                            InvalidSnapshot, InvalidSnapshot,
-                           read_only, true, tcount, dest);
+                           read_only, false,
+                           true, tcount,
+                           NULL, NULL);
 
    _SPI_end_call(true);
    return res;
@@ -654,7 +657,9 @@ SPI_execute_snapshot(SPIPlanPtr plan,
                            _SPI_convert_params(plan->nargs, plan->argtypes,
                                                Values, Nulls),
                            snapshot, crosscheck_snapshot,
-                           read_only, fire_triggers, tcount, NULL);
+                           read_only, false,
+                           fire_triggers, tcount,
+                           NULL, NULL);
 
    _SPI_end_call(true);
    return res;
@@ -702,7 +707,9 @@ SPI_execute_with_args(const char *src,
 
    res = _SPI_execute_plan(&plan, paramLI,
                            InvalidSnapshot, InvalidSnapshot,
-                           read_only, true, tcount, NULL);
+                           read_only, false,
+                           true, tcount,
+                           NULL, NULL);
 
    _SPI_end_call(true);
    return res;
@@ -746,7 +753,9 @@ SPI_execute_with_receiver(const char *src,
 
    res = _SPI_execute_plan(&plan, params,
                            InvalidSnapshot, InvalidSnapshot,
-                           read_only, true, tcount, dest);
+                           read_only, false,
+                           true, tcount,
+                           dest, NULL);
 
    _SPI_end_call(true);
    return res;
@@ -1554,7 +1563,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
     */
 
    /* Replan if needed, and increment plan refcount for portal */
-   cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
+   cplan = GetCachedPlan(plansource, paramLI, NULL, _SPI_current->queryEnv);
    stmt_list = cplan->stmt_list;
 
    if (!plan->saved)
@@ -1568,7 +1577,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
        oldcontext = MemoryContextSwitchTo(portal->portalContext);
        stmt_list = copyObject(stmt_list);
        MemoryContextSwitchTo(oldcontext);
-       ReleaseCachedPlan(cplan, false);
+       ReleaseCachedPlan(cplan, NULL);
        cplan = NULL;           /* portal shouldn't depend on cplan */
    }
 
@@ -1950,7 +1959,10 @@ SPI_plan_get_plan_sources(SPIPlanPtr plan)
 /*
  * SPI_plan_get_cached_plan --- get a SPI plan's generic CachedPlan,
  * if the SPI plan contains exactly one CachedPlanSource.  If not,
- * return NULL.  Caller is responsible for doing ReleaseCachedPlan().
+ * return NULL.
+ *
+ * The plan's refcount is incremented (and logged in CurrentResourceOwner,
+ * if it's a saved plan).  Caller is responsible for doing ReleaseCachedPlan.
  *
  * This is exported so that PL/pgSQL can use it (this beats letting PL/pgSQL
  * look directly into the SPIPlan for itself).  It's not documented in
@@ -1984,7 +1996,8 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
    error_context_stack = &spierrcontext;
 
    /* Get the generic plan for the query */
-   cplan = GetCachedPlan(plansource, NULL, plan->saved,
+   cplan = GetCachedPlan(plansource, NULL,
+                         plan->saved ? CurrentResourceOwner : NULL,
                          _SPI_current->queryEnv);
    Assert(cplan == plansource->gplan);
 
@@ -2265,16 +2278,20 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
  *     behavior of taking a new snapshot for each query.
  * crosscheck_snapshot: for RI use, all others pass InvalidSnapshot
  * read_only: true for read-only execution (no CommandCounterIncrement)
+ * no_snapshots: true to skip snapshot management
  * fire_triggers: true to fire AFTER triggers at end of query (normal case);
  *     false means any AFTER triggers are postponed to end of outer query
  * tcount: execution tuple-count limit, or 0 for none
  * caller_dest: DestReceiver to receive output, or NULL for normal SPI output
+ * plan_owner: ResourceOwner that will be used to hold refcount on plan;
+ *     if NULL, CurrentResourceOwner is used (ignored for non-saved plan)
  */
 static int
 _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                  Snapshot snapshot, Snapshot crosscheck_snapshot,
-                 bool read_only, bool fire_triggers, uint64 tcount,
-                 DestReceiver *caller_dest)
+                 bool read_only, bool no_snapshots,
+                 bool fire_triggers, uint64 tcount,
+                 DestReceiver *caller_dest, ResourceOwner plan_owner)
 {
    int         my_res = 0;
    uint64      my_processed = 0;
@@ -2315,10 +2332,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
     * In the first two cases, we can just push the snap onto the stack once
     * for the whole plan list.
     *
-    * But if the plan has no_snapshots set to true, then don't manage
-    * snapshots at all.  The caller should then take care of that.
+    * But if no_snapshots is true, then don't manage snapshots at all here.
+    * The caller must then take care of that.
     */
-   if (snapshot != InvalidSnapshot && !plan->no_snapshots)
+   if (snapshot != InvalidSnapshot && !no_snapshots)
    {
        if (read_only)
        {
@@ -2333,6 +2350,15 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
        }
    }
 
+   /*
+    * Ensure that we have a resource owner if plan is saved, and not if it
+    * isn't.
+    */
+   if (!plan->saved)
+       plan_owner = NULL;
+   else if (plan_owner == NULL)
+       plan_owner = CurrentResourceOwner;
+
    foreach(lc1, plan->plancache_list)
    {
        CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
@@ -2388,16 +2414,18 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 
        /*
         * Replan if needed, and increment plan refcount.  If it's a saved
-        * plan, the refcount must be backed by the CurrentResourceOwner.
+        * plan, the refcount must be backed by the plan_owner.
         */
-       cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
+       cplan = GetCachedPlan(plansource, paramLI,
+                             plan_owner, _SPI_current->queryEnv);
+
        stmt_list = cplan->stmt_list;
 
        /*
         * In the default non-read-only case, get a new snapshot, replacing
         * any that we pushed in a previous cycle.
         */
-       if (snapshot == InvalidSnapshot && !read_only && !plan->no_snapshots)
+       if (snapshot == InvalidSnapshot && !read_only && !no_snapshots)
        {
            if (pushed_active_snap)
                PopActiveSnapshot();
@@ -2450,7 +2478,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
             * If not read-only mode, advance the command counter before each
             * command and update the snapshot.
             */
-           if (!read_only && !plan->no_snapshots)
+           if (!read_only && !no_snapshots)
            {
                CommandCounterIncrement();
                UpdateActiveSnapshotCommandId();
@@ -2499,7 +2527,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                 * caller must be in a nonatomic SPI context and manage
                 * snapshots itself.
                 */
-               if (_SPI_current->atomic || !plan->no_snapshots)
+               if (_SPI_current->atomic || !no_snapshots)
                    context = PROCESS_UTILITY_QUERY;
                else
                    context = PROCESS_UTILITY_QUERY_NONATOMIC;
@@ -2586,7 +2614,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
        }
 
        /* Done with this plan, so release refcount */
-       ReleaseCachedPlan(cplan, plan->saved);
+       ReleaseCachedPlan(cplan, plan_owner);
        cplan = NULL;
 
        /*
@@ -2606,7 +2634,7 @@ fail:
 
    /* We no longer need the cached plan refcount, if any */
    if (cplan)
-       ReleaseCachedPlan(cplan, plan->saved);
+       ReleaseCachedPlan(cplan, plan_owner);
 
    /*
     * Pop the error context stack
index 8dab9fd578001e3cc23bd6f58e287e923b03248d..cb5a96117f6cd1b6a224421ddae8642798a47c24 100644 (file)
@@ -1963,7 +1963,7 @@ exec_bind_message(StringInfo input_message)
     * will be generated in MessageContext.  The plan refcount will be
     * assigned to the Portal, so it will be released at portal destruction.
     */
-   cplan = GetCachedPlan(psrc, params, false, NULL);
+   cplan = GetCachedPlan(psrc, params, NULL, NULL);
 
    /*
     * Now we can define the portal.
index cc04b5b4bef900da3dc1cc44ed3a0fb7d61a56a6..1a0950489d741dfc7eae3d3f6b5f37f773ac8be8 100644 (file)
@@ -533,7 +533,7 @@ ReleaseGenericPlan(CachedPlanSource *plansource)
 
        Assert(plan->magic == CACHEDPLAN_MAGIC);
        plansource->gplan = NULL;
-       ReleaseCachedPlan(plan, false);
+       ReleaseCachedPlan(plan, NULL);
    }
 }
 
@@ -1130,16 +1130,16 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
  * execution.
  *
  * On return, the refcount of the plan has been incremented; a later
- * ReleaseCachedPlan() call is expected.  The refcount has been reported
- * to the CurrentResourceOwner if useResOwner is true (note that that must
- * only be true if it's a "saved" CachedPlanSource).
+ * ReleaseCachedPlan() call is expected.  If "owner" is not NULL then
+ * the refcount has been reported to that ResourceOwner (note that this
+ * is only supported for "saved" CachedPlanSources).
  *
  * Note: if any replanning activity is required, the caller's memory context
  * is used for that work.
  */
 CachedPlan *
 GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
-             bool useResOwner, QueryEnvironment *queryEnv)
+             ResourceOwner owner, QueryEnvironment *queryEnv)
 {
    CachedPlan *plan = NULL;
    List       *qlist;
@@ -1149,7 +1149,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
    Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
    Assert(plansource->is_complete);
    /* This seems worth a real test, though */
-   if (useResOwner && !plansource->is_saved)
+   if (owner && !plansource->is_saved)
        elog(ERROR, "cannot apply ResourceOwner to non-saved cached plan");
 
    /* Make sure the querytree list is valid and we have parse-time locks */
@@ -1228,11 +1228,11 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
    Assert(plan != NULL);
 
    /* Flag the plan as in use by caller */
-   if (useResOwner)
-       ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+   if (owner)
+       ResourceOwnerEnlargePlanCacheRefs(owner);
    plan->refcount++;
-   if (useResOwner)
-       ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
+   if (owner)
+       ResourceOwnerRememberPlanCacheRef(owner, plan);
 
    /*
     * Saved plans should be under CacheMemoryContext so they will not go away
@@ -1253,21 +1253,21 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
  * ReleaseCachedPlan: release active use of a cached plan.
  *
  * This decrements the reference count, and frees the plan if the count
- * has thereby gone to zero.  If useResOwner is true, it is assumed that
- * the reference count is managed by the CurrentResourceOwner.
+ * has thereby gone to zero.  If "owner" is not NULL, it is assumed that
+ * the reference count is managed by that ResourceOwner.
  *
- * Note: useResOwner = false is used for releasing references that are in
+ * Note: owner == NULL is used for releasing references that are in
  * persistent data structures, such as the parent CachedPlanSource or a
  * Portal.  Transient references should be protected by a resource owner.
  */
 void
-ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
+ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner)
 {
    Assert(plan->magic == CACHEDPLAN_MAGIC);
-   if (useResOwner)
+   if (owner)
    {
        Assert(plan->is_saved);
-       ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
+       ResourceOwnerForgetPlanCacheRef(owner, plan);
    }
    Assert(plan->refcount > 0);
    plan->refcount--;
index 22c6d2ba5b2feaeb69d4eafb0772ff62619214ed..66e3181815687e4e10fd481897e756e8ec66b45a 100644 (file)
@@ -310,7 +310,7 @@ PortalReleaseCachedPlan(Portal portal)
 {
    if (portal->cplan)
    {
-       ReleaseCachedPlan(portal->cplan, false);
+       ReleaseCachedPlan(portal->cplan, NULL);
        portal->cplan = NULL;
 
        /*
index 10f15f6a3579a4f4d9329eb158ca1f8923c1643e..a171df573ceebf165e5efb20e850c5601f5f913e 100644 (file)
@@ -652,7 +652,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 
            if (isCommit)
                PrintPlanCacheLeakWarning(res);
-           ReleaseCachedPlan(res, true);
+           ReleaseCachedPlan(res, owner);
        }
 
        /* Ditto for tupdesc references */
@@ -703,18 +703,14 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
 void
 ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner owner)
 {
-   ResourceOwner save;
    Datum       foundres;
 
-   save = CurrentResourceOwner;
-   CurrentResourceOwner = owner;
    while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
    {
        CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);
 
-       ReleaseCachedPlan(res, true);
+       ReleaseCachedPlan(res, owner);
    }
-   CurrentResourceOwner = save;
 }
 
 /*
index 9c70603434a20ad0604aa066555188d7678bf506..5740f8956e53aa8d94a3437543491376f7661743 100644 (file)
@@ -42,6 +42,17 @@ typedef struct SPIPrepareOptions
    int         cursorOptions;
 } SPIPrepareOptions;
 
+/* Optional arguments for SPI_execute_plan_extended */
+typedef struct SPIExecuteOptions
+{
+   ParamListInfo params;
+   bool        read_only;
+   bool        no_snapshots;
+   uint64      tcount;
+   DestReceiver *dest;
+   ResourceOwner owner;
+} SPIExecuteOptions;
+
 /* Plans are opaque structs for standard users of SPI */
 typedef struct _SPI_plan *SPIPlanPtr;
 
@@ -96,13 +107,11 @@ extern int SPI_finish(void);
 extern int SPI_execute(const char *src, bool read_only, long tcount);
 extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
                             bool read_only, long tcount);
+extern int SPI_execute_plan_extended(SPIPlanPtr plan,
+                                     const SPIExecuteOptions *options);
 extern int SPI_execute_plan_with_paramlist(SPIPlanPtr plan,
                                            ParamListInfo params,
                                            bool read_only, long tcount);
-extern int SPI_execute_plan_with_receiver(SPIPlanPtr plan,
-                                          ParamListInfo params,
-                                          bool read_only, long tcount,
-                                          DestReceiver *dest);
 extern int SPI_exec(const char *src, long tcount);
 extern int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls,
                      long tcount);
index ce0f58ce687b075a6995d5a5fe53d23164503dee..97f4279ac4ad9d9e111f42b5360ca2a77abd1468 100644 (file)
@@ -92,7 +92,6 @@ typedef struct _SPI_plan
    int         magic;          /* should equal _SPI_PLAN_MAGIC */
    bool        saved;          /* saved or unsaved plan? */
    bool        oneshot;        /* one-shot plan? */
-   bool        no_snapshots;   /* let the caller handle the snapshots */
    List       *plancache_list; /* one CachedPlanSource per parsetree */
    MemoryContext plancxt;      /* Context containing _SPI_plan and data */
    RawParseMode parse_mode;    /* raw_parser() mode */
index 79d96e5ed03320f8f50067d5adfa0ed5389c0b93..ff09c63a02f1465d5268e57f6b58d053b7650c54 100644 (file)
@@ -219,9 +219,9 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
 
 extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
                                 ParamListInfo boundParams,
-                                bool useResOwner,
+                                ResourceOwner owner,
                                 QueryEnvironment *queryEnv);
-extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern void ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner);
 
 extern bool CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
                                                CachedPlan *plan,
index 5336793a9335ebd696edc938f43f13e9d9688e6a..ce8d97447d1d36d68501cad0c7bf0f6c134e2393 100644 (file)
@@ -369,6 +369,7 @@ do_compile(FunctionCallInfo fcinfo,
    function->fn_prokind = procStruct->prokind;
 
    function->nstatements = 0;
+   function->requires_procedure_resowner = false;
 
    /*
     * Initialize the compiler, particularly the namespace stack.  The
@@ -903,6 +904,7 @@ plpgsql_compile_inline(char *proc_source)
    function->extra_errors = 0;
 
    function->nstatements = 0;
+   function->requires_procedure_resowner = false;
 
    plpgsql_ns_init();
    plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
index 3a9349b724261e030dfa65b48b7b94db246d90c8..383d92fc1d054c8017a60cc180f80c3c07e8712f 100644 (file)
@@ -26,7 +26,6 @@
 #include "commands/defrem.h"
 #include "executor/execExpr.h"
 #include "executor/spi.h"
-#include "executor/spi_priv.h"
 #include "executor/tstoreReceiver.h"
 #include "funcapi.h"
 #include "mb/stringinfo_mb.h"
@@ -329,8 +328,7 @@ static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 static void exec_eval_cleanup(PLpgSQL_execstate *estate);
 
 static void exec_prepare_plan(PLpgSQL_execstate *estate,
-                             PLpgSQL_expr *expr, int cursorOptions,
-                             bool keepplan);
+                             PLpgSQL_expr *expr, int cursorOptions);
 static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
 static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan);
 static void exec_check_rw_parameter(PLpgSQL_expr *expr);
@@ -446,6 +444,8 @@ static char *format_expr_params(PLpgSQL_execstate *estate,
                                const PLpgSQL_expr *expr);
 static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
                                       ParamListInfo paramLI);
+static PLpgSQL_variable *make_callstmt_target(PLpgSQL_execstate *estate,
+                                             PLpgSQL_expr *expr);
 
 
 /* ----------
@@ -460,12 +460,18 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
  * shared_simple_eval_resowner.  (When using a private simple_eval_estate,
  * we must also use a private cast hashtable, but that's taken care of
  * within plpgsql_estate_setup.)
+ * procedure_resowner is a resowner that will survive for the duration
+ * of execution of this function/procedure.  It is needed only if we
+ * are doing non-atomic execution and there are CALL or DO statements
+ * in the function; otherwise it can be NULL.  We use it to hold refcounts
+ * on the CALL/DO statements' plans.
  * ----------
  */
 Datum
 plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
                      EState *simple_eval_estate,
                      ResourceOwner simple_eval_resowner,
+                     ResourceOwner procedure_resowner,
                      bool atomic)
 {
    PLpgSQL_execstate estate;
@@ -478,6 +484,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
     */
    plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
                         simple_eval_estate, simple_eval_resowner);
+   estate.procedure_resowner = procedure_resowner;
    estate.atomic = atomic;
 
    /*
@@ -2150,233 +2157,65 @@ static int
 exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
 {
    PLpgSQL_expr *expr = stmt->expr;
-   SPIPlanPtr  orig_plan = expr->plan;
-   bool        local_plan;
-   PLpgSQL_variable *volatile cur_target = stmt->target;
-   volatile LocalTransactionId before_lxid;
+   LocalTransactionId before_lxid;
    LocalTransactionId after_lxid;
-   volatile bool pushed_active_snap = false;
-   volatile int rc;
+   bool        pushed_active_snap = false;
+   ParamListInfo paramLI;
+   SPIExecuteOptions options;
+   int         rc;
 
    /*
-    * If not in atomic context, we make a local plan that we'll just use for
-    * this invocation, and will free at the end.  Otherwise, transaction ends
-    * would cause errors about plancache leaks.
-    *
-    * XXX This would be fixable with some plancache/resowner surgery
-    * elsewhere, but for now we'll just work around this here.
+    * Make a plan if we don't have one already.
     */
-   local_plan = !estate->atomic;
-
-   /* PG_TRY to ensure we clear the plan link, if needed, on failure */
-   PG_TRY();
+   if (expr->plan == NULL)
    {
-       SPIPlanPtr  plan = expr->plan;
-       ParamListInfo paramLI;
+       exec_prepare_plan(estate, expr, 0);
 
        /*
-        * Make a plan if we don't have one, or if we need a local one.  Note
-        * that we'll overwrite expr->plan either way; the PG_TRY block will
-        * ensure we undo that on the way out, if the plan is local.
+        * A CALL or DO can never be a simple expression.
         */
-       if (plan == NULL || local_plan)
-       {
-           /* Don't let SPI save the plan if it's going to be local */
-           exec_prepare_plan(estate, expr, 0, !local_plan);
-           plan = expr->plan;
-
-           /*
-            * A CALL or DO can never be a simple expression.  (If it could
-            * be, we'd have to worry about saving/restoring the previous
-            * values of the related expr fields, not just expr->plan.)
-            */
-           Assert(!expr->expr_simple_expr);
-
-           /*
-            * The procedure call could end transactions, which would upset
-            * the snapshot management in SPI_execute*, so don't let it do it.
-            * Instead, we set the snapshots ourselves below.
-            */
-           plan->no_snapshots = true;
-
-           /*
-            * Force target to be recalculated whenever the plan changes, in
-            * case the procedure's argument list has changed.
-            */
-           stmt->target = NULL;
-           cur_target = NULL;
-       }
+       Assert(!expr->expr_simple_expr);
 
        /*
-        * We construct a DTYPE_ROW datum representing the plpgsql variables
+        * Also construct a DTYPE_ROW datum representing the plpgsql variables
         * associated with the procedure's output arguments.  Then we can use
         * exec_move_row() to do the assignments.
-        *
-        * If we're using a local plan, also make a local target; otherwise,
-        * since the above code will force a new plan each time through, we'd
-        * repeatedly leak the memory for the target.  (Note: we also leak the
-        * target when a plan change is forced, but that isn't so likely to
-        * cause excessive memory leaks.)
         */
-       if (stmt->is_call && cur_target == NULL)
-       {
-           Node       *node;
-           FuncExpr   *funcexpr;
-           HeapTuple   func_tuple;
-           List       *funcargs;
-           Oid        *argtypes;
-           char      **argnames;
-           char       *argmodes;
-           MemoryContext oldcontext;
-           PLpgSQL_row *row;
-           int         nfields;
-           int         i;
-           ListCell   *lc;
-
-           /* Use stmt_mcontext for any cruft accumulated here */
-           oldcontext = MemoryContextSwitchTo(get_stmt_mcontext(estate));
-
-           /*
-            * Get the parsed CallStmt, and look up the called procedure
-            */
-           node = linitial_node(Query,
-                                ((CachedPlanSource *) linitial(plan->plancache_list))->query_list)->utilityStmt;
-           if (node == NULL || !IsA(node, CallStmt))
-               elog(ERROR, "query for CALL statement is not a CallStmt");
-
-           funcexpr = ((CallStmt *) node)->funcexpr;
-
-           func_tuple = SearchSysCache1(PROCOID,
-                                        ObjectIdGetDatum(funcexpr->funcid));
-           if (!HeapTupleIsValid(func_tuple))
-               elog(ERROR, "cache lookup failed for function %u",
-                    funcexpr->funcid);
-
-           /*
-            * Extract function arguments, and expand any named-arg notation
-            */
-           funcargs = expand_function_arguments(funcexpr->args,
-                                                funcexpr->funcresulttype,
-                                                func_tuple);
-
-           /*
-            * Get the argument names and modes, too
-            */
-           get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes);
-
-           ReleaseSysCache(func_tuple);
-
-           /*
-            * Begin constructing row Datum; keep it in fn_cxt if it's to be
-            * long-lived.
-            */
-           if (!local_plan)
-               MemoryContextSwitchTo(estate->func->fn_cxt);
-
-           row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
-           row->dtype = PLPGSQL_DTYPE_ROW;
-           row->refname = "(unnamed row)";
-           row->lineno = -1;
-           row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs));
-
-           if (!local_plan)
-               MemoryContextSwitchTo(get_stmt_mcontext(estate));
-
-           /*
-            * Examine procedure's argument list.  Each output arg position
-            * should be an unadorned plpgsql variable (Datum), which we can
-            * insert into the row Datum.
-            */
-           nfields = 0;
-           i = 0;
-           foreach(lc, funcargs)
-           {
-               Node       *n = lfirst(lc);
-
-               if (argmodes &&
-                   (argmodes[i] == PROARGMODE_INOUT ||
-                    argmodes[i] == PROARGMODE_OUT))
-               {
-                   if (IsA(n, Param))
-                   {
-                       Param      *param = (Param *) n;
-
-                       /* paramid is offset by 1 (see make_datum_param()) */
-                       row->varnos[nfields++] = param->paramid - 1;
-                   }
-                   else
-                   {
-                       /* report error using parameter name, if available */
-                       if (argnames && argnames[i] && argnames[i][0])
-                           ereport(ERROR,
-                                   (errcode(ERRCODE_SYNTAX_ERROR),
-                                    errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
-                                           argnames[i])));
-                       else
-                           ereport(ERROR,
-                                   (errcode(ERRCODE_SYNTAX_ERROR),
-                                    errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
-                                           i + 1)));
-                   }
-               }
-               i++;
-           }
-
-           row->nfields = nfields;
-
-           cur_target = (PLpgSQL_variable *) row;
-
-           /* We can save and re-use the target datum, if it's not local */
-           if (!local_plan)
-               stmt->target = cur_target;
-
-           MemoryContextSwitchTo(oldcontext);
-       }
+       if (stmt->is_call)
+           stmt->target = make_callstmt_target(estate, expr);
+   }
 
-       paramLI = setup_param_list(estate, expr);
+   paramLI = setup_param_list(estate, expr);
 
-       before_lxid = MyProc->lxid;
+   before_lxid = MyProc->lxid;
 
-       /*
-        * Set snapshot only for non-read-only procedures, similar to SPI
-        * behavior.
-        */
-       if (!estate->readonly_func)
-       {
-           PushActiveSnapshot(GetTransactionSnapshot());
-           pushed_active_snap = true;
-       }
-
-       rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
-                                            estate->readonly_func, 0);
-   }
-   PG_CATCH();
+   /*
+    * The procedure call could end transactions, which would upset the
+    * snapshot management in SPI_execute*, so handle snapshots here instead.
+    * Set a snapshot only for non-read-only procedures, similar to SPI
+    * behavior.
+    */
+   if (!estate->readonly_func)
    {
-       /*
-        * If we are using a local plan, restore the old plan link.
-        */
-       if (local_plan)
-           expr->plan = orig_plan;
-       PG_RE_THROW();
+       PushActiveSnapshot(GetTransactionSnapshot());
+       pushed_active_snap = true;
    }
-   PG_END_TRY();
 
    /*
-    * If we are using a local plan, restore the old plan link; then free the
-    * local plan to avoid memory leaks.  (Note that the error exit path above
-    * just clears the link without risking calling SPI_freeplan; we expect
-    * that xact cleanup will take care of the mess in that case.)
+    * If we have a procedure-lifespan resowner, use that to hold the refcount
+    * for the plan.  This avoids refcount leakage complaints if the called
+    * procedure ends the current transaction.
     */
-   if (local_plan)
-   {
-       SPIPlanPtr  plan = expr->plan;
+   memset(&options, 0, sizeof(options));
+   options.params = paramLI;
+   options.read_only = estate->readonly_func;
+   options.no_snapshots = true;    /* disable SPI's snapshot management */
+   options.owner = estate->procedure_resowner;
 
-       expr->plan = orig_plan;
-       SPI_freeplan(plan);
-   }
+   rc = SPI_execute_plan_extended(expr->plan, &options);
 
    if (rc < 0)
-       elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
+       elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
             expr->query, SPI_result_code_string(rc));
 
    after_lxid = MyProc->lxid;
@@ -2410,10 +2249,10 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
    {
        SPITupleTable *tuptab = SPI_tuptable;
 
-       if (!cur_target)
+       if (!stmt->is_call)
            elog(ERROR, "DO statement returned a row");
 
-       exec_move_row(estate, cur_target, tuptab->vals[0], tuptab->tupdesc);
+       exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc);
    }
    else if (SPI_processed > 1)
        elog(ERROR, "procedure call returned more than one row");
@@ -2424,6 +2263,128 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
    return PLPGSQL_RC_OK;
 }
 
+/*
+ * We construct a DTYPE_ROW datum representing the plpgsql variables
+ * associated with the procedure's output arguments.  Then we can use
+ * exec_move_row() to do the assignments.
+ */
+static PLpgSQL_variable *
+make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
+{
+   List       *plansources;
+   CachedPlanSource *plansource;
+   Node       *node;
+   FuncExpr   *funcexpr;
+   HeapTuple   func_tuple;
+   List       *funcargs;
+   Oid        *argtypes;
+   char      **argnames;
+   char       *argmodes;
+   MemoryContext oldcontext;
+   PLpgSQL_row *row;
+   int         nfields;
+   int         i;
+   ListCell   *lc;
+
+   /* Use eval_mcontext for any cruft accumulated here */
+   oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+   /*
+    * Get the parsed CallStmt, and look up the called procedure
+    */
+   plansources = SPI_plan_get_plan_sources(expr->plan);
+   if (list_length(plansources) != 1)
+       elog(ERROR, "query for CALL statement is not a CallStmt");
+   plansource = (CachedPlanSource *) linitial(plansources);
+   if (list_length(plansource->query_list) != 1)
+       elog(ERROR, "query for CALL statement is not a CallStmt");
+   node = linitial_node(Query, plansource->query_list)->utilityStmt;
+   if (node == NULL || !IsA(node, CallStmt))
+       elog(ERROR, "query for CALL statement is not a CallStmt");
+
+   funcexpr = ((CallStmt *) node)->funcexpr;
+
+   func_tuple = SearchSysCache1(PROCOID,
+                                ObjectIdGetDatum(funcexpr->funcid));
+   if (!HeapTupleIsValid(func_tuple))
+       elog(ERROR, "cache lookup failed for function %u",
+            funcexpr->funcid);
+
+   /*
+    * Extract function arguments, and expand any named-arg notation
+    */
+   funcargs = expand_function_arguments(funcexpr->args,
+                                        funcexpr->funcresulttype,
+                                        func_tuple);
+
+   /*
+    * Get the argument names and modes, too
+    */
+   get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes);
+
+   ReleaseSysCache(func_tuple);
+
+   /*
+    * Begin constructing row Datum; keep it in fn_cxt so it's adequately
+    * long-lived.
+    */
+   MemoryContextSwitchTo(estate->func->fn_cxt);
+
+   row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
+   row->dtype = PLPGSQL_DTYPE_ROW;
+   row->refname = "(unnamed row)";
+   row->lineno = -1;
+   row->varnos = (int *) palloc(sizeof(int) * list_length(funcargs));
+
+   MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+   /*
+    * Examine procedure's argument list.  Each output arg position should be
+    * an unadorned plpgsql variable (Datum), which we can insert into the row
+    * Datum.
+    */
+   nfields = 0;
+   i = 0;
+   foreach(lc, funcargs)
+   {
+       Node       *n = lfirst(lc);
+
+       if (argmodes &&
+           (argmodes[i] == PROARGMODE_INOUT ||
+            argmodes[i] == PROARGMODE_OUT))
+       {
+           if (IsA(n, Param))
+           {
+               Param      *param = (Param *) n;
+
+               /* paramid is offset by 1 (see make_datum_param()) */
+               row->varnos[nfields++] = param->paramid - 1;
+           }
+           else
+           {
+               /* report error using parameter name, if available */
+               if (argnames && argnames[i] && argnames[i][0])
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
+                                   argnames[i])));
+               else
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
+                                   i + 1)));
+           }
+       }
+       i++;
+   }
+
+   row->nfields = nfields;
+
+   MemoryContextSwitchTo(oldcontext);
+
+   return (PLpgSQL_variable *) row;
+}
+
 /* ----------
  * exec_stmt_getdiag                   Put internal PG information into
  *                                     specified variables.
@@ -2960,7 +2921,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
    Assert(query);
 
    if (query->plan == NULL)
-       exec_prepare_plan(estate, query, curvar->cursor_options, true);
+       exec_prepare_plan(estate, query, curvar->cursor_options);
 
    /*
     * Set up ParamListInfo for this query
@@ -3607,12 +3568,13 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
        /* static query */
        PLpgSQL_expr *expr = stmt->query;
        ParamListInfo paramLI;
+       SPIExecuteOptions options;
 
        /*
         * On the first call for this expression generate the plan.
         */
        if (expr->plan == NULL)
-           exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
+           exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
 
        /*
         * Set up ParamListInfo to pass to executor
@@ -3622,9 +3584,12 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
        /*
         * Execute the query
         */
-       rc = SPI_execute_plan_with_receiver(expr->plan, paramLI,
-                                           estate->readonly_func, 0,
-                                           treceiver);
+       memset(&options, 0, sizeof(options));
+       options.params = paramLI;
+       options.read_only = estate->readonly_func;
+       options.dest = treceiver;
+
+       rc = SPI_execute_plan_extended(expr->plan, &options);
        if (rc != SPI_OK_SELECT)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
@@ -4091,6 +4056,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
    else
        estate->simple_eval_resowner = shared_simple_eval_resowner;
 
+   /* if there's a procedure resowner, it'll be filled in later */
+   estate->procedure_resowner = NULL;
+
    /*
     * We start with no stmt_mcontext; one will be created only if needed.
     * That context will be a direct child of the function's main execution
@@ -4159,8 +4127,7 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
  */
 static void
 exec_prepare_plan(PLpgSQL_execstate *estate,
-                 PLpgSQL_expr *expr, int cursorOptions,
-                 bool keepplan)
+                 PLpgSQL_expr *expr, int cursorOptions)
 {
    SPIPlanPtr  plan;
    SPIPrepareOptions options;
@@ -4183,8 +4150,8 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
    if (plan == NULL)
        elog(ERROR, "SPI_prepare_extended failed for \"%s\": %s",
             expr->query, SPI_result_code_string(SPI_result));
-   if (keepplan)
-       SPI_keepplan(plan);
+
+   SPI_keepplan(plan);
    expr->plan = plan;
 
    /* Check to see if it's a simple expression */
@@ -4222,7 +4189,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
    {
        ListCell   *l;
 
-       exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
+       exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
        stmt->mod_stmt = false;
        foreach(l, SPI_plan_get_plan_sources(expr->plan))
        {
@@ -4681,7 +4648,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
         */
        query = stmt->query;
        if (query->plan == NULL)
-           exec_prepare_plan(estate, query, stmt->cursor_options, true);
+           exec_prepare_plan(estate, query, stmt->cursor_options);
    }
    else if (stmt->dynquery != NULL)
    {
@@ -4752,7 +4719,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 
        query = curvar->cursor_explicit_expr;
        if (query->plan == NULL)
-           exec_prepare_plan(estate, query, curvar->cursor_options, true);
+           exec_prepare_plan(estate, query, curvar->cursor_options);
    }
 
    /*
@@ -4985,18 +4952,20 @@ static int
 exec_stmt_set(PLpgSQL_execstate *estate, PLpgSQL_stmt_set *stmt)
 {
    PLpgSQL_expr *expr = stmt->expr;
+   SPIExecuteOptions options;
    int         rc;
 
    if (expr->plan == NULL)
-   {
-       exec_prepare_plan(estate, expr, 0, true);
-       expr->plan->no_snapshots = true;
-   }
+       exec_prepare_plan(estate, expr, 0);
+
+   memset(&options, 0, sizeof(options));
+   options.read_only = estate->readonly_func;
+   options.no_snapshots = true;    /* disable SPI's snapshot management */
 
-   rc = SPI_execute_plan(expr->plan, NULL, NULL, estate->readonly_func, 0);
+   rc = SPI_execute_plan_extended(expr->plan, &options);
 
    if (rc != SPI_OK_UTILITY)
-       elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
+       elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
             expr->query, SPI_result_code_string(rc));
 
    return PLPGSQL_RC_OK;
@@ -5032,7 +5001,7 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
        else
            expr->target_param = -1;    /* should be that already */
 
-       exec_prepare_plan(estate, expr, 0, true);
+       exec_prepare_plan(estate, expr, 0);
    }
 
    value = exec_eval_expr(estate, expr, &isnull, &valtype, &valtypmod);
@@ -5697,7 +5666,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
     * If first time through, create a plan for this expression.
     */
    if (expr->plan == NULL)
-       exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
+       exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
 
    /*
     * If this is a simple expression, bypass SPI and use the executor
@@ -5783,7 +5752,7 @@ exec_run_select(PLpgSQL_execstate *estate,
     */
    if (expr->plan == NULL)
        exec_prepare_plan(estate, expr,
-                         portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0, true);
+                         portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
 
    /*
     * Set up ParamListInfo to pass to executor
@@ -6056,11 +6025,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
         */
        if (expr->expr_simple_plan_lxid == curlxid)
        {
-           ResourceOwner saveResourceOwner = CurrentResourceOwner;
-
-           CurrentResourceOwner = estate->simple_eval_resowner;
-           ReleaseCachedPlan(expr->expr_simple_plan, true);
-           CurrentResourceOwner = saveResourceOwner;
+           ReleaseCachedPlan(expr->expr_simple_plan,
+                             estate->simple_eval_resowner);
            expr->expr_simple_plan = NULL;
            expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
        }
@@ -6094,7 +6060,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
        else
        {
            /* Release SPI_plan_get_cached_plan's refcount */
-           ReleaseCachedPlan(cplan, true);
+           ReleaseCachedPlan(cplan, CurrentResourceOwner);
            /* Mark expression as non-simple, and fail */
            expr->expr_simple_expr = NULL;
            expr->expr_rw_param = NULL;
@@ -6105,7 +6071,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
         * SPI_plan_get_cached_plan acquired a plan refcount stored in the
         * active resowner.  We don't need that anymore, so release it.
         */
-       ReleaseCachedPlan(cplan, true);
+       ReleaseCachedPlan(cplan, CurrentResourceOwner);
 
        /* Extract desired scalar expression from cached plan */
        exec_save_simple_expr(expr, cplan);
@@ -8023,7 +7989,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
     * Release the plan refcount obtained by SPI_plan_get_cached_plan.  (This
     * refcount is held by the wrong resowner, so we can't just repurpose it.)
     */
-   ReleaseCachedPlan(cplan, true);
+   ReleaseCachedPlan(cplan, CurrentResourceOwner);
 }
 
 /*
index 0b0ff4e5de28d78936356d8b7f93df192d29c58d..abf196d4be14964216349d56de43a17b171c2541 100644 (file)
@@ -951,6 +951,9 @@ stmt_call       : K_CALL
                        new->expr = read_sql_stmt();
                        new->is_call = true;
 
+                       /* Remember we may need a procedure resource owner */
+                       plpgsql_curr_compile->requires_procedure_resowner = true;
+
                        $$ = (PLpgSQL_stmt *)new;
 
                    }
@@ -967,6 +970,9 @@ stmt_call       : K_CALL
                        new->expr = read_sql_stmt();
                        new->is_call = false;
 
+                       /* Remember we may need a procedure resource owner */
+                       plpgsql_curr_compile->requires_procedure_resowner = true;
+
                        $$ = (PLpgSQL_stmt *)new;
 
                    }
index 52e6c69cc559920f8b4cdf973723678b0d7ffbb5..97f4264c287265ea994d2448ad28b281d205e3a3 100644 (file)
@@ -224,6 +224,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
    bool        nonatomic;
    PLpgSQL_function *func;
    PLpgSQL_execstate *save_cur_estate;
+   ResourceOwner procedure_resowner = NULL;
    Datum       retval;
    int         rc;
 
@@ -246,6 +247,17 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
    /* Mark the function as busy, so it can't be deleted from under us */
    func->use_count++;
 
+   /*
+    * If we'll need a procedure-lifespan resowner to execute any CALL or DO
+    * statements, create it now.  Since this resowner is not tied to any
+    * parent, failing to free it would result in process-lifespan leaks.
+    * Therefore, be very wary of adding any code between here and the PG_TRY
+    * block.
+    */
+   if (nonatomic && func->requires_procedure_resowner)
+       procedure_resowner =
+           ResourceOwnerCreate(NULL, "PL/pgSQL procedure resources");
+
    PG_TRY();
    {
        /*
@@ -264,6 +276,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
        else
            retval = plpgsql_exec_function(func, fcinfo,
                                           NULL, NULL,
+                                          procedure_resowner,
                                           !nonatomic);
    }
    PG_FINALLY();
@@ -271,6 +284,13 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
        /* Decrement use-count, restore cur_estate */
        func->use_count--;
        func->cur_estate = save_cur_estate;
+
+       /* Be sure to release the procedure resowner if any */
+       if (procedure_resowner)
+       {
+           ResourceOwnerReleaseAllPlanCacheRefs(procedure_resowner);
+           ResourceOwnerDelete(procedure_resowner);
+       }
    }
    PG_END_TRY();
 
@@ -333,6 +353,10 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
     * unconditionally try to clean them up below.  (Hence, be wary of adding
     * anything that could fail between here and the PG_TRY block.)  See the
     * comments for shared_simple_eval_estate.
+    *
+    * Because this resowner isn't tied to the calling transaction, we can
+    * also use it as the "procedure" resowner for any CALL statements.  That
+    * helps reduce the opportunities for failure here.
     */
    simple_eval_estate = CreateExecutorState();
    simple_eval_resowner =
@@ -344,6 +368,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
        retval = plpgsql_exec_function(func, fake_fcinfo,
                                       simple_eval_estate,
                                       simple_eval_resowner,
+                                      simple_eval_resowner,    /* see above */
                                       codeblock->atomic);
    }
    PG_CATCH();
index 416fda7f3b5c6473f4304a3fa1067929e10b2b5b..d5010862a5035c0314af9119db24f011d3f8bde6 100644 (file)
@@ -1009,9 +1009,6 @@ typedef struct PLpgSQL_function
    int         extra_warnings;
    int         extra_errors;
 
-   /* count of statements inside function */
-   unsigned int nstatements;
-
    /* the datums representing the function's local variables */
    int         ndatums;
    PLpgSQL_datum **datums;
@@ -1020,6 +1017,10 @@ typedef struct PLpgSQL_function
    /* function body parsetree */
    PLpgSQL_stmt_block *action;
 
+   /* data derived while parsing body */
+   unsigned int nstatements;   /* counter for assigning stmtids */
+   bool        requires_procedure_resowner;    /* contains CALL or DO? */
+
    /* these fields change when the function is used */
    struct PLpgSQL_execstate *cur_estate;
    unsigned long use_count;
@@ -1081,6 +1082,9 @@ typedef struct PLpgSQL_execstate
    EState     *simple_eval_estate;
    ResourceOwner simple_eval_resowner;
 
+   /* if running nonatomic procedure or DO block, resowner to use for CALL */
+   ResourceOwner procedure_resowner;
+
    /* lookup table to use for executing type casts */
    HTAB       *cast_hash;
    MemoryContext cast_hash_context;
@@ -1265,6 +1269,7 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func,
                                   FunctionCallInfo fcinfo,
                                   EState *simple_eval_estate,
                                   ResourceOwner simple_eval_resowner,
+                                  ResourceOwner procedure_resowner,
                                   bool atomic);
 extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
                                      TriggerData *trigdata);