]> 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:28 +0000 (21:05 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
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.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
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
 --