Commit b4570d33 authored by Tom Lane's avatar Tom Lane

Avoid holding a directory FD open across assorted SRF calls.

This extends the fixes made in commit 085b6b66 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
parent 11375815
...@@ -56,11 +56,6 @@ static int64 pg_file_write_internal(text *file, text *data, bool replace); ...@@ -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 bool pg_file_rename_internal(text *file1, text *file2, text *file3);
static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo); static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
typedef struct
{
char *location;
DIR *dirdesc;
} directory_fctx;
/*----------------------- /*-----------------------
* some helper functions * some helper functions
...@@ -504,50 +499,51 @@ pg_logdir_ls_v1_1(PG_FUNCTION_ARGS) ...@@ -504,50 +499,51 @@ pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
static Datum static Datum
pg_logdir_ls_internal(FunctionCallInfo fcinfo) 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; struct dirent *de;
directory_fctx *fctx; MemoryContext oldcontext;
if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0) if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))); errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
if (SRF_IS_FIRSTCALL()) /* check to see if caller supports us returning a tuplestore */
{ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
MemoryContext oldcontext; ereport(ERROR,
TupleDesc tupdesc; (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
funcctx = SRF_FIRSTCALL_INIT(); if (!(rsinfo->allowedModes & SFRM_Materialize))
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
fctx = palloc(sizeof(directory_fctx)); errmsg("materialize mode required, but it is not allowed in this context")));
tupdesc = CreateTemplateTupleDesc(2);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
TIMESTAMPOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
TEXTOID, -1, 0);
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); tupdesc = CreateTemplateTupleDesc(2);
fctx->dirdesc = AllocateDir(fctx->location); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
TIMESTAMPOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
TEXTOID, -1, 0);
if (!fctx->dirdesc) randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
ereport(ERROR, tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
(errcode_for_file_access(), rsinfo->returnMode = SFRM_Materialize;
errmsg("could not open directory \"%s\": %m", rsinfo->setResult = tupstore;
fctx->location))); rsinfo->setDesc = tupdesc;
funcctx->user_fctx = fctx; MemoryContextSwitchTo(oldcontext);
MemoryContextSwitchTo(oldcontext);
}
funcctx = SRF_PERCALL_SETUP(); attinmeta = TupleDescGetAttInMetadata(tupdesc);
fctx = (directory_fctx *) funcctx->user_fctx;
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) dirdesc = AllocateDir(Log_directory);
while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
{ {
char *values[2]; char *values[2];
HeapTuple tuple; HeapTuple tuple;
...@@ -584,13 +580,13 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo) ...@@ -584,13 +580,13 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
/* Seems the timestamp is OK; prepare and return tuple */ /* Seems the timestamp is OK; prepare and return tuple */
values[0] = timestampbuf; 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); FreeDir(dirdesc);
SRF_RETURN_DONE(funcctx); return (Datum) 0;
} }
...@@ -54,13 +54,6 @@ PG_FUNCTION_INFO_V1(pgrowlocks); ...@@ -54,13 +54,6 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
#define NCHARS 32 #define NCHARS 32
typedef struct
{
Relation rel;
TableScanDesc scan;
int ncolumns;
} MyData;
#define Atnum_tid 0 #define Atnum_tid 0
#define Atnum_xmax 1 #define Atnum_xmax 1
#define Atnum_ismulti 2 #define Atnum_ismulti 2
...@@ -71,84 +64,86 @@ typedef struct ...@@ -71,84 +64,86 @@ typedef struct
Datum Datum
pgrowlocks(PG_FUNCTION_ARGS) pgrowlocks(PG_FUNCTION_ARGS)
{ {
FuncCallContext *funcctx; text *relname = PG_GETARG_TEXT_PP(0);
TableScanDesc scan; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
HeapScanDesc hscan; bool randomAccess;
HeapTuple tuple;
TupleDesc tupdesc; TupleDesc tupdesc;
Tuplestorestate *tupstore;
AttInMetadata *attinmeta; AttInMetadata *attinmeta;
Datum result;
MyData *mydata;
Relation rel; 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()) attinmeta = TupleDescGetAttInMetadata(tupdesc);
{
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);
}
funcctx = SRF_PERCALL_SETUP(); values = (char **) palloc(tupdesc->natts * sizeof(char *));
attinmeta = funcctx->attinmeta;
mydata = (MyData *) funcctx->user_fctx;
scan = mydata->scan;
hscan = (HeapScanDesc) scan;
/* scan the relation */
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{ {
TM_Result htsu; TM_Result htsu;
...@@ -169,10 +164,6 @@ pgrowlocks(PG_FUNCTION_ARGS) ...@@ -169,10 +164,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
*/ */
if (htsu == TM_BeingModified) if (htsu == TM_BeingModified)
{ {
char **values;
values = (char **) palloc(mydata->ncolumns * sizeof(char *));
values[Atnum_tid] = (char *) DirectFunctionCall1(tidout, values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
PointerGetDatum(&tuple->t_self)); PointerGetDatum(&tuple->t_self));
...@@ -297,16 +288,7 @@ pgrowlocks(PG_FUNCTION_ARGS) ...@@ -297,16 +288,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
/* build a tuple */ /* build a tuple */
tuple = BuildTupleFromCStrings(attinmeta, values); tuple = BuildTupleFromCStrings(attinmeta, values);
tuplestore_puttuple(tupstore, tuple);
/* 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);
} }
else else
{ {
...@@ -315,7 +297,6 @@ pgrowlocks(PG_FUNCTION_ARGS) ...@@ -315,7 +297,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
} }
table_endscan(scan); table_endscan(scan);
table_close(mydata->rel, AccessShareLock); table_close(rel, AccessShareLock);
return (Datum) 0;
SRF_RETURN_DONE(funcctx);
} }
...@@ -2812,22 +2812,50 @@ HeapTupleGetDatum(HeapTuple tuple) ...@@ -2812,22 +2812,50 @@ HeapTupleGetDatum(HeapTuple tuple)
<title>Returning Sets</title> <title>Returning Sets</title>
<para> <para>
There is also a special API that provides support for returning C-language functions have two options for returning sets (multiple
sets (multiple rows) from a C-language function. A set-returning rows). In one method, called <firstterm>ValuePerCall</firstterm>
function must follow the version-1 calling conventions. Also, mode, a set-returning function is called repeatedly (passing the same
source files must include <filename>funcapi.h</filename>, as arguments each time) and it returns one new row on each call, until
above. it has no more rows to return and signals that by returning NULL.
</para> The set-returning function (<acronym>SRF</acronym>) must therefore
save enough state across calls to remember what it was doing and
<para> return the correct next item on each call.
A set-returning function (<acronym>SRF</acronym>) is called In the other method, called <firstterm>Materialize</firstterm> mode,
once for each item it returns. The <acronym>SRF</acronym> must a SRF fills and returns a tuplestore object containing its
therefore save enough state to remember what it was doing and entire result; then only one call occurs for the whole result, and
return the next item on each call. no inter-call state is needed.
The structure <structname>FuncCallContext</structname> is provided to help </para>
control this process. Within a function, <literal>fcinfo-&gt;flinfo-&gt;fn_extra</literal>
is used to hold a pointer to <structname>FuncCallContext</structname> <para>
across calls. 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> <programlisting>
typedef struct FuncCallContext typedef struct FuncCallContext
{ {
...@@ -2892,29 +2920,26 @@ typedef struct FuncCallContext ...@@ -2892,29 +2920,26 @@ typedef struct FuncCallContext
</para> </para>
<para> <para>
An <acronym>SRF</acronym> uses several functions and macros that The macros to be used by an <acronym>SRF</acronym> using this
automatically manipulate the <structname>FuncCallContext</structname> infrastructure are:
structure (and expect to find it via <literal>fn_extra</literal>). Use:
<programlisting> <programlisting>
SRF_IS_FIRSTCALL() SRF_IS_FIRSTCALL()
</programlisting> </programlisting>
to determine if your function is being called for the first or a Use this to determine if your function is being called for the first or a
subsequent time. On the first call (only) use: subsequent time. On the first call (only), call:
<programlisting> <programlisting>
SRF_FIRSTCALL_INIT() SRF_FIRSTCALL_INIT()
</programlisting> </programlisting>
to initialize the <structname>FuncCallContext</structname>. On every function call, to initialize the <structname>FuncCallContext</structname>. On every function call,
including the first, use: including the first, call:
<programlisting> <programlisting>
SRF_PERCALL_SETUP() SRF_PERCALL_SETUP()
</programlisting> </programlisting>
to properly set up for using the <structname>FuncCallContext</structname> to set up for using the <structname>FuncCallContext</structname>.
and clearing any previously returned data left over from the
previous pass.
</para> </para>
<para> <para>
If your function has data to return, use: If your function has data to return in the current call, use:
<programlisting> <programlisting>
SRF_RETURN_NEXT(funcctx, result) SRF_RETURN_NEXT(funcctx, result)
</programlisting> </programlisting>
...@@ -2938,7 +2963,14 @@ SRF_RETURN_DONE(funcctx) ...@@ -2938,7 +2963,14 @@ SRF_RETURN_DONE(funcctx)
<structfield>multi_call_memory_ctx</structfield> is a suitable location for any <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 data that needs to survive until the <acronym>SRF</acronym> is finished running. In most
cases, this means that you should switch into 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> </para>
<warning> <warning>
...@@ -2995,8 +3027,8 @@ my_set_returning_function(PG_FUNCTION_ARGS) ...@@ -2995,8 +3027,8 @@ my_set_returning_function(PG_FUNCTION_ARGS)
} }
else else
{ {
/* Here we are done returning items and just need to clean up: */ /* Here we are done returning items, so just report that fact. */
<replaceable>user code</replaceable> /* (Resist the temptation to put cleanup code here.) */
SRF_RETURN_DONE(funcctx); SRF_RETURN_DONE(funcctx);
} }
} }
...@@ -3118,12 +3150,6 @@ CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer, ...@@ -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 Notice that in this method the output type of the function is formally
an anonymous <structname>record</structname> type. an anonymous <structname>record</structname> type.
</para> </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>
<sect2> <sect2>
......
...@@ -4755,12 +4755,12 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS) ...@@ -4755,12 +4755,12 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
Datum Datum
pg_timezone_names(PG_FUNCTION_ARGS) pg_timezone_names(PG_FUNCTION_ARGS)
{ {
MemoryContext oldcontext; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
FuncCallContext *funcctx; bool randomAccess;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
pg_tzenum *tzenum; pg_tzenum *tzenum;
pg_tz *tz; pg_tz *tz;
Datum result;
HeapTuple tuple;
Datum values[4]; Datum values[4];
bool nulls[4]; bool nulls[4];
int tzoff; int tzoff;
...@@ -4769,59 +4769,41 @@ pg_timezone_names(PG_FUNCTION_ARGS) ...@@ -4769,59 +4769,41 @@ pg_timezone_names(PG_FUNCTION_ARGS)
const char *tzn; const char *tzn;
Interval *resInterval; Interval *resInterval;
struct pg_tm itm; struct pg_tm itm;
MemoryContext oldcontext;
/* stuff done only on the first call of the function */ /* check to see if caller supports us returning a tuplestore */
if (SRF_IS_FIRSTCALL()) if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
{ ereport(ERROR,
TupleDesc tupdesc; (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 */ /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
/* if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
* switch to memory context appropriate for multiple function calls elog(ERROR, "return type must be a row type");
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* initialize timezone scanning code */ randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tzenum = pg_tzenumerate_start(); tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
funcctx->user_fctx = (void *) tzenum; rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
/* MemoryContextSwitchTo(oldcontext);
* 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);
funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* initialize timezone scanning code */
MemoryContextSwitchTo(oldcontext); tzenum = pg_tzenumerate_start();
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
tzenum = (pg_tzenum *) funcctx->user_fctx;
/* search for another zone to display */ /* search for another zone to display */
for (;;) for (;;)
{ {
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
tz = pg_tzenumerate_next(tzenum); tz = pg_tzenumerate_next(tzenum);
MemoryContextSwitchTo(oldcontext);
if (!tz) if (!tz)
{ break;
pg_tzenumerate_end(tzenum);
funcctx->user_fctx = NULL;
SRF_RETURN_DONE(funcctx);
}
/* Convert now() to local time in this zone */ /* Convert now() to local time in this zone */
if (timestamp2tm(GetCurrentTransactionStartTimestamp(), if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
...@@ -4840,25 +4822,22 @@ pg_timezone_names(PG_FUNCTION_ARGS) ...@@ -4840,25 +4822,22 @@ pg_timezone_names(PG_FUNCTION_ARGS)
if (tzn && strlen(tzn) > 31) if (tzn && strlen(tzn) > 31)
continue; continue;
/* Found a displayable zone */ MemSet(nulls, 0, sizeof(nulls));
break;
}
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)); MemSet(&itm, 0, sizeof(struct pg_tm));
values[1] = CStringGetTextDatum(tzn ? tzn : ""); itm.tm_sec = -tzoff;
resInterval = (Interval *) palloc(sizeof(Interval));
tm2interval(&itm, 0, resInterval);
values[2] = IntervalPGetDatum(resInterval);
MemSet(&itm, 0, sizeof(struct pg_tm)); values[3] = BoolGetDatum(tm.tm_isdst > 0);
itm.tm_sec = -tzoff;
resInterval = (Interval *) palloc(sizeof(Interval));
tm2interval(&itm, 0, resInterval);
values[2] = IntervalPGetDatum(resInterval);
values[3] = BoolGetDatum(tm.tm_isdst > 0); tuplestore_putvalues(tupstore, tupdesc, values, nulls);
}
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result); pg_tzenumerate_end(tzenum);
return (Datum) 0;
} }
...@@ -36,13 +36,6 @@ ...@@ -36,13 +36,6 @@
#include "utils/syscache.h" #include "utils/syscache.h"
#include "utils/timestamp.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. * Convert a "text" filename argument to C string, and check it's allowable.
...@@ -447,67 +440,79 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS) ...@@ -447,67 +440,79 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum Datum
pg_ls_dir(PG_FUNCTION_ARGS) 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; struct dirent *de;
directory_fctx *fctx;
MemoryContext oldcontext; 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; if (!PG_ARGISNULL(1))
bool include_dot_dirs = false; missing_ok = PG_GETARG_BOOL(1);
if (!PG_ARGISNULL(2))
include_dot_dirs = PG_GETARG_BOOL(2);
}
/* check the optional arguments */ /* check to see if caller supports us returning a tuplestore */
if (PG_NARGS() == 3) if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
{ ereport(ERROR,
if (!PG_ARGISNULL(1)) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
missing_ok = PG_GETARG_BOOL(1); errmsg("set-valued function called in context that cannot accept a set")));
if (!PG_ARGISNULL(2)) if (!(rsinfo->allowedModes & SFRM_Materialize))
include_dot_dirs = PG_GETARG_BOOL(2); 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(); tupdesc = CreateTemplateTupleDesc(1);
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
fctx = palloc(sizeof(directory_fctx)); randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(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; MemoryContextSwitchTo(oldcontext);
fctx->dirdesc = AllocateDir(fctx->location);
if (!fctx->dirdesc) dirdesc = AllocateDir(location);
{ if (!dirdesc)
if (missing_ok && errno == ENOENT) {
{ /* Return empty tuplestore if appropriate */
MemoryContextSwitchTo(oldcontext); if (missing_ok && errno == ENOENT)
SRF_RETURN_DONE(funcctx); return (Datum) 0;
} /* Otherwise, we can let ReadDir() throw the error */
else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
fctx->location)));
}
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
} }
funcctx = SRF_PERCALL_SETUP(); while ((de = ReadDir(dirdesc, location)) != NULL)
fctx = (directory_fctx *) funcctx->user_fctx;
while ((de = ReadDir(fctx->dirdesc, fctx->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 ||
strcmp(de->d_name, "..") == 0)) strcmp(de->d_name, "..") == 0))
continue; 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) ...@@ -548,8 +553,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!(rsinfo->allowedModes & SFRM_Materialize)) if (!(rsinfo->allowedModes & SFRM_Materialize))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("materialize mode required, but it is not " errmsg("materialize mode required, but it is not allowed in this context")));
"allowed in this context")));
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->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) ...@@ -575,10 +579,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
{ {
/* Return empty tuplestore if appropriate */ /* Return empty tuplestore if appropriate */
if (missing_ok && errno == ENOENT) if (missing_ok && errno == ENOENT)
{
tuplestore_donestoring(tupstore);
return (Datum) 0; return (Datum) 0;
}
/* Otherwise, we can let ReadDir() throw the error */ /* Otherwise, we can let ReadDir() throw the error */
} }
...@@ -613,7 +614,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) ...@@ -613,7 +614,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
} }
FreeDir(dirdesc); FreeDir(dirdesc);
tuplestore_donestoring(tupstore);
return (Datum) 0; return (Datum) 0;
} }
......
...@@ -194,72 +194,82 @@ current_query(PG_FUNCTION_ARGS) ...@@ -194,72 +194,82 @@ current_query(PG_FUNCTION_ARGS)
/* Function to find out which databases make use of a tablespace */ /* Function to find out which databases make use of a tablespace */
typedef struct
{
char *location;
DIR *dirdesc;
} ts_db_fctx;
Datum Datum
pg_tablespace_databases(PG_FUNCTION_ARGS) 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; struct dirent *de;
ts_db_fctx *fctx; MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL()) /* check to see if caller supports us returning a tuplestore */
{ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
MemoryContext oldcontext; ereport(ERROR,
Oid tablespaceOid = PG_GETARG_OID(0); (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(); /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); 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) randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
{ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
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); rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
if (!fctx->dirdesc) MemoryContextSwitchTo(oldcontext);
{
/* the only expected error is ENOENT */ if (tablespaceOid == GLOBALTABLESPACE_OID)
if (errno != ENOENT) {
ereport(ERROR, ereport(WARNING,
(errcode_for_file_access(), (errmsg("global tablespace never has databases")));
errmsg("could not open directory \"%s\": %m", /* return empty tuplestore */
fctx->location))); return (Datum) 0;
ereport(WARNING,
(errmsg("%u is not a tablespace OID", tablespaceOid)));
}
}
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
} }
funcctx = SRF_PERCALL_SETUP(); if (tablespaceOid == DEFAULTTABLESPACE_OID)
fctx = (ts_db_fctx *) funcctx->user_fctx; location = psprintf("base");
else
location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
TABLESPACE_VERSION_DIRECTORY);
if (!fctx->dirdesc) /* not a tablespace */ dirdesc = AllocateDir(location);
SRF_RETURN_DONE(funcctx);
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); Oid datOid = atooid(de->d_name);
char *subdir; char *subdir;
bool isempty; bool isempty;
Datum values[1];
bool nulls[1];
/* this test skips . and .., but is awfully weak */ /* this test skips . and .., but is awfully weak */
if (!datOid) if (!datOid)
...@@ -267,18 +277,21 @@ pg_tablespace_databases(PG_FUNCTION_ARGS) ...@@ -267,18 +277,21 @@ pg_tablespace_databases(PG_FUNCTION_ARGS)
/* if database subdir is empty, don't report tablespace as used */ /* 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); isempty = directory_is_empty(subdir);
pfree(subdir); pfree(subdir);
if (isempty) if (isempty)
continue; /* indeed, nothing in it */ 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); FreeDir(dirdesc);
SRF_RETURN_DONE(funcctx); return (Datum) 0;
} }
......
...@@ -239,8 +239,6 @@ tuple toaster will decide whether toasting is needed. ...@@ -239,8 +239,6 @@ tuple toaster will decide whether toasting is needed.
Functions Accepting or Returning Sets 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 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 with fcinfo->resultinfo pointing to a node of type ReturnSetInfo. A
function that desires to return a set should raise an error "called in 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 ...@@ -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 null result. (Note it is possible to return an empty set by doing this on
the first call.) the first call.)
The ReturnSetInfo node also contains a link to the ExprContext within which Value-per-call functions MUST NOT assume that they will be run to completion;
the function is being evaluated. This is useful for value-per-call functions the executor might simply stop calling them, for example because of a LIMIT.
that need to close down internal state when they are not run to completion: Therefore, it's unsafe to attempt to perform any resource cleanup in the
they can register a shutdown callback function in the ExprContext. 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 Materialize mode works like this: the function creates a Tuplestore holding
the (possibly empty) result set, and returns it. There are no multiple calls. the (possibly empty) result set, and returns it. There are no multiple calls.
......
...@@ -234,7 +234,7 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple); ...@@ -234,7 +234,7 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
/*---------- /*----------
* Support for Set Returning Functions (SRFs) * 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 * Datum
* my_Set_Returning_Function(PG_FUNCTION_ARGS) * my_Set_Returning_Function(PG_FUNCTION_ARGS)
...@@ -271,6 +271,17 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple); ...@@ -271,6 +271,17 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple);
* SRF_RETURN_DONE(funcctx); * 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().
*
*---------- *----------
*/ */
......
...@@ -180,6 +180,27 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir(); ...@@ -180,6 +180,27 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
t t
(1 row) (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 -- Test adding a support function to a subject function
-- --
......
...@@ -51,6 +51,15 @@ from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1; ...@@ -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 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 -- Test adding a support function to a subject function
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment