Avoid holding a directory FD open across assorted SRF calls.
authorTom Lane <[email protected]>
Tue, 17 Mar 2020 01:05:28 +0000 (21:05 -0400)
committerTom Lane <[email protected]>
Tue, 17 Mar 2020 01:05:52 +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[email protected]

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
src/test/regress/expected/misc_functions.out
src/test/regress/sql/misc_functions.sql

index bc45e79895142b9dcf3a9b3c7909736c74aae8a1..7d0a19b29492da88efa47e8dcaa1951517fd2539 100644 (file)
@@ -56,11 +56,6 @@ static int64 pg_file_write_internal(text *file, text *data, bool replace);
 static bool pg_file_rename_internal(text *file1, text *file2, text *file3);
 static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
 
-typedef struct
-{
-   char       *location;
-   DIR        *dirdesc;
-} directory_fctx;
 
 /*-----------------------
  * some helper functions
@@ -504,50 +499,51 @@ pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
 static Datum
 pg_logdir_ls_internal(FunctionCallInfo fcinfo)
 {
-   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 (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);
-
-       fctx = palloc(sizeof(directory_fctx));
-
-       tupdesc = CreateTemplateTupleDesc(2);
-       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);
+   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 open 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;
@@ -584,13 +580,13 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
        /* 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 a2c44a916cff84c5921d96c7b2ae5e4209727666..714398831bc962a38da94b8b06be6c7a886d75c9 100644 (file)
@@ -54,13 +54,6 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
 
 #define NCHARS 32
 
-typedef struct
-{
-   Relation    rel;
-   TableScanDesc scan;
-   int         ncolumns;
-} MyData;
-
 #define        Atnum_tid       0
 #define        Atnum_xmax      1
 #define        Atnum_ismulti   2
@@ -71,84 +64,86 @@ typedef struct
 Datum
 pgrowlocks(PG_FUNCTION_ARGS)
 {
-   FuncCallContext *funcctx;
-   TableScanDesc scan;
-   HeapScanDesc hscan;
-   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;
+   TableScanDesc scan;
+   HeapScanDesc hscan;
+   HeapTuple   tuple;
+   MemoryContext oldcontext;
+   AclResult   aclresult;
+   char      **values;
+
+   /* 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");
+
+   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 = relation_openrv(relrv, AccessShareLock);
+
+   if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
+       ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                       errmsg("only heap AM is supported")));
+
+   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("\"%s\" is a partitioned table",
+                       RelationGetRelationName(rel)),
+                errdetail("Partitioned tables do not contain rows.")));
+   else if (rel->rd_rel->relkind != RELKIND_RELATION)
+       ereport(ERROR,
+               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                errmsg("\"%s\" is not a table",
+                       RelationGetRelationName(rel))));
+
+   /*
+    * check permissions: must have SELECT on table or be in
+    * pg_stat_scan_tables
+    */
+   aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+                                 ACL_SELECT);
+   if (aclresult != ACLCHECK_OK)
+       aclresult = is_member_of_role(GetUserId(), DEFAULT_ROLE_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+
+   if (aclresult != ACLCHECK_OK)
+       aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
+                      RelationGetRelationName(rel));
+
+   /* Scan the relation */
+   scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+   hscan = (HeapScanDesc) scan;
 
-   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_PP(0);
-       relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
-       rel = relation_openrv(relrv, AccessShareLock);
-
-       if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
-           ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                           errmsg("only heap AM is supported")));
-
-       if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-           ereport(ERROR,
-                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("\"%s\" is a partitioned table",
-                           RelationGetRelationName(rel)),
-                    errdetail("Partitioned tables do not contain rows.")));
-       else if (rel->rd_rel->relkind != RELKIND_RELATION)
-           ereport(ERROR,
-                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("\"%s\" is not a table",
-                           RelationGetRelationName(rel))));
-
-       /*
-        * check permissions: must have SELECT on table or be in
-        * pg_stat_scan_tables
-        */
-       aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-                                     ACL_SELECT);
-       if (aclresult != ACLCHECK_OK)
-           aclresult = is_member_of_role(GetUserId(), DEFAULT_ROLE_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
-
-       if (aclresult != ACLCHECK_OK)
-           aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
-                          RelationGetRelationName(rel));
-
-       scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);
-       hscan = (HeapScanDesc) scan;
-       mydata = palloc(sizeof(*mydata));
-       mydata->rel = rel;
-       mydata->scan = scan;
-       mydata->ncolumns = tupdesc->natts;
-       funcctx->user_fctx = mydata;
-
-       MemoryContextSwitchTo(oldcontext);
-   }
+   attinmeta = TupleDescGetAttInMetadata(tupdesc);
 
-   funcctx = SRF_PERCALL_SETUP();
-   attinmeta = funcctx->attinmeta;
-   mydata = (MyData *) funcctx->user_fctx;
-   scan = mydata->scan;
-   hscan = (HeapScanDesc) scan;
+   values = (char **) palloc(tupdesc->natts * sizeof(char *));
 
-   /* scan the relation */
    while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
    {
        TM_Result   htsu;
@@ -169,10 +164,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
         */
        if (htsu == TM_BeingModified)
        {
-           char      **values;
-
-           values = (char **) palloc(mydata->ncolumns * sizeof(char *));
-
            values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
                                                             PointerGetDatum(&tuple->t_self));
 
@@ -297,16 +288,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
        {
@@ -315,7 +297,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
    }
 
    table_endscan(scan);
-   table_close(mydata->rel, AccessShareLock);
-
-   SRF_RETURN_DONE(funcctx);
+   table_close(rel, AccessShareLock);
+   return (Datum) 0;
 }
index a91f8871191ec857b8496ca6206113dca0fbf7d5..6350f92f8669c169512ce432a5b4121e5aeb2121 100644 (file)
@@ -2812,22 +2812,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</acronym>) is called
-     once for each item it returns.  The <acronym>SRF</acronym> must
-     therefore save enough state to remember what it was doing and
-     return the next item on each call.
-     The structure <structname>FuncCallContext</structname> is provided to help
-     control this process.  Within a function, <literal>fcinfo-&gt;flinfo-&gt;fn_extra</literal>
-     is used to hold a pointer to <structname>FuncCallContext</structname>
-     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 FuncCallContext
 {
@@ -2892,29 +2920,26 @@ typedef struct FuncCallContext
     </para>
 
     <para>
-     An <acronym>SRF</acronym> uses several functions and macros that
-     automatically manipulate the <structname>FuncCallContext</structname>
-     structure (and expect to find it via <literal>fn_extra</literal>).  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</structname>. On every function call,
-     including the first, use:
+     including the first, call:
 <programlisting>
 SRF_PERCALL_SETUP()
 </programlisting>
-     to properly set up for using the <structname>FuncCallContext</structname>
-     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>
@@ -2938,7 +2963,14 @@ SRF_RETURN_DONE(funcctx)
      <structfield>multi_call_memory_ctx</structfield> is a suitable location for any
      data that needs to survive until the <acronym>SRF</acronym> is finished running.  In most
      cases, this means that you should switch into
-     <structfield>multi_call_memory_ctx</structfield> 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>
@@ -2995,8 +3027,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);
     }
 }
@@ -3118,12 +3150,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</structname> type.
     </para>
-
-    <para>
-     The directory <link linkend="tablefunc"><filename>contrib/tablefunc</filename></link>
-     module in the source distribution contains more examples of
-     set-returning functions.
-    </para>
    </sect2>
 
    <sect2>
index 4f109111d19724d3d657c6b35bdd81f08d6fe7bf..9c808942819dd66f4b9a0cb7e9533a7470502c7e 100644 (file)
@@ -4755,12 +4755,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;
@@ -4769,59 +4769,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);
-       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(),
@@ -4840,25 +4822,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 bcf9bd1b970e6cdf24343f49cf2bb0df308f3b25..01185f218b7247cda81e63b02eab435c0437033c 100644 (file)
 #include "utils/syscache.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.
@@ -447,67 +440,79 @@ 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 (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")));
+
+   /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+   oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-       funcctx = SRF_FIRSTCALL_INIT();
-       oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+   tupdesc = CreateTemplateTupleDesc(1);
+   TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
 
-       fctx = palloc(sizeof(directory_fctx));
-       fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+   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;
 
-       fctx->include_dot_dirs = include_dot_dirs;
-       fctx->dirdesc = AllocateDir(fctx->location);
+   MemoryContextSwitchTo(oldcontext);
 
-       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);
+   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 */
    }
 
-   funcctx = SRF_PERCALL_SETUP();
-   fctx = (directory_fctx *) funcctx->user_fctx;
-
-   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;
 }
 
 /*
@@ -548,8 +553,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
    if (!(rsinfo->allowedModes & SFRM_Materialize))
        ereport(ERROR,
                (errcode(ERRCODE_SYNTAX_ERROR),
-                errmsg("materialize mode required, but it is not "
-                       "allowed in this context")));
+                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);
@@ -575,10 +579,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
    {
        /* Return empty tuplestore if appropriate */
        if (missing_ok && errno == ENOENT)
-       {
-           tuplestore_donestoring(tupstore);
            return (Datum) 0;
-       }
        /* Otherwise, we can let ReadDir() throw the error */
    }
 
@@ -613,7 +614,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
    }
 
    FreeDir(dirdesc);
-   tuplestore_donestoring(tupstore);
    return (Datum) 0;
 }
 
index 323e36b81c786d24ce91208231441a39687b4ccc..ee340fb0f021bee659374ef589320b09601ceaac 100644 (file)
@@ -194,72 +194,82 @@ current_query(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);
+   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);
+   randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+   tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
 
-           fctx->dirdesc = AllocateDir(fctx->location);
+   rsinfo->returnMode = SFRM_Materialize;
+   rsinfo->setResult = tupstore;
+   rsinfo->setDesc = tupdesc;
 
-           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);
+   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)
    {
        Oid         datOid = atooid(de->d_name);
        char       *subdir;
        bool        isempty;
+       Datum       values[1];
+       bool        nulls[1];
 
        /* this test skips . and .., but is awfully weak */
        if (!datOid)
@@ -267,18 +277,21 @@ 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);
+       subdir = psprintf("%s/%s", location, de->d_name);
        isempty = directory_is_empty(subdir);
        pfree(subdir);
 
        if (isempty)
            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 a4d6a07bddc6c77f05a4bd1a0098530b6a184f1d..1e4c4b94a95f75f73860a8f314c9a1a63d6f90a1 100644 (file)
@@ -239,8 +239,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
@@ -277,10 +275,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 f9b75ae3905bf628b52b3cb503ad5bec7d07554c..b047acdc1a85f12da5365003cd4aa27ecaa66233 100644 (file)
@@ -234,7 +234,7 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
 /*----------
  *     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)
@@ -271,6 +271,17 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
  *     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().
+ *
  *----------
  */
 
index e217b678d78779222916aaa57dcd2f6f816110db..d3acb98d04e1f1a25ca436f38654d6881f5136a8 100644 (file)
@@ -180,6 +180,27 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
  t
 (1 row)
 
+select * from (select pg_ls_dir('.') a) a where a = 'base' limit 1;
+  a   
+------
+ base
+(1 row)
+
+select * from (select (pg_timezone_names()).name) ptn where name='UTC' limit 1;
+ name 
+------
+ UTC
+(1 row)
+
+select count(*) > 0 from
+  (select pg_tablespace_databases(oid) as pts from pg_tablespace
+   where spcname = 'pg_default') pts
+  join pg_database db on pts.pts = db.oid;
+ ?column? 
+----------
+ t
+(1 row)
+
 --
 -- Test adding a support function to a subject function
 --
index 1e11eb35547e46d6d52740bfca997d481d6404fa..094e8f8296fbd8a1a3af304478a2b5edfcafc130 100644 (file)
@@ -51,6 +51,15 @@ from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1;
 
 select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 
+select * from (select pg_ls_dir('.') a) a where a = 'base' limit 1;
+
+select * from (select (pg_timezone_names()).name) ptn where name='UTC' limit 1;
+
+select count(*) > 0 from
+  (select pg_tablespace_databases(oid) as pts from pg_tablespace
+   where spcname = 'pg_default') pts
+  join pg_database db on pts.pts = db.oid;
+
 --
 -- Test adding a support function to a subject function
 --