Previous fix for temporary file management broke returning a set from
authorHeikki Linnakangas <[email protected]>
Tue, 29 Dec 2009 17:40:59 +0000 (17:40 +0000)
committerHeikki Linnakangas <[email protected]>
Tue, 29 Dec 2009 17:40:59 +0000 (17:40 +0000)
PL/pgSQL function within an exception handler. Make sure we use the right
resource owner when we create the tuplestore to hold returned tuples.

Simplify tuplestore API so that the caller doesn't need to be in the right
memory context when calling tuplestore_put* functions. tuplestore.c
automatically switches to the memory context used when the tuplestore was
created. Tuplesort was already modified like this earlier. This patch also
removes the now useless MemoryContextSwitch calls from callers.

Report by Aleksei on pgsql-bugs on Dec 22 2009. Backpatch to 8.1, like
the previous patch that broke this.

14 files changed:
contrib/dblink/dblink.c
contrib/pg_stat_statements/pg_stat_statements.c
contrib/tablefunc/tablefunc.c
contrib/xml2/xpath.c
src/backend/commands/prepare.c
src/backend/executor/execQual.c
src/backend/executor/functions.c
src/backend/executor/nodeWindowAgg.c
src/backend/executor/tstoreReceiver.c
src/backend/utils/mmgr/portalmem.c
src/backend/utils/sort/tuplestore.c
src/pl/plperl/plperl.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/plpgsql.h

index 72fdf56a711e0935aee9435f15e3bfcfdaef3e33..9309da85fac77a4d29c58b017a7d2d21171a16d5 100644 (file)
@@ -1703,10 +1703,7 @@ dblink_get_notify(PG_FUNCTION_ARGS)
        else
            nulls[2] = true;
 
-       /* switch to appropriate context while storing the tuple */
-       MemoryContextSwitchTo(per_query_ctx);
        tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-       MemoryContextSwitchTo(oldcontext);
 
        PQfreemem(notify);
        PQconsumeInput(conn);
index 1c958ad0ba99689244438aaddf63160baaf713c7..964f892eb34172251adc88d25632a0d0493e77d3 100644 (file)
@@ -783,6 +783,8 @@ pg_stat_statements(PG_FUNCTION_ARGS)
    rsinfo->setResult = tupstore;
    rsinfo->setDesc = tupdesc;
 
+   MemoryContextSwitchTo(oldcontext);
+
    LWLockAcquire(pgss->lock, LW_SHARED);
 
    hash_seq_init(&hash_seq, pgss_hash);
@@ -793,9 +795,6 @@ pg_stat_statements(PG_FUNCTION_ARGS)
        int         i = 0;
        Counters    tmp;
 
-       /* generate junk in short-term context */
-       MemoryContextSwitchTo(oldcontext);
-
        memset(values, 0, sizeof(values));
        memset(nulls, 0, sizeof(nulls));
 
@@ -833,8 +832,6 @@ pg_stat_statements(PG_FUNCTION_ARGS)
 
        Assert(i == PG_STAT_STATEMENTS_COLS);
 
-       /* switch to appropriate context while storing the tuple */
-       MemoryContextSwitchTo(per_query_ctx);
        tuplestore_putvalues(tupstore, tupdesc, values, nulls);
    }
 
@@ -843,8 +840,6 @@ pg_stat_statements(PG_FUNCTION_ARGS)
    /* clean up and return the tuplestore */
    tuplestore_donestoring(tupstore);
 
-   MemoryContextSwitchTo(oldcontext);
-
    return (Datum) 0;
 }
 
index c9fddf0ac7d2e4b8649f88b012b534491c7903f2..46823b22a07222d42c064557e0fa9b5e93e03350 100644 (file)
@@ -567,14 +567,9 @@ crosstab(PG_FUNCTION_ARGS)
        {
            HeapTuple   tuple;
 
-           /* build the tuple */
+           /* build the tuple and store it */
            tuple = BuildTupleFromCStrings(attinmeta, values);
-
-           /* switch to appropriate context while storing the tuple */
-           oldcontext = MemoryContextSwitchTo(per_query_ctx);
            tuplestore_puttuple(tupstore, tuple);
-           MemoryContextSwitchTo(oldcontext);
-
            heap_freetuple(tuple);
        }
 
@@ -807,7 +802,6 @@ get_crosstab_tuplestore(char *sql,
    HeapTuple   tuple;
    int         ret;
    int         proc;
-   MemoryContext SPIcontext;
 
    /* initialize our tuplestore (while still in query context!) */
    tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -907,10 +901,7 @@ get_crosstab_tuplestore(char *sql,
                    /* rowid changed, flush the previous output row */
                    tuple = BuildTupleFromCStrings(attinmeta, values);
 
-                   /* switch to appropriate context while storing the tuple */
-                   SPIcontext = MemoryContextSwitchTo(per_query_ctx);
                    tuplestore_puttuple(tupstore, tuple);
-                   MemoryContextSwitchTo(SPIcontext);
 
                    for (j = 0; j < result_ncols; j++)
                        xpfree(values[j]);
@@ -943,10 +934,7 @@ get_crosstab_tuplestore(char *sql,
        /* flush the last output row */
        tuple = BuildTupleFromCStrings(attinmeta, values);
 
-       /* switch to appropriate context while storing the tuple */
-       SPIcontext = MemoryContextSwitchTo(per_query_ctx);
        tuplestore_puttuple(tupstore, tuple);
-       MemoryContextSwitchTo(SPIcontext);
    }
 
    if (SPI_finish() != SPI_OK_FINISH)
@@ -1232,7 +1220,6 @@ build_tuplestore_recursively(char *key_fld,
                             Tuplestorestate *tupstore)
 {
    TupleDesc   tupdesc = attinmeta->tupdesc;
-   MemoryContext oldcontext;
    int         ret;
    int         proc;
    int         serial_column;
@@ -1310,15 +1297,9 @@ build_tuplestore_recursively(char *key_fld,
        /* construct the tuple */
        tuple = BuildTupleFromCStrings(attinmeta, values);
 
-       /* switch to long lived context while storing the tuple */
-       oldcontext = MemoryContextSwitchTo(per_query_ctx);
-
        /* now store it */
        tuplestore_puttuple(tupstore, tuple);
 
-       /* now reset the context */
-       MemoryContextSwitchTo(oldcontext);
-
        /* increment level */
        level++;
    }
@@ -1404,15 +1385,9 @@ build_tuplestore_recursively(char *key_fld,
            xpfree(current_key);
            xpfree(current_key_parent);
 
-           /* switch to long lived context while storing the tuple */
-           oldcontext = MemoryContextSwitchTo(per_query_ctx);
-
            /* store the tuple for later use */
            tuplestore_puttuple(tupstore, tuple);
 
-           /* now reset the context */
-           MemoryContextSwitchTo(oldcontext);
-
            heap_freetuple(tuple);
 
            /* recurse using current_key_parent as the new start_with */
index cf7f66ca44a2989d24b414a112e67da7c301f46c..bbf1467b1cd2d16a95bfc0bc60e10c5f4e0369ae 100644 (file)
@@ -821,9 +821,7 @@ xpath_table(PG_FUNCTION_ARGS)
        {
            /* not well-formed, so output all-NULL tuple */
            ret_tuple = BuildTupleFromCStrings(attinmeta, values);
-           oldcontext = MemoryContextSwitchTo(per_query_ctx);
            tuplestore_puttuple(tupstore, ret_tuple);
-           MemoryContextSwitchTo(oldcontext);
            heap_freetuple(ret_tuple);
        }
        else
@@ -897,9 +895,7 @@ xpath_table(PG_FUNCTION_ARGS)
                if (had_values)
                {
                    ret_tuple = BuildTupleFromCStrings(attinmeta, values);
-                   oldcontext = MemoryContextSwitchTo(per_query_ctx);
                    tuplestore_puttuple(tupstore, ret_tuple);
-                   MemoryContextSwitchTo(oldcontext);
                    heap_freetuple(ret_tuple);
                }
 
index ad344c8eeeda3d94e4179f2bde1429d93771378f..46e8d2da26f68ef9ca4d66839f10d72bf59c349c 100644 (file)
@@ -779,6 +779,9 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
        tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
                              false, work_mem);
 
+   /* generate junk in short-term context */
+   MemoryContextSwitchTo(oldcontext);
+
    /* hash table might be uninitialized */
    if (prepared_queries)
    {
@@ -791,9 +794,6 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
            Datum       values[5];
            bool        nulls[5];
 
-           /* generate junk in short-term context */
-           MemoryContextSwitchTo(oldcontext);
-
            MemSet(nulls, 0, sizeof(nulls));
 
            values[0] = CStringGetTextDatum(prep_stmt->stmt_name);
@@ -803,8 +803,6 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
                                          prep_stmt->plansource->num_params);
            values[4] = BoolGetDatum(prep_stmt->from_sql);
 
-           /* switch to appropriate context while storing the tuple */
-           MemoryContextSwitchTo(per_query_ctx);
            tuplestore_putvalues(tupstore, tupdesc, values, nulls);
        }
    }
@@ -812,8 +810,6 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
    /* clean up and return the tuplestore */
    tuplestore_donestoring(tupstore);
 
-   MemoryContextSwitchTo(oldcontext);
-
    rsinfo->returnMode = SFRM_Materialize;
    rsinfo->setResult = tupstore;
    rsinfo->setDesc = tupdesc;
index 7d9af2454ff6b3ce39d88a9c8d8cb4b31fb6dfdd..d58e6321feaba778480e3fc7c84bce7c0e5bb269 100644 (file)
@@ -2038,15 +2038,10 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
                tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
                tmptup.t_data = td;
 
-               oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
                tuplestore_puttuple(tupstore, &tmptup);
            }
            else
-           {
-               oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
                tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
-           }
-           MemoryContextSwitchTo(oldcontext);
 
            /*
             * Are we done?
index 526f1f36a529e90dfeea94dc77430cc372b22ca4..3395a0ec1ce1912f53cbcec36c06bab077e07d5f 100644 (file)
@@ -1405,15 +1405,12 @@ static void
 sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
 {
    DR_sqlfunction *myState = (DR_sqlfunction *) self;
-   MemoryContext oldcxt;
 
    /* Filter tuple as needed */
    slot = ExecFilterJunk(myState->filter, slot);
 
    /* Store the filtered tuple into the tuplestore */
-   oldcxt = MemoryContextSwitchTo(myState->cxt);
    tuplestore_puttupleslot(myState->tstore, slot);
-   MemoryContextSwitchTo(oldcxt);
 }
 
 /*
index 3a04f93659a96fe1311d3d705fbe1ab399149dda..9ab8a7e6f9ebd5ea3dafa2c35eff8b9a4214cf40 100644 (file)
@@ -723,7 +723,7 @@ spool_tuples(WindowAggState *winstate, int64 pos)
 
    outerPlan = outerPlanState(winstate);
 
-   /* Must be in query context to call outerplan or touch tuplestore */
+   /* Must be in query context to call outerplan */
    oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
    while (winstate->spooled_rows <= pos || pos == -1)
index 7fa5bd0fece4d2926229904f999a173de008786d..2402bbc1dd8126a27d8dacf2ddcd151fd9db91c6 100644 (file)
@@ -94,11 +94,8 @@ static void
 tstoreReceiveSlot_notoast(TupleTableSlot *slot, DestReceiver *self)
 {
    TStoreState *myState = (TStoreState *) self;
-   MemoryContext oldcxt = MemoryContextSwitchTo(myState->cxt);
 
    tuplestore_puttupleslot(myState->tstore, slot);
-
-   MemoryContextSwitchTo(oldcxt);
 }
 
 /*
index 3234cf33a9e215beb0c783a620034c2af490f79d..752b2a8c7927fd4564b8037d62273156a599e376 100644 (file)
@@ -923,6 +923,9 @@ pg_cursor(PG_FUNCTION_ARGS)
        tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
                              false, work_mem);
 
+   /* generate junk in short-term context */
+   MemoryContextSwitchTo(oldcontext);
+
    hash_seq_init(&hash_seq, PortalHashTable);
    while ((hentry = hash_seq_search(&hash_seq)) != NULL)
    {
@@ -934,9 +937,6 @@ pg_cursor(PG_FUNCTION_ARGS)
        if (!portal->visible)
            continue;
 
-       /* generate junk in short-term context */
-       MemoryContextSwitchTo(oldcontext);
-
        MemSet(nulls, 0, sizeof(nulls));
 
        values[0] = CStringGetTextDatum(portal->name);
@@ -946,16 +946,12 @@ pg_cursor(PG_FUNCTION_ARGS)
        values[4] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_SCROLL);
        values[5] = TimestampTzGetDatum(portal->creation_time);
 
-       /* switch to appropriate context while storing the tuple */
-       MemoryContextSwitchTo(per_query_ctx);
        tuplestore_putvalues(tupstore, tupdesc, values, nulls);
    }
 
    /* clean up and return the tuplestore */
    tuplestore_donestoring(tupstore);
 
-   MemoryContextSwitchTo(oldcontext);
-
    rsinfo->returnMode = SFRM_Materialize;
    rsinfo->setResult = tupstore;
    rsinfo->setDesc = tupdesc;
index bb605dd00ae91be78a53ff06cbcdc90f83af1333..c5dd85afa3a9bd780bb6188a0c5af88925617987 100644 (file)
@@ -58,6 +58,7 @@
 #include "executor/executor.h"
 #include "storage/buffile.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/tuplestore.h"
 
 
@@ -105,6 +106,8 @@ struct Tuplestorestate
    bool        truncated;      /* tuplestore_trim has removed tuples? */
    long        availMem;       /* remaining memory available, in bytes */
    BufFile    *myfile;         /* underlying file, or NULL if none */
+   MemoryContext context;      /* memory context for holding tuples */
+   ResourceOwner resowner;     /* resowner for holding temp files */
 
    /*
     * These function pointers decouple the routines that must know what kind
@@ -246,6 +249,8 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
    state->truncated = false;
    state->availMem = maxKBytes * 1024L;
    state->myfile = NULL;
+   state->context = CurrentMemoryContext;
+   state->resowner = CurrentResourceOwner;
 
    state->memtupcount = 0;
    state->memtupsize = 1024;   /* initial guess */
@@ -278,9 +283,9 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
  *
  * interXact: if true, the files used for on-disk storage persist beyond the
  * end of the current transaction. NOTE: It's the caller's responsibility to
- * create such a tuplestore in a memory context that will also survive
- * transaction boundaries, and to ensure the tuplestore is closed when it's
- * no longer wanted.
+ * create such a tuplestore in a memory context and resource owner that will
+ * also survive transaction boundaries, and to ensure the tuplestore is closed
+ * when it's no longer wanted.
  *
  * maxKBytes: how much data to store in memory (any data beyond this
  * amount is paged to disk).  When in doubt, use work_mem.
@@ -533,6 +538,7 @@ tuplestore_puttupleslot(Tuplestorestate *state,
                        TupleTableSlot *slot)
 {
    MinimalTuple tuple;
+   MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
 
    /*
     * Form a MinimalTuple in working memory
@@ -541,6 +547,8 @@ tuplestore_puttupleslot(Tuplestorestate *state,
    USEMEM(state, GetMemoryChunkSpace(tuple));
 
    tuplestore_puttuple_common(state, (void *) tuple);
+
+   MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -550,12 +558,16 @@ tuplestore_puttupleslot(Tuplestorestate *state,
 void
 tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
 {
+   MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
+
    /*
     * Copy the tuple.  (Must do this even in WRITEFILE case.)
     */
    tuple = COPYTUP(state, tuple);
 
    tuplestore_puttuple_common(state, (void *) tuple);
+
+   MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -568,10 +580,13 @@ tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc,
                     Datum *values, bool *isnull)
 {
    MinimalTuple tuple;
+   MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
 
    tuple = heap_form_minimal_tuple(tdesc, values, isnull);
 
    tuplestore_puttuple_common(state, (void *) tuple);
+
+   MemoryContextSwitchTo(oldcxt);  
 }
 
 static void
@@ -579,6 +594,7 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
 {
    TSReadPointer *readptr;
    int         i;
+   ResourceOwner oldowner;
 
    switch (state->status)
    {
@@ -635,8 +651,15 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
             * the temp file(s) are created in suitable temp tablespaces.
             */
            PrepareTempTablespaces();
+
+           /* associate the file with the store's resource owner */
+           oldowner = CurrentResourceOwner;
+           CurrentResourceOwner = state->resowner;
+
            state->myfile = BufFileCreateTemp(state->interXact);
 
+           CurrentResourceOwner = oldowner;
+
            /*
             * Freeze the decision about whether trailing length words will be
             * used.  We can't change this choice once data is on tape, even
index 1b0709897f6f468857e68c68d23ed51e25a88884..d200a4f56c197d3c0ddb0a0b8a8db1b3a5765a55 100644 (file)
@@ -2107,11 +2107,7 @@ plperl_return_next(SV *sv)
 
        tuple = plperl_build_tuple_result((HV *) SvRV(sv),
                                          current_call_data->attinmeta);
-
-       /* Make sure to store the tuple in a long-lived memory context */
-       MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
        tuplestore_puttuple(current_call_data->tuple_store, tuple);
-       MemoryContextSwitchTo(old_cxt);
    }
    else
    {
@@ -2141,14 +2137,12 @@ plperl_return_next(SV *sv)
            isNull = true;
        }
 
-       /* Make sure to store the tuple in a long-lived memory context */
-       MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
        tuplestore_putvalues(current_call_data->tuple_store,
                             current_call_data->ret_tdesc,
                             &ret, &isNull);
-       MemoryContextSwitchTo(old_cxt);
    }
 
+   MemoryContextSwitchTo(old_cxt);
    MemoryContextReset(current_call_data->tmp_cxt);
 }
 
index 2925af3f7595c2d15c40f35c413cb007092bb1e2..376f4dc1b673df74b66206fc6417a1f6c67dadab 100644 (file)
@@ -2172,7 +2172,6 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 {
    TupleDesc   tupdesc;
    int         natts;
-   MemoryContext oldcxt;
    HeapTuple   tuple = NULL;
    bool        free_tuple = false;
 
@@ -2212,10 +2211,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                                tupdesc->attrs[0]->atttypmod,
                                                    isNull);
 
-                   oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
                    tuplestore_putvalues(estate->tuple_store, tupdesc,
                                         &retval, &isNull);
-                   MemoryContextSwitchTo(oldcxt);
                }
                break;
 
@@ -2285,10 +2282,8 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
                                        tupdesc->attrs[0]->atttypmod,
                                        isNull);
 
-       oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
        tuplestore_putvalues(estate->tuple_store, tupdesc,
                             &retval, &isNull);
-       MemoryContextSwitchTo(oldcxt);
 
        exec_eval_cleanup(estate);
    }
@@ -2301,9 +2296,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 
    if (HeapTupleIsValid(tuple))
    {
-       oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
        tuplestore_puttuple(estate->tuple_store, tuple);
-       MemoryContextSwitchTo(oldcxt);
 
        if (free_tuple)
            heap_freetuple(tuple);
@@ -2353,14 +2346,12 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
 
    while (true)
    {
-       MemoryContext old_cxt;
        int         i;
 
        SPI_cursor_fetch(portal, true, 50);
        if (SPI_processed == 0)
            break;
 
-       old_cxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
        for (i = 0; i < SPI_processed; i++)
        {
            HeapTuple   tuple = SPI_tuptable->vals[i];
@@ -2372,7 +2363,6 @@ exec_stmt_return_query(PLpgSQL_execstate *estate,
                heap_freetuple(tuple);
            processed++;
        }
-       MemoryContextSwitchTo(old_cxt);
 
        SPI_freetuptable(SPI_tuptable);
    }
@@ -2394,6 +2384,7 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
 {
    ReturnSetInfo *rsi = estate->rsi;
    MemoryContext oldcxt;
+   ResourceOwner oldowner;
 
    /*
     * Check caller can handle a set result in the way we want
@@ -2405,12 +2396,22 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("set-valued function called in context that cannot accept a set")));
 
-   estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
-
+   /*
+    * Switch to the right memory context and resource owner for storing
+    * the tuplestore for return set. If we're within a subtransaction opened
+    * for an exception-block, for example, we must still create the
+    * tuplestore in the resource owner that was active when this function was
+    * entered, and not in the subtransaction resource owner.
+    */
    oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+   oldowner = CurrentResourceOwner;
+   CurrentResourceOwner = estate->tuple_store_owner;
+
    estate->tuple_store =
        tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
                              false, work_mem);
+
+   CurrentResourceOwner = oldowner;
    MemoryContextSwitchTo(oldcxt);
 
    estate->rettupdesc = rsi->expectedDesc;
@@ -2635,7 +2636,16 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
    estate->exitlabel = NULL;
 
    estate->tuple_store = NULL;
-   estate->tuple_store_cxt = NULL;
+   if (rsi)
+   {
+       estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
+       estate->tuple_store_owner = CurrentResourceOwner;
+   }
+   else
+   {
+       estate->tuple_store_cxt = NULL;
+       estate->tuple_store_owner = NULL;
+   }
    estate->rsi = rsi;
 
    estate->found_varno = func->found_varno;
index 9f6d144667cf7f281b9c4f21d40258d3c0b0079c..afdf6e5228bfdb8060bc0ed503ed11f535bd7565 100644 (file)
@@ -701,6 +701,7 @@ typedef struct PLpgSQL_execstate
 
    Tuplestorestate *tuple_store;       /* SRFs accumulate results here */
    MemoryContext tuple_store_cxt;
+   ResourceOwner tuple_store_owner;
    ReturnSetInfo *rsi;
 
    int         found_varno;