]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Avoid holding a directory FD open across assorted SRF calls.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 17 Mar 2020 01:05:29 +0000 (21:05 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 17 Mar 2020 01:05:29 +0000 (21:05 -0400)
This extends the fixes made in commit 085b6b667 to other SRFs with the
same bug, namely pg_logdir_ls(), pgrowlocks(), pg_timezone_names(),
pg_ls_dir(), and pg_tablespace_databases().

Also adjust various comments and documentation to warn against
expecting to clean up resources during a ValuePerCall SRF's final
call.

Back-patch to all supported branches, since these functions were
all born broken.

Justin Pryzby, with cosmetic tweaks by me

Discussion: https://postgr.es/m/20200308173103.GC1357@telsasoft.com

contrib/adminpack/adminpack.c
contrib/pgrowlocks/pgrowlocks.c
doc/src/sgml/xfunc.sgml
src/backend/utils/adt/datetime.c
src/backend/utils/adt/genfile.c
src/backend/utils/adt/misc.c
src/backend/utils/fmgr/README
src/include/funcapi.h

index 26b33219b73ed5533061b2499317ea193e6c1aa8..db0b033956ea1aca93ba50ab48a9647001e37b34 100644 (file)
@@ -45,11 +45,6 @@ PG_FUNCTION_INFO_V1(pg_file_rename);
 PG_FUNCTION_INFO_V1(pg_file_unlink);
 PG_FUNCTION_INFO_V1(pg_logdir_ls);
 
-typedef struct
-{
-       char       *location;
-       DIR                *dirdesc;
-} directory_fctx;
 
 /*-----------------------
  * some helper functions
@@ -281,9 +276,14 @@ pg_file_unlink(PG_FUNCTION_ARGS)
 Datum
 pg_logdir_ls(PG_FUNCTION_ARGS)
 {
-       FuncCallContext *funcctx;
+       ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       bool            randomAccess;
+       TupleDesc       tupdesc;
+       Tuplestorestate *tupstore;
+       AttInMetadata *attinmeta;
+       DIR                *dirdesc;
        struct dirent *de;
-       directory_fctx *fctx;
+       MemoryContext oldcontext;
 
        if (!superuser())
                ereport(ERROR,
@@ -293,43 +293,39 @@ pg_logdir_ls(PG_FUNCTION_ARGS)
        if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                (errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))));
-
-       if (SRF_IS_FIRSTCALL())
-       {
-               MemoryContext oldcontext;
-               TupleDesc       tupdesc;
-
-               funcctx = SRF_FIRSTCALL_INIT();
-               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+                                errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
 
-               fctx = palloc(sizeof(directory_fctx));
-
-               tupdesc = CreateTemplateTupleDesc(2, false);
-               TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
-                                                  TIMESTAMPOID, -1, 0);
-               TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
-                                                  TEXTOID, -1, 0);
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("materialize mode required, but it is not allowed in this context")));
 
-               funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+       /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+       oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-               fctx->location = pstrdup(Log_directory);
-               fctx->dirdesc = AllocateDir(fctx->location);
+       tupdesc = CreateTemplateTupleDesc(2, false);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
+                                          TIMESTAMPOID, -1, 0);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
+                                          TEXTOID, -1, 0);
 
-               if (!fctx->dirdesc)
-                       ereport(ERROR,
-                                       (errcode_for_file_access(),
-                                        errmsg("could not read directory \"%s\": %m",
-                                                       fctx->location)));
+       randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+       tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
 
-               funcctx->user_fctx = fctx;
-               MemoryContextSwitchTo(oldcontext);
-       }
+       MemoryContextSwitchTo(oldcontext);
 
-       funcctx = SRF_PERCALL_SETUP();
-       fctx = (directory_fctx *) funcctx->user_fctx;
+       attinmeta = TupleDescGetAttInMetadata(tupdesc);
 
-       while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+       dirdesc = AllocateDir(Log_directory);
+       while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
        {
                char       *values[2];
                HeapTuple       tuple;
@@ -366,13 +362,13 @@ pg_logdir_ls(PG_FUNCTION_ARGS)
                /* Seems the timestamp is OK; prepare and return tuple */
 
                values[0] = timestampbuf;
-               values[1] = psprintf("%s/%s", fctx->location, de->d_name);
+               values[1] = psprintf("%s/%s", Log_directory, de->d_name);
 
-               tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+               tuple = BuildTupleFromCStrings(attinmeta, values);
 
-               SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+               tuplestore_puttuple(tupstore, tuple);
        }
 
-       FreeDir(fctx->dirdesc);
-       SRF_RETURN_DONE(funcctx);
+       FreeDir(dirdesc);
+       return (Datum) 0;
 }
index e20e7f83de3e94c834cfe0fc0155bffbfbb407ec..1a71b390143571e58a7dfe8184bab17b35a2e2b0 100644 (file)
@@ -50,13 +50,6 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 
 #define NCHARS 32
 
-typedef struct
-{
-       Relation        rel;
-       HeapScanDesc scan;
-       int                     ncolumns;
-} MyData;
-
 #define                Atnum_tid               0
 #define                Atnum_xmax              1
 #define                Atnum_ismulti   2
@@ -67,59 +60,62 @@ typedef struct
 Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
-       FuncCallContext *funcctx;
-       HeapScanDesc scan;
-       HeapTuple       tuple;
+       text       *relname = PG_GETARG_TEXT_PP(0);
+       ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       bool            randomAccess;
        TupleDesc       tupdesc;
+       Tuplestorestate *tupstore;
        AttInMetadata *attinmeta;
-       Datum           result;
-       MyData     *mydata;
        Relation        rel;
+       RangeVar   *relrv;
+       HeapScanDesc scan;
+       HeapTuple       tuple;
+       MemoryContext oldcontext;
+       AclResult       aclresult;
+       char      **values;
 
-       if (SRF_IS_FIRSTCALL())
-       {
-               text       *relname;
-               RangeVar   *relrv;
-               MemoryContext oldcontext;
-               AclResult       aclresult;
-
-               funcctx = SRF_FIRSTCALL_INIT();
-               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
-
-               /* Build a tuple descriptor for our result type */
-               if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-                       elog(ERROR, "return type must be a row type");
-
-               attinmeta = TupleDescGetAttInMetadata(tupdesc);
-               funcctx->attinmeta = attinmeta;
-
-               relname = PG_GETARG_TEXT_P(0);
-               relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
-               rel = heap_openrv(relrv, AccessShareLock);
-
-               /* check permissions: must have SELECT on table */
-               aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-                                                                         ACL_SELECT);
-               if (aclresult != ACLCHECK_OK)
-                       aclcheck_error(aclresult, ACL_KIND_CLASS,
-                                                  RelationGetRelationName(rel));
-
-               scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
-               mydata = palloc(sizeof(*mydata));
-               mydata->rel = rel;
-               mydata->scan = scan;
-               mydata->ncolumns = tupdesc->natts;
-               funcctx->user_fctx = mydata;
-
-               MemoryContextSwitchTo(oldcontext);
-       }
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("materialize mode required, but it is not allowed in this context")));
+
+       /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+       oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
+
+       if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+               elog(ERROR, "return type must be a row type");
 
-       funcctx = SRF_PERCALL_SETUP();
-       attinmeta = funcctx->attinmeta;
-       mydata = (MyData *) funcctx->user_fctx;
-       scan = mydata->scan;
+       randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+       tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
+
+       MemoryContextSwitchTo(oldcontext);
+
+       /* Access the table */
+       relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+       rel = heap_openrv(relrv, AccessShareLock);
+
+       /* check permissions: must have SELECT on table */
+       aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+                                                                 ACL_SELECT);
+       if (aclresult != ACLCHECK_OK)
+               aclcheck_error(aclresult, ACL_KIND_CLASS,
+                                          RelationGetRelationName(rel));
+
+       /* Scan the relation */
+       scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+
+       attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+       values = (char **) palloc(tupdesc->natts * sizeof(char *));
 
-       /* scan the relation */
        while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
        {
                HTSU_Result htsu;
@@ -140,10 +136,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
                 */
                if (htsu == HeapTupleBeingUpdated)
                {
-                       char      **values;
-
-                       values = (char **) palloc(mydata->ncolumns * sizeof(char *));
-
                        values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
                                                                                        PointerGetDatum(&tuple->t_self));
 
@@ -268,16 +260,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
 
                        /* build a tuple */
                        tuple = BuildTupleFromCStrings(attinmeta, values);
-
-                       /* make the tuple into a datum */
-                       result = HeapTupleGetDatum(tuple);
-
-                       /*
-                        * no need to pfree what we allocated; it's on a short-lived
-                        * memory context anyway
-                        */
-
-                       SRF_RETURN_NEXT(funcctx, result);
+                       tuplestore_puttuple(tupstore, tuple);
                }
                else
                {
@@ -286,7 +269,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
        }
 
        heap_endscan(scan);
-       heap_close(mydata->rel, AccessShareLock);
-
-       SRF_RETURN_DONE(funcctx);
+       heap_close(rel, AccessShareLock);
+       return (Datum) 0;
 }
index 2adbf2719e60eac73bafcb4987a9590c555f47ca..e2f40f18562ddd3942645298cd59125ef386c1e9 100644 (file)
@@ -2815,22 +2815,50 @@ HeapTupleGetDatum(HeapTuple tuple)
     <title>Returning Sets</title>
 
     <para>
-     There is also a special API that provides support for returning
-     sets (multiple rows) from a C-language function.  A set-returning
-     function must follow the version-1 calling conventions.  Also,
-     source files must include <filename>funcapi.h</filename>, as
-     above.
-    </para>
-
-    <para>
-     A set-returning function (<acronym>SRF</>) is called
-     once for each item it returns.  The <acronym>SRF</> must
-     therefore save enough state to remember what it was doing and
-     return the next item on each call.
-     The structure <structname>FuncCallContext</> is provided to help
-     control this process.  Within a function, <literal>fcinfo-&gt;flinfo-&gt;fn_extra</>
-     is used to hold a pointer to <structname>FuncCallContext</>
-     across calls.
+     C-language functions have two options for returning sets (multiple
+     rows).  In one method, called <firstterm>ValuePerCall</firstterm>
+     mode, a set-returning function is called repeatedly (passing the same
+     arguments each time) and it returns one new row on each call, until
+     it has no more rows to return and signals that by returning NULL.
+     The set-returning function (<acronym>SRF</acronym>) must therefore
+     save enough state across calls to remember what it was doing and
+     return the correct next item on each call.
+     In the other method, called <firstterm>Materialize</firstterm> mode,
+     a SRF fills and returns a tuplestore object containing its
+     entire result; then only one call occurs for the whole result, and
+     no inter-call state is needed.
+    </para>
+
+    <para>
+     When using ValuePerCall mode, it is important to remember that the
+     query is not guaranteed to be run to completion; that is, due to
+     options such as <literal>LIMIT</literal>, the executor might stop
+     making calls to the set-returning function before all rows have been
+     fetched.  This means it is not safe to perform cleanup activities in
+     the last call, because that might not ever happen.  It's recommended
+     to use Materialize mode for functions that need access to external
+     resources, such as file descriptors.
+    </para>
+
+    <para>
+     The remainder of this section documents a set of helper macros that
+     are commonly used (though not required to be used) for SRFs using
+     ValuePerCall mode.  Additional details about Materialize mode can be
+     found in <filename>src/backend/utils/fmgr/README</filename>.  Also,
+     the <filename>contrib</filename> modules in
+     the <productname>PostgreSQL</productname> source distribution contain
+     many examples of SRFs using both ValuePerCall and Materialize mode.
+    </para>
+
+    <para>
+     To use the ValuePerCall support macros described here,
+     include <filename>funcapi.h</filename>.  These macros work with a
+     structure <structname>FuncCallContext</structname> that contains the
+     state that needs to be saved across calls.  Within the calling
+     SRF, <literal>fcinfo-&gt;flinfo-&gt;fn_extra</literal> is used to
+     hold a pointer to <structname>FuncCallContext</structname> across
+     calls.  The macros automatically fill that field on first use,
+     and expect to find the same pointer there on subsequent uses.
 <programlisting>
 typedef struct
 {
@@ -2903,29 +2931,26 @@ typedef struct
     </para>
 
     <para>
-     An <acronym>SRF</> uses several functions and macros that
-     automatically manipulate the <structname>FuncCallContext</>
-     structure (and expect to find it via <literal>fn_extra</>).  Use:
+     The macros to be used by an <acronym>SRF</acronym> using this
+     infrastructure are:
 <programlisting>
 SRF_IS_FIRSTCALL()
 </programlisting>
-     to determine if your function is being called for the first or a
-     subsequent time. On the first call (only) use:
+     Use this to determine if your function is being called for the first or a
+     subsequent time. On the first call (only), call:
 <programlisting>
 SRF_FIRSTCALL_INIT()
 </programlisting>
-     to initialize the <structname>FuncCallContext</>. On every function call,
-     including the first, use:
+     to initialize the <structname>FuncCallContext</structname>. On every function call,
+     including the first, call:
 <programlisting>
 SRF_PERCALL_SETUP()
 </programlisting>
-     to properly set up for using the <structname>FuncCallContext</>
-     and clearing any previously returned data left over from the
-     previous pass.
+     to set up for using the <structname>FuncCallContext</structname>.
     </para>
 
     <para>
-     If your function has data to return, use:
+     If your function has data to return in the current call, use:
 <programlisting>
 SRF_RETURN_NEXT(funcctx, result)
 </programlisting>
@@ -2949,7 +2974,14 @@ SRF_RETURN_DONE(funcctx)
      <structfield>multi_call_memory_ctx</> is a suitable location for any
      data that needs to survive until the <acronym>SRF</> is finished running.  In most
      cases, this means that you should switch into
-     <structfield>multi_call_memory_ctx</> while doing the first-call setup.
+     <structfield>multi_call_memory_ctx</structfield> while doing the
+     first-call setup.
+     Use <literal>funcctx-&gt;user_fctx</literal> to hold a pointer to
+     any such cross-call data structures.
+     (Data you allocate
+     in <structfield>multi_call_memory_ctx</structfield> will go away
+     automatically when the query ends, so it is not necessary to free
+     that data manually, either.)
     </para>
 
     <warning>
@@ -3006,8 +3038,8 @@ my_set_returning_function(PG_FUNCTION_ARGS)
     }
     else
     {
-        /* Here we are done returning items and just need to clean up: */
-        <replaceable>user code</replaceable>
+        /* Here we are done returning items, so just report that fact. */
+        /* (Resist the temptation to put cleanup code here.) */
         SRF_RETURN_DONE(funcctx);
     }
 }
@@ -3129,12 +3161,6 @@ CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
      Notice that in this method the output type of the function is formally
      an anonymous <structname>record</> type.
     </para>
-
-    <para>
-     The directory <link linkend="tablefunc">contrib/tablefunc</>
-     module in the source distribution contains more examples of
-     set-returning functions.
-    </para>
    </sect2>
 
    <sect2>
index d9b5be7b4d59eaf822df3252504aebd3f6fab94b..38e95effc7bccb04ac06ce35bff14fbc6b020445 100644 (file)
@@ -4759,12 +4759,12 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 Datum
 pg_timezone_names(PG_FUNCTION_ARGS)
 {
-       MemoryContext oldcontext;
-       FuncCallContext *funcctx;
+       ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       bool            randomAccess;
+       TupleDesc       tupdesc;
+       Tuplestorestate *tupstore;
        pg_tzenum  *tzenum;
        pg_tz      *tz;
-       Datum           result;
-       HeapTuple       tuple;
        Datum           values[4];
        bool            nulls[4];
        int                     tzoff;
@@ -4773,59 +4773,41 @@ pg_timezone_names(PG_FUNCTION_ARGS)
        const char *tzn;
        Interval   *resInterval;
        struct pg_tm itm;
+       MemoryContext oldcontext;
 
-       /* stuff done only on the first call of the function */
-       if (SRF_IS_FIRSTCALL())
-       {
-               TupleDesc       tupdesc;
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("materialize mode required, but it is not allowed in this context")));
 
-               /* create a function context for cross-call persistence */
-               funcctx = SRF_FIRSTCALL_INIT();
+       /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+       oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-               /*
-                * switch to memory context appropriate for multiple function calls
-                */
-               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+       if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+               elog(ERROR, "return type must be a row type");
 
-               /* initialize timezone scanning code */
-               tzenum = pg_tzenumerate_start();
-               funcctx->user_fctx = (void *) tzenum;
+       randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+       tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
 
-               /*
-                * build tupdesc for result tuples. This must match this function's
-                * pg_proc entry!
-                */
-               tupdesc = CreateTemplateTupleDesc(4, false);
-               TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
-                                                  TEXTOID, -1, 0);
-               TupleDescInitEntry(tupdesc, (AttrNumber) 2, "abbrev",
-                                                  TEXTOID, -1, 0);
-               TupleDescInitEntry(tupdesc, (AttrNumber) 3, "utc_offset",
-                                                  INTERVALOID, -1, 0);
-               TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_dst",
-                                                  BOOLOID, -1, 0);
+       MemoryContextSwitchTo(oldcontext);
 
-               funcctx->tuple_desc = BlessTupleDesc(tupdesc);
-               MemoryContextSwitchTo(oldcontext);
-       }
-
-       /* stuff done on every call of the function */
-       funcctx = SRF_PERCALL_SETUP();
-       tzenum = (pg_tzenum *) funcctx->user_fctx;
+       /* initialize timezone scanning code */
+       tzenum = pg_tzenumerate_start();
 
        /* search for another zone to display */
        for (;;)
        {
-               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
                tz = pg_tzenumerate_next(tzenum);
-               MemoryContextSwitchTo(oldcontext);
-
                if (!tz)
-               {
-                       pg_tzenumerate_end(tzenum);
-                       funcctx->user_fctx = NULL;
-                       SRF_RETURN_DONE(funcctx);
-               }
+                       break;
 
                /* Convert now() to local time in this zone */
                if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
@@ -4844,25 +4826,22 @@ pg_timezone_names(PG_FUNCTION_ARGS)
                if (tzn && strlen(tzn) > 31)
                        continue;
 
-               /* Found a displayable zone */
-               break;
-       }
+               MemSet(nulls, 0, sizeof(nulls));
 
-       MemSet(nulls, 0, sizeof(nulls));
+               values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
+               values[1] = CStringGetTextDatum(tzn ? tzn : "");
 
-       values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
-       values[1] = CStringGetTextDatum(tzn ? tzn : "");
+               MemSet(&itm, 0, sizeof(struct pg_tm));
+               itm.tm_sec = -tzoff;
+               resInterval = (Interval *) palloc(sizeof(Interval));
+               tm2interval(&itm, 0, resInterval);
+               values[2] = IntervalPGetDatum(resInterval);
 
-       MemSet(&itm, 0, sizeof(struct pg_tm));
-       itm.tm_sec = -tzoff;
-       resInterval = (Interval *) palloc(sizeof(Interval));
-       tm2interval(&itm, 0, resInterval);
-       values[2] = IntervalPGetDatum(resInterval);
+               values[3] = BoolGetDatum(tm.tm_isdst > 0);
 
-       values[3] = BoolGetDatum(tm.tm_isdst > 0);
-
-       tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
-       result = HeapTupleGetDatum(tuple);
+               tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+       }
 
-       SRF_RETURN_NEXT(funcctx, result);
+       pg_tzenumerate_end(tzenum);
+       return (Datum) 0;
 }
index c4eb10d3cfd53ce0261194f655790f2de8c242aa..6932589dfedd557796f6e215c4175e778294e500 100644 (file)
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
 
-typedef struct
-{
-       char       *location;
-       DIR                *dirdesc;
-       bool            include_dot_dirs;
-} directory_fctx;
-
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -393,9 +386,15 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-       FuncCallContext *funcctx;
+       ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       char       *location;
+       bool            missing_ok = false;
+       bool            include_dot_dirs = false;
+       bool            randomAccess;
+       TupleDesc       tupdesc;
+       Tuplestorestate *tupstore;
+       DIR                *dirdesc;
        struct dirent *de;
-       directory_fctx *fctx;
        MemoryContext oldcontext;
 
        if (!superuser())
@@ -403,62 +402,68 @@ pg_ls_dir(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                                 (errmsg("must be superuser to get directory listings"))));
 
-       if (SRF_IS_FIRSTCALL())
+       location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+       /* check the optional arguments */
+       if (PG_NARGS() == 3)
        {
-               bool            missing_ok = false;
-               bool            include_dot_dirs = false;
+               if (!PG_ARGISNULL(1))
+                       missing_ok = PG_GETARG_BOOL(1);
+               if (!PG_ARGISNULL(2))
+                       include_dot_dirs = PG_GETARG_BOOL(2);
+       }
 
-               /* check the optional arguments */
-               if (PG_NARGS() == 3)
-               {
-                       if (!PG_ARGISNULL(1))
-                               missing_ok = PG_GETARG_BOOL(1);
-                       if (!PG_ARGISNULL(2))
-                               include_dot_dirs = PG_GETARG_BOOL(2);
-               }
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("materialize mode required, but it is not allowed in this context")));
 
-               funcctx = SRF_FIRSTCALL_INIT();
-               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+       /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+       oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-               fctx = palloc(sizeof(directory_fctx));
-               fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
+       tupdesc = CreateTemplateTupleDesc(1, false);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
 
-               fctx->include_dot_dirs = include_dot_dirs;
-               fctx->dirdesc = AllocateDir(fctx->location);
+       randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+       tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
 
-               if (!fctx->dirdesc)
-               {
-                       if (missing_ok && errno == ENOENT)
-                       {
-                               MemoryContextSwitchTo(oldcontext);
-                               SRF_RETURN_DONE(funcctx);
-                       }
-                       else
-                               ereport(ERROR,
-                                               (errcode_for_file_access(),
-                                                errmsg("could not open directory \"%s\": %m",
-                                                               fctx->location)));
-               }
-               funcctx->user_fctx = fctx;
-               MemoryContextSwitchTo(oldcontext);
-       }
+       MemoryContextSwitchTo(oldcontext);
 
-       funcctx = SRF_PERCALL_SETUP();
-       fctx = (directory_fctx *) funcctx->user_fctx;
+       dirdesc = AllocateDir(location);
+       if (!dirdesc)
+       {
+               /* Return empty tuplestore if appropriate */
+               if (missing_ok && errno == ENOENT)
+                       return (Datum) 0;
+               /* Otherwise, we can let ReadDir() throw the error */
+       }
 
-       while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+       while ((de = ReadDir(dirdesc, location)) != NULL)
        {
-               if (!fctx->include_dot_dirs &&
+               Datum           values[1];
+               bool            nulls[1];
+
+               if (!include_dot_dirs &&
                        (strcmp(de->d_name, ".") == 0 ||
                         strcmp(de->d_name, "..") == 0))
                        continue;
 
-               SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
-       }
+               values[0] = CStringGetTextDatum(de->d_name);
+               nulls[0] = false;
 
-       FreeDir(fctx->dirdesc);
+               tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+       }
 
-       SRF_RETURN_DONE(funcctx);
+       FreeDir(dirdesc);
+       return (Datum) 0;
 }
 
 /*
index 4ac8183f36af56e307844d46b9a87dd5e8602393..281a9e0ddaf6a72d218142f1e99fb15f84fa2f82 100644 (file)
@@ -243,72 +243,82 @@ pg_rotate_logfile(PG_FUNCTION_ARGS)
 
 /* Function to find out which databases make use of a tablespace */
 
-typedef struct
-{
-       char       *location;
-       DIR                *dirdesc;
-} ts_db_fctx;
-
 Datum
 pg_tablespace_databases(PG_FUNCTION_ARGS)
 {
-       FuncCallContext *funcctx;
+       Oid                     tablespaceOid = PG_GETARG_OID(0);
+       ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       bool            randomAccess;
+       TupleDesc       tupdesc;
+       Tuplestorestate *tupstore;
+       char       *location;
+       DIR                *dirdesc;
        struct dirent *de;
-       ts_db_fctx *fctx;
+       MemoryContext oldcontext;
 
-       if (SRF_IS_FIRSTCALL())
-       {
-               MemoryContext oldcontext;
-               Oid                     tablespaceOid = PG_GETARG_OID(0);
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("materialize mode required, but it is not allowed in this context")));
 
-               funcctx = SRF_FIRSTCALL_INIT();
-               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+       /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+       oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-               fctx = palloc(sizeof(ts_db_fctx));
+       tupdesc = CreateTemplateTupleDesc(1, false);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_tablespace_databases",
+                                          OIDOID, -1, 0);
 
-               if (tablespaceOid == GLOBALTABLESPACE_OID)
-               {
-                       fctx->dirdesc = NULL;
-                       ereport(WARNING,
-                                       (errmsg("global tablespace never has databases")));
-               }
-               else
-               {
-                       if (tablespaceOid == DEFAULTTABLESPACE_OID)
-                               fctx->location = psprintf("base");
-                       else
-                               fctx->location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
-                                                                                 TABLESPACE_VERSION_DIRECTORY);
-
-                       fctx->dirdesc = AllocateDir(fctx->location);
-
-                       if (!fctx->dirdesc)
-                       {
-                               /* the only expected error is ENOENT */
-                               if (errno != ENOENT)
-                                       ereport(ERROR,
-                                                       (errcode_for_file_access(),
-                                                        errmsg("could not open directory \"%s\": %m",
-                                                                       fctx->location)));
-                               ereport(WARNING,
-                                         (errmsg("%u is not a tablespace OID", tablespaceOid)));
-                       }
-               }
-               funcctx->user_fctx = fctx;
-               MemoryContextSwitchTo(oldcontext);
+       randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+       tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
+
+       MemoryContextSwitchTo(oldcontext);
+
+       if (tablespaceOid == GLOBALTABLESPACE_OID)
+       {
+               ereport(WARNING,
+                               (errmsg("global tablespace never has databases")));
+               /* return empty tuplestore */
+               return (Datum) 0;
        }
 
-       funcctx = SRF_PERCALL_SETUP();
-       fctx = (ts_db_fctx *) funcctx->user_fctx;
+       if (tablespaceOid == DEFAULTTABLESPACE_OID)
+               location = psprintf("base");
+       else
+               location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
+                                                       TABLESPACE_VERSION_DIRECTORY);
 
-       if (!fctx->dirdesc)                     /* not a tablespace */
-               SRF_RETURN_DONE(funcctx);
+       dirdesc = AllocateDir(location);
 
-       while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+       if (!dirdesc)
+       {
+               /* the only expected error is ENOENT */
+               if (errno != ENOENT)
+                       ereport(ERROR,
+                                       (errcode_for_file_access(),
+                                        errmsg("could not open directory \"%s\": %m",
+                                                       location)));
+               ereport(WARNING,
+                               (errmsg("%u is not a tablespace OID", tablespaceOid)));
+               /* return empty tuplestore */
+               return (Datum) 0;
+       }
+
+       while ((de = ReadDir(dirdesc, location)) != NULL)
        {
-               char       *subdir;
-               DIR                *dirdesc;
                Oid                     datOid = atooid(de->d_name);
+               char       *subdir;
+               DIR                *dirdesc2;
+               Datum           values[1];
+               bool            nulls[1];
 
                /* this test skips . and .., but is awfully weak */
                if (!datOid)
@@ -316,24 +326,27 @@ pg_tablespace_databases(PG_FUNCTION_ARGS)
 
                /* if database subdir is empty, don't report tablespace as used */
 
-               subdir = psprintf("%s/%s", fctx->location, de->d_name);
-               dirdesc = AllocateDir(subdir);
-               while ((de = ReadDir(dirdesc, subdir)) != NULL)
+               subdir = psprintf("%s/%s", location, de->d_name);
+               dirdesc2 = AllocateDir(subdir);
+               while ((de = ReadDir(dirdesc2, subdir)) != NULL)
                {
                        if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0)
                                break;
                }
-               FreeDir(dirdesc);
+               FreeDir(dirdesc2);
                pfree(subdir);
 
                if (!de)
                        continue;                       /* indeed, nothing in it */
 
-               SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(datOid));
+               values[0] = ObjectIdGetDatum(datOid);
+               nulls[0] = false;
+
+               tuplestore_putvalues(tupstore, tupdesc, values, nulls);
        }
 
-       FreeDir(fctx->dirdesc);
-       SRF_RETURN_DONE(funcctx);
+       FreeDir(dirdesc);
+       return (Datum) 0;
 }
 
 
index e7e7ae9c6e862548ae9cb28f3b3d14dfa167b431..8eb189e5b8125a69f6f6777c58355cb63aa405a1 100644 (file)
@@ -388,8 +388,6 @@ tuple toaster will decide whether toasting is needed.
 Functions Accepting or Returning Sets
 -------------------------------------
 
-[ this section revised 29-Aug-2002 for 7.3 ]
-
 If a function is marked in pg_proc as returning a set, then it is called
 with fcinfo->resultinfo pointing to a node of type ReturnSetInfo.  A
 function that desires to return a set should raise an error "called in
@@ -426,10 +424,16 @@ been returned, the next call should set isDone to ExprEndResult and return a
 null result.  (Note it is possible to return an empty set by doing this on
 the first call.)
 
-The ReturnSetInfo node also contains a link to the ExprContext within which
-the function is being evaluated.  This is useful for value-per-call functions
-that need to close down internal state when they are not run to completion:
-they can register a shutdown callback function in the ExprContext.
+Value-per-call functions MUST NOT assume that they will be run to completion;
+the executor might simply stop calling them, for example because of a LIMIT.
+Therefore, it's unsafe to attempt to perform any resource cleanup in the
+final call.  It's usually not necessary to clean up memory, anyway.  If it's
+necessary to clean up other types of resources, such as file descriptors,
+one can register a shutdown callback function in the ExprContext pointed to
+by the ReturnSetInfo node.  (But note that file descriptors are a limited
+resource, so it's generally unwise to hold those open across calls; SRFs
+that need file access are better written to do it in a single call using
+Materialize mode.)
 
 Materialize mode works like this: the function creates a Tuplestore holding
 the (possibly empty) result set, and returns it.  There are no multiple calls.
index 1b62bc120cc0f301ebb5e61c50411847b8d16552..3e74e5d5d4ac744a30efb7a52fef6244fa58d7a8 100644 (file)
@@ -238,7 +238,7 @@ extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
 /*----------
  *             Support for Set Returning Functions (SRFs)
  *
- * The basic API for SRFs looks something like:
+ * The basic API for SRFs using ValuePerCall mode looks something like this:
  *
  * Datum
  * my_Set_Returning_Function(PG_FUNCTION_ARGS)
@@ -275,6 +275,17 @@ extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
  *             SRF_RETURN_DONE(funcctx);
  * }
  *
+ * NOTE: there is no guarantee that a SRF using ValuePerCall mode will be
+ * run to completion; for example, a query with LIMIT might stop short of
+ * fetching all the rows.  Therefore, do not expect that you can do resource
+ * cleanup just before SRF_RETURN_DONE().  You need not worry about releasing
+ * memory allocated in multi_call_memory_ctx, but holding file descriptors or
+ * other non-memory resources open across calls is a bug.  SRFs that need
+ * such resources should not use these macros, but instead populate a
+ * tuplestore during a single call, and return that using SFRM_Materialize
+ * mode (see fmgr/README).  Alternatively, set up a callback to release
+ * resources at query shutdown, using RegisterExprContextCallback().
+ *
  *----------
  */