mirror of
https://github.com/postgres/postgres.git
synced 2025-04-20 00:42:27 +03:00
Avoid holding a directory FD open across assorted SRF calls.
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
This commit is contained in:
parent
113758155c
commit
b4570d33aa
@ -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,24 +499,32 @@ 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;
|
||||
/* 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);
|
||||
|
||||
fctx = palloc(sizeof(directory_fctx));
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
tupdesc = CreateTemplateTupleDesc(2);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
||||
@ -529,25 +532,18 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
||||
TEXTOID, -1, 0);
|
||||
|
||||
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
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->location = pstrdup(Log_directory);
|
||||
fctx->dirdesc = AllocateDir(fctx->location);
|
||||
|
||||
if (!fctx->dirdesc)
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
@ -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,34 +64,46 @@ typedef struct
|
||||
Datum
|
||||
pgrowlocks(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
text *relname = PG_GETARG_TEXT_PP(0);
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
AttInMetadata *attinmeta;
|
||||
Relation rel;
|
||||
RangeVar *relrv;
|
||||
TableScanDesc scan;
|
||||
HeapScanDesc hscan;
|
||||
HeapTuple tuple;
|
||||
TupleDesc tupdesc;
|
||||
AttInMetadata *attinmeta;
|
||||
Datum result;
|
||||
MyData *mydata;
|
||||
Relation rel;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
text *relname;
|
||||
RangeVar *relrv;
|
||||
MemoryContext oldcontext;
|
||||
AclResult aclresult;
|
||||
char **values;
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
/* 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);
|
||||
|
||||
/* 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;
|
||||
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;
|
||||
|
||||
relname = PG_GETARG_TEXT_PP(0);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
/* Access the table */
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||
rel = relation_openrv(relrv, AccessShareLock);
|
||||
|
||||
@ -131,24 +136,14 @@ pgrowlocks(PG_FUNCTION_ARGS)
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
@ -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.
|
||||
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>
|
||||
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->flinfo->fn_extra</literal>
|
||||
is used to hold a pointer to <structname>FuncCallContext</structname>
|
||||
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->flinfo->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->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>
|
||||
|
@ -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");
|
||||
|
||||
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);
|
||||
|
||||
/* initialize timezone scanning code */
|
||||
tzenum = pg_tzenumerate_start();
|
||||
funcctx->user_fctx = (void *) tzenum;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
/* stuff done on every call of the function */
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
tzenum = (pg_tzenum *) funcctx->user_fctx;
|
||||
|
||||
/* 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,10 +4822,6 @@ pg_timezone_names(PG_FUNCTION_ARGS)
|
||||
if (tzn && strlen(tzn) > 31)
|
||||
continue;
|
||||
|
||||
/* Found a displayable zone */
|
||||
break;
|
||||
}
|
||||
|
||||
MemSet(nulls, 0, sizeof(nulls));
|
||||
|
||||
values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
|
||||
@ -4857,8 +4835,9 @@ pg_timezone_names(PG_FUNCTION_ARGS)
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -36,13 +36,6 @@
|
||||
#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,15 +440,18 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
|
||||
Datum
|
||||
pg_ls_dir(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
struct dirent *de;
|
||||
directory_fctx *fctx;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
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;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
|
||||
|
||||
/* check the optional arguments */
|
||||
if (PG_NARGS() == 3)
|
||||
@ -466,48 +462,57 @@ pg_ls_dir(PG_FUNCTION_ARGS)
|
||||
include_dot_dirs = PG_GETARG_BOOL(2);
|
||||
}
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
|
||||
fctx = palloc(sizeof(directory_fctx));
|
||||
fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
|
||||
|
||||
fctx->include_dot_dirs = include_dot_dirs;
|
||||
fctx->dirdesc = AllocateDir(fctx->location);
|
||||
|
||||
if (!fctx->dirdesc)
|
||||
{
|
||||
if (missing_ok && errno == ENOENT)
|
||||
{
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
}
|
||||
else
|
||||
/* check to see if caller supports us returning a tuplestore */
|
||||
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open directory \"%s\": %m",
|
||||
fctx->location)));
|
||||
}
|
||||
funcctx->user_fctx = fctx;
|
||||
(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);
|
||||
|
||||
tupdesc = CreateTemplateTupleDesc(1);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 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;
|
||||
|
||||
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;
|
||||
|
||||
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||
}
|
||||
|
||||
FreeDir(fctx->dirdesc);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
struct dirent *de;
|
||||
ts_db_fctx *fctx;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
MemoryContext oldcontext;
|
||||
Oid tablespaceOid = PG_GETARG_OID(0);
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
char *location;
|
||||
DIR *dirdesc;
|
||||
struct dirent *de;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
/* 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")));
|
||||
|
||||
fctx = palloc(sizeof(ts_db_fctx));
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
tupdesc = CreateTemplateTupleDesc(1);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_tablespace_databases",
|
||||
OIDOID, -1, 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;
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
if (tablespaceOid == GLOBALTABLESPACE_OID)
|
||||
{
|
||||
fctx->dirdesc = NULL;
|
||||
ereport(WARNING,
|
||||
(errmsg("global tablespace never has databases")));
|
||||
/* return empty tuplestore */
|
||||
return (Datum) 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (tablespaceOid == DEFAULTTABLESPACE_OID)
|
||||
fctx->location = psprintf("base");
|
||||
location = psprintf("base");
|
||||
else
|
||||
fctx->location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
|
||||
location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
|
||||
TABLESPACE_VERSION_DIRECTORY);
|
||||
|
||||
fctx->dirdesc = AllocateDir(fctx->location);
|
||||
dirdesc = AllocateDir(location);
|
||||
|
||||
if (!fctx->dirdesc)
|
||||
if (!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)));
|
||||
location)));
|
||||
ereport(WARNING,
|
||||
(errmsg("%u is not a tablespace OID", tablespaceOid)));
|
||||
}
|
||||
}
|
||||
funcctx->user_fctx = fctx;
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
/* return empty tuplestore */
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
fctx = (ts_db_fctx *) funcctx->user_fctx;
|
||||
|
||||
if (!fctx->dirdesc) /* not a tablespace */
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
|
||||
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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().
|
||||
*
|
||||
*----------
|
||||
*/
|
||||
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
|
Loading…
x
Reference in New Issue
Block a user