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 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)
|
|||||||
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")));
|
||||||
|
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(directory_fctx));
|
tupdesc = CreateTemplateTupleDesc(2);
|
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
||||||
|
TIMESTAMPOID, -1, 0);
|
||||||
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
||||||
|
TEXTOID, -1, 0);
|
||||||
|
|
||||||
tupdesc = CreateTemplateTupleDesc(2);
|
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
||||||
TIMESTAMPOID, -1, 0);
|
rsinfo->returnMode = SFRM_Materialize;
|
||||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
rsinfo->setResult = tupstore;
|
||||||
TEXTOID, -1, 0);
|
rsinfo->setDesc = tupdesc;
|
||||||
|
|
||||||
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
fctx->location = pstrdup(Log_directory);
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||||
fctx->dirdesc = AllocateDir(fctx->location);
|
|
||||||
|
|
||||||
if (!fctx->dirdesc)
|
dirdesc = AllocateDir(Log_directory);
|
||||||
ereport(ERROR,
|
while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
|
||||||
(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;
|
|
||||||
|
|
||||||
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
|
|
||||||
{
|
{
|
||||||
char *values[2];
|
char *values[2];
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
@ -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);
|
|||||||
|
|
||||||
#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
|
|||||||
Datum
|
Datum
|
||||||
pgrowlocks(PG_FUNCTION_ARGS)
|
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;
|
TableScanDesc scan;
|
||||||
HeapScanDesc hscan;
|
HeapScanDesc hscan;
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
TupleDesc tupdesc;
|
MemoryContext oldcontext;
|
||||||
AttInMetadata *attinmeta;
|
AclResult aclresult;
|
||||||
Datum result;
|
char **values;
|
||||||
MyData *mydata;
|
|
||||||
Relation rel;
|
|
||||||
|
|
||||||
if (SRF_IS_FIRSTCALL())
|
/* check to see if caller supports us returning a tuplestore */
|
||||||
{
|
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||||
text *relname;
|
ereport(ERROR,
|
||||||
RangeVar *relrv;
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
MemoryContext oldcontext;
|
errmsg("set-valued function called in context that cannot accept a set")));
|
||||||
AclResult aclresult;
|
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);
|
||||||
|
|
||||||
/* Build a tuple descriptor for our result type */
|
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
elog(ERROR, "return type must be a row type");
|
||||||
elog(ERROR, "return type must be a row type");
|
|
||||||
|
|
||||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||||
funcctx->attinmeta = attinmeta;
|
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);
|
||||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
|
||||||
rel = relation_openrv(relrv, AccessShareLock);
|
|
||||||
|
|
||||||
if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
|
/* Access the table */
|
||||||
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||||
errmsg("only heap AM is supported")));
|
rel = relation_openrv(relrv, AccessShareLock);
|
||||||
|
|
||||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
|
||||||
ereport(ERROR,
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
errmsg("only heap AM is supported")));
|
||||||
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))));
|
|
||||||
|
|
||||||
/*
|
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
||||||
* check permissions: must have SELECT on table or be in
|
ereport(ERROR,
|
||||||
* pg_stat_scan_tables
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||||
*/
|
errmsg("\"%s\" is a partitioned table",
|
||||||
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
|
RelationGetRelationName(rel)),
|
||||||
ACL_SELECT);
|
errdetail("Partitioned tables do not contain rows.")));
|
||||||
if (aclresult != ACLCHECK_OK)
|
else if (rel->rd_rel->relkind != RELKIND_RELATION)
|
||||||
aclresult = is_member_of_role(GetUserId(), DEFAULT_ROLE_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||||
|
errmsg("\"%s\" is not a table",
|
||||||
|
RelationGetRelationName(rel))));
|
||||||
|
|
||||||
if (aclresult != ACLCHECK_OK)
|
/*
|
||||||
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
|
* check permissions: must have SELECT on table or be in
|
||||||
RelationGetRelationName(rel));
|
* 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;
|
||||||
|
|
||||||
scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);
|
if (aclresult != ACLCHECK_OK)
|
||||||
hscan = (HeapScanDesc) scan;
|
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
|
||||||
mydata = palloc(sizeof(*mydata));
|
RelationGetRelationName(rel));
|
||||||
mydata->rel = rel;
|
|
||||||
mydata->scan = scan;
|
|
||||||
mydata->ncolumns = tupdesc->natts;
|
|
||||||
funcctx->user_fctx = mydata;
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
/* Scan the relation */
|
||||||
}
|
scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);
|
||||||
|
|
||||||
funcctx = SRF_PERCALL_SETUP();
|
|
||||||
attinmeta = funcctx->attinmeta;
|
|
||||||
mydata = (MyData *) funcctx->user_fctx;
|
|
||||||
scan = mydata->scan;
|
|
||||||
hscan = (HeapScanDesc) scan;
|
hscan = (HeapScanDesc) scan;
|
||||||
|
|
||||||
/* scan the relation */
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||||
|
|
||||||
|
values = (char **) palloc(tupdesc->natts * sizeof(char *));
|
||||||
|
|
||||||
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)
|
|||||||
*/
|
*/
|
||||||
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)
|
|||||||
|
|
||||||
/* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
<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.
|
||||||
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
A set-returning function (<acronym>SRF</acronym>) is called
|
When using ValuePerCall mode, it is important to remember that the
|
||||||
once for each item it returns. The <acronym>SRF</acronym> must
|
query is not guaranteed to be run to completion; that is, due to
|
||||||
therefore save enough state to remember what it was doing and
|
options such as <literal>LIMIT</literal>, the executor might stop
|
||||||
return the next item on each call.
|
making calls to the set-returning function before all rows have been
|
||||||
The structure <structname>FuncCallContext</structname> is provided to help
|
fetched. This means it is not safe to perform cleanup activities in
|
||||||
control this process. Within a function, <literal>fcinfo->flinfo->fn_extra</literal>
|
the last call, because that might not ever happen. It's recommended
|
||||||
is used to hold a pointer to <structname>FuncCallContext</structname>
|
to use Materialize mode for functions that need access to external
|
||||||
across calls.
|
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>
|
<programlisting>
|
||||||
typedef struct FuncCallContext
|
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)
|
|||||||
<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->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)
|
|||||||
}
|
}
|
||||||
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,
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
if (tzn && strlen(tzn) > 31)
|
if (tzn && strlen(tzn) > 31)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Found a displayable zone */
|
MemSet(nulls, 0, sizeof(nulls));
|
||||||
break;
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
values[3] = BoolGetDatum(tm.tm_isdst > 0);
|
||||||
|
|
||||||
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemSet(nulls, 0, sizeof(nulls));
|
pg_tzenumerate_end(tzenum);
|
||||||
|
return (Datum) 0;
|
||||||
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);
|
|
||||||
|
|
||||||
values[3] = BoolGetDatum(tm.tm_isdst > 0);
|
|
||||||
|
|
||||||
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
|
|
||||||
result = HeapTupleGetDatum(tuple);
|
|
||||||
|
|
||||||
SRF_RETURN_NEXT(funcctx, result);
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|||||||
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))
|
||||||
/* check the optional arguments */
|
include_dot_dirs = PG_GETARG_BOOL(2);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
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();
|
/* check to see if caller supports us returning a tuplestore */
|
||||||
fctx = (directory_fctx *) funcctx->user_fctx;
|
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")));
|
||||||
|
|
||||||
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
|
/* 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)
|
||||||
{
|
{
|
||||||
if (!fctx->include_dot_dirs &&
|
/* Return empty tuplestore if appropriate */
|
||||||
|
if (missing_ok && errno == ENOENT)
|
||||||
|
return (Datum) 0;
|
||||||
|
/* Otherwise, we can let ReadDir() throw the error */
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((de = ReadDir(dirdesc, location)) != NULL)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
||||||
}
|
}
|
||||||
|
|
||||||
FreeDir(fctx->dirdesc);
|
FreeDir(dirdesc);
|
||||||
|
return (Datum) 0;
|
||||||
SRF_RETURN_DONE(funcctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -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)
|
|||||||
{
|
{
|
||||||
/* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FreeDir(dirdesc);
|
FreeDir(dirdesc);
|
||||||
tuplestore_donestoring(tupstore);
|
|
||||||
return (Datum) 0;
|
return (Datum) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
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);
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
MemoryContext oldcontext;
|
ereport(WARNING,
|
||||||
Oid tablespaceOid = PG_GETARG_OID(0);
|
(errmsg("global tablespace never has databases")));
|
||||||
|
/* return empty tuplestore */
|
||||||
funcctx = SRF_FIRSTCALL_INIT();
|
return (Datum) 0;
|
||||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
||||||
|
|
||||||
fctx = palloc(sizeof(ts_db_fctx));
|
|
||||||
|
|
||||||
if (tablespaceOid == GLOBALTABLESPACE_OID)
|
|
||||||
{
|
|
||||||
fctx->dirdesc = NULL;
|
|
||||||
ereport(WARNING,
|
|
||||||
(errmsg("global tablespace never has databases")));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (tablespaceOid == DEFAULTTABLESPACE_OID)
|
|
||||||
fctx->location = psprintf("base");
|
|
||||||
else
|
|
||||||
fctx->location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
|
|
||||||
TABLESPACE_VERSION_DIRECTORY);
|
|
||||||
|
|
||||||
fctx->dirdesc = AllocateDir(fctx->location);
|
|
||||||
|
|
||||||
if (!fctx->dirdesc)
|
|
||||||
{
|
|
||||||
/* the only expected error is ENOENT */
|
|
||||||
if (errno != ENOENT)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode_for_file_access(),
|
|
||||||
errmsg("could not open directory \"%s\": %m",
|
|
||||||
fctx->location)));
|
|
||||||
ereport(WARNING,
|
|
||||||
(errmsg("%u is not a tablespace OID", tablespaceOid)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
funcctx->user_fctx = fctx;
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|||||||
|
|
||||||
/* 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.
|
|||||||
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
|
|||||||
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);
|
|||||||
/*----------
|
/*----------
|
||||||
* 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);
|
|||||||
* 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();
|
|||||||
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;
|
|||||||
|
|
||||||
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
|
||||||
--
|
--
|
||||||
|
Loading…
x
Reference in New Issue
Block a user