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
a6b5ddf64c
commit
819d552574
@ -54,11 +54,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
|
||||
@ -483,50 +478,51 @@ pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
|
||||
static Datum
|
||||
pg_logdir_ls_internal(FunctionCallInfo fcinfo)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
AttInMetadata *attinmeta;
|
||||
DIR *dirdesc;
|
||||
struct dirent *de;
|
||||
directory_fctx *fctx;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
(errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))));
|
||||
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);
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
fctx = palloc(sizeof(directory_fctx));
|
||||
tupdesc = CreateTemplateTupleDesc(2, false);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
||||
TIMESTAMPOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
||||
TEXTOID, -1, 0);
|
||||
|
||||
tupdesc = CreateTemplateTupleDesc(2, false);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
|
||||
TIMESTAMPOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
|
||||
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;
|
||||
|
||||
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
fctx->location = pstrdup(Log_directory);
|
||||
fctx->dirdesc = AllocateDir(fctx->location);
|
||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
|
||||
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;
|
||||
|
||||
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
|
||||
dirdesc = AllocateDir(Log_directory);
|
||||
while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
|
||||
{
|
||||
char *values[2];
|
||||
HeapTuple tuple;
|
||||
@ -563,13 +559,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;
|
||||
}
|
||||
|
@ -52,13 +52,6 @@ PG_FUNCTION_INFO_V1(pgrowlocks);
|
||||
|
||||
#define NCHARS 32
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Relation rel;
|
||||
HeapScanDesc scan;
|
||||
int ncolumns;
|
||||
} MyData;
|
||||
|
||||
#define Atnum_tid 0
|
||||
#define Atnum_xmax 1
|
||||
#define Atnum_ismulti 2
|
||||
@ -69,77 +62,80 @@ 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;
|
||||
HeapScanDesc scan;
|
||||
HeapTuple tuple;
|
||||
TupleDesc tupdesc;
|
||||
AttInMetadata *attinmeta;
|
||||
Datum result;
|
||||
MyData *mydata;
|
||||
Relation rel;
|
||||
MemoryContext oldcontext;
|
||||
AclResult aclresult;
|
||||
char **values;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
text *relname;
|
||||
RangeVar *relrv;
|
||||
MemoryContext oldcontext;
|
||||
AclResult aclresult;
|
||||
/* check to see if caller supports us returning a tuplestore */
|
||||
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("set-valued function called in context that cannot accept a set")));
|
||||
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("materialize mode required, but it is not allowed in this context")));
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
/* 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");
|
||||
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);
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||
rel = relation_openrv(relrv, AccessShareLock);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
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))));
|
||||
/* Access the table */
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||
rel = relation_openrv(relrv, AccessShareLock);
|
||||
|
||||
/*
|
||||
* 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 (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))));
|
||||
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
|
||||
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;
|
||||
|
||||
scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
|
||||
mydata = palloc(sizeof(*mydata));
|
||||
mydata->rel = rel;
|
||||
mydata->scan = scan;
|
||||
mydata->ncolumns = tupdesc->natts;
|
||||
funcctx->user_fctx = mydata;
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
|
||||
RelationGetRelationName(rel));
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
/* Scan the relation */
|
||||
scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
attinmeta = funcctx->attinmeta;
|
||||
mydata = (MyData *) funcctx->user_fctx;
|
||||
scan = mydata->scan;
|
||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||
|
||||
values = (char **) palloc(tupdesc->natts * sizeof(char *));
|
||||
|
||||
/* scan the relation */
|
||||
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
||||
{
|
||||
HTSU_Result htsu;
|
||||
@ -160,10 +156,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
|
||||
*/
|
||||
if (htsu == HeapTupleBeingUpdated)
|
||||
{
|
||||
char **values;
|
||||
|
||||
values = (char **) palloc(mydata->ncolumns * sizeof(char *));
|
||||
|
||||
values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
|
||||
PointerGetDatum(&tuple->t_self));
|
||||
|
||||
@ -288,16 +280,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
|
||||
{
|
||||
@ -306,7 +289,6 @@ pgrowlocks(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
heap_endscan(scan);
|
||||
heap_close(mydata->rel, AccessShareLock);
|
||||
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
heap_close(rel, AccessShareLock);
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
@ -2834,22 +2834,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
|
||||
{
|
||||
@ -2922,29 +2950,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>
|
||||
@ -2968,7 +2993,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>
|
||||
@ -3025,8 +3057,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);
|
||||
}
|
||||
}
|
||||
@ -3148,12 +3180,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>
|
||||
|
@ -4772,12 +4772,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;
|
||||
@ -4786,59 +4786,41 @@ pg_timezone_names(PG_FUNCTION_ARGS)
|
||||
const char *tzn;
|
||||
Interval *resInterval;
|
||||
struct pg_tm itm;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
/* stuff done only on the first call of the function */
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
TupleDesc tupdesc;
|
||||
/* check to see if caller supports us returning a tuplestore */
|
||||
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("set-valued function called in context that cannot accept a set")));
|
||||
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("materialize mode required, but it is not allowed in this context")));
|
||||
|
||||
/* create a function context for cross-call persistence */
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
||||
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
||||
|
||||
/*
|
||||
* switch to memory context appropriate for multiple function calls
|
||||
*/
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||
elog(ERROR, "return type must be a row type");
|
||||
|
||||
/* initialize timezone scanning code */
|
||||
tzenum = pg_tzenumerate_start();
|
||||
funcctx->user_fctx = (void *) tzenum;
|
||||
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
||||
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
||||
rsinfo->returnMode = SFRM_Materialize;
|
||||
rsinfo->setResult = tupstore;
|
||||
rsinfo->setDesc = tupdesc;
|
||||
|
||||
/*
|
||||
* build tupdesc for result tuples. This must match this function's
|
||||
* pg_proc entry!
|
||||
*/
|
||||
tupdesc = CreateTemplateTupleDesc(4, false);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
|
||||
TEXTOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "abbrev",
|
||||
TEXTOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "utc_offset",
|
||||
INTERVALOID, -1, 0);
|
||||
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_dst",
|
||||
BOOLOID, -1, 0);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
/* stuff done on every call of the function */
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
tzenum = (pg_tzenum *) funcctx->user_fctx;
|
||||
/* initialize timezone scanning code */
|
||||
tzenum = pg_tzenumerate_start();
|
||||
|
||||
/* search for another zone to display */
|
||||
for (;;)
|
||||
{
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
tz = pg_tzenumerate_next(tzenum);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
if (!tz)
|
||||
{
|
||||
pg_tzenumerate_end(tzenum);
|
||||
funcctx->user_fctx = NULL;
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
}
|
||||
break;
|
||||
|
||||
/* Convert now() to local time in this zone */
|
||||
if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
|
||||
@ -4857,25 +4839,22 @@ 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));
|
||||
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));
|
||||
|
||||
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);
|
||||
pg_tzenumerate_end(tzenum);
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
@ -33,13 +33,6 @@
|
||||
#include "utils/memutils.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.
|
||||
@ -442,67 +435,79 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
|
||||
Datum
|
||||
pg_ls_dir(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
char *location;
|
||||
bool missing_ok = false;
|
||||
bool include_dot_dirs = false;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
DIR *dirdesc;
|
||||
struct dirent *de;
|
||||
directory_fctx *fctx;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
|
||||
|
||||
/* check the optional arguments */
|
||||
if (PG_NARGS() == 3)
|
||||
{
|
||||
bool missing_ok = false;
|
||||
bool include_dot_dirs = false;
|
||||
|
||||
/* check the optional arguments */
|
||||
if (PG_NARGS() == 3)
|
||||
{
|
||||
if (!PG_ARGISNULL(1))
|
||||
missing_ok = PG_GETARG_BOOL(1);
|
||||
if (!PG_ARGISNULL(2))
|
||||
include_dot_dirs = PG_GETARG_BOOL(2);
|
||||
}
|
||||
|
||||
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);
|
||||
if (!PG_ARGISNULL(1))
|
||||
missing_ok = PG_GETARG_BOOL(1);
|
||||
if (!PG_ARGISNULL(2))
|
||||
include_dot_dirs = PG_GETARG_BOOL(2);
|
||||
}
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
fctx = (directory_fctx *) funcctx->user_fctx;
|
||||
/* 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")));
|
||||
|
||||
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, false);
|
||||
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))
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -540,8 +545,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir)
|
||||
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);
|
||||
@ -594,7 +598,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir)
|
||||
}
|
||||
|
||||
FreeDir(dirdesc);
|
||||
tuplestore_donestoring(tupstore);
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
||||
|
@ -388,72 +388,82 @@ pg_rotate_logfile_v2(PG_FUNCTION_ARGS)
|
||||
|
||||
/* Function to find out which databases make use of a tablespace */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *location;
|
||||
DIR *dirdesc;
|
||||
} ts_db_fctx;
|
||||
|
||||
Datum
|
||||
pg_tablespace_databases(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
Oid tablespaceOid = PG_GETARG_OID(0);
|
||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||
bool randomAccess;
|
||||
TupleDesc tupdesc;
|
||||
Tuplestorestate *tupstore;
|
||||
char *location;
|
||||
DIR *dirdesc;
|
||||
struct dirent *de;
|
||||
ts_db_fctx *fctx;
|
||||
MemoryContext oldcontext;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
/* 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, false);
|
||||
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;
|
||||
Oid tablespaceOid = PG_GETARG_OID(0);
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
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);
|
||||
ereport(WARNING,
|
||||
(errmsg("global tablespace never has databases")));
|
||||
/* return empty tuplestore */
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
fctx = (ts_db_fctx *) funcctx->user_fctx;
|
||||
if (tablespaceOid == DEFAULTTABLESPACE_OID)
|
||||
location = psprintf("base");
|
||||
else
|
||||
location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
|
||||
TABLESPACE_VERSION_DIRECTORY);
|
||||
|
||||
if (!fctx->dirdesc) /* not a tablespace */
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
dirdesc = AllocateDir(location);
|
||||
|
||||
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
|
||||
if (!dirdesc)
|
||||
{
|
||||
/* the only expected error is ENOENT */
|
||||
if (errno != ENOENT)
|
||||
ereport(ERROR,
|
||||
(errcode_for_file_access(),
|
||||
errmsg("could not open directory \"%s\": %m",
|
||||
location)));
|
||||
ereport(WARNING,
|
||||
(errmsg("%u is not a tablespace OID", tablespaceOid)));
|
||||
/* return empty tuplestore */
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
||||
while ((de = ReadDir(dirdesc, location)) != NULL)
|
||||
{
|
||||
Oid datOid = atooid(de->d_name);
|
||||
char *subdir;
|
||||
bool isempty;
|
||||
Datum values[1];
|
||||
bool nulls[1];
|
||||
|
||||
/* this test skips . and .., but is awfully weak */
|
||||
if (!datOid)
|
||||
@ -461,18 +471,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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -240,8 +240,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
|
||||
@ -278,10 +276,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.
|
||||
|
@ -246,7 +246,7 @@ extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
|
||||
/*----------
|
||||
* 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)
|
||||
@ -283,6 +283,17 @@ extern TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc);
|
||||
* 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().
|
||||
*
|
||||
*----------
|
||||
*/
|
||||
|
||||
|
@ -174,3 +174,24 @@ from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1;
|
||||
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)
|
||||
|
||||
|
@ -48,3 +48,12 @@ select * from pg_ls_waldir() limit 0;
|
||||
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
|
||||
select (w).size = :segsize as ok
|
||||
from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1;
|
||||
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user