aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/datetime.c101
-rw-r--r--src/backend/utils/adt/genfile.c105
-rw-r--r--src/backend/utils/adt/misc.c131
-rw-r--r--src/backend/utils/fmgr/README16
-rw-r--r--src/include/funcapi.h13
5 files changed, 189 insertions, 177 deletions
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index d9b5be7b4d5..38e95effc7b 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -4759,12 +4759,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;
@@ -4773,59 +4773,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(),
@@ -4844,25 +4826,22 @@ pg_timezone_names(PG_FUNCTION_ARGS)
if (tzn && strlen(tzn) > 31)
continue;
- /* Found a displayable zone */
- break;
- }
+ MemSet(nulls, 0, sizeof(nulls));
- MemSet(nulls, 0, sizeof(nulls));
+ values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
+ values[1] = CStringGetTextDatum(tzn ? tzn : "");
- values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
- values[1] = CStringGetTextDatum(tzn ? tzn : "");
+ MemSet(&itm, 0, sizeof(struct pg_tm));
+ itm.tm_sec = -tzoff;
+ resInterval = (Interval *) palloc(sizeof(Interval));
+ tm2interval(&itm, 0, resInterval);
+ values[2] = IntervalPGetDatum(resInterval);
- MemSet(&itm, 0, sizeof(struct pg_tm));
- itm.tm_sec = -tzoff;
- resInterval = (Interval *) palloc(sizeof(Interval));
- tm2interval(&itm, 0, resInterval);
- values[2] = IntervalPGetDatum(resInterval);
+ values[3] = BoolGetDatum(tm.tm_isdst > 0);
- values[3] = BoolGetDatum(tm.tm_isdst > 0);
-
- tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
- result = HeapTupleGetDatum(tuple);
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
- SRF_RETURN_NEXT(funcctx, result);
+ pg_tzenumerate_end(tzenum);
+ return (Datum) 0;
}
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c4eb10d3cfd..6932589dfed 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -31,13 +31,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.
@@ -393,9 +386,15 @@ 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 (!superuser())
@@ -403,62 +402,68 @@ pg_ls_dir(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser to get directory listings"))));
- if (SRF_IS_FIRSTCALL())
+ location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
{
- bool missing_ok = false;
- bool include_dot_dirs = false;
+ if (!PG_ARGISNULL(1))
+ missing_ok = PG_GETARG_BOOL(1);
+ if (!PG_ARGISNULL(2))
+ include_dot_dirs = PG_GETARG_BOOL(2);
+ }
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- if (!PG_ARGISNULL(1))
- missing_ok = PG_GETARG_BOOL(1);
- if (!PG_ARGISNULL(2))
- include_dot_dirs = PG_GETARG_BOOL(2);
- }
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("materialize mode required, but it is not allowed in this context")));
- 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));
- fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0));
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
- fctx->include_dot_dirs = include_dot_dirs;
- fctx->dirdesc = AllocateDir(fctx->location);
+ randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
- 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);
- }
+ MemoryContextSwitchTo(oldcontext);
- funcctx = SRF_PERCALL_SETUP();
- fctx = (directory_fctx *) funcctx->user_fctx;
+ 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 */
+ }
- while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+ while ((de = ReadDir(dirdesc, location)) != NULL)
{
- if (!fctx->include_dot_dirs &&
+ Datum values[1];
+ bool nulls[1];
+
+ if (!include_dot_dirs &&
(strcmp(de->d_name, ".") == 0 ||
strcmp(de->d_name, "..") == 0))
continue;
- SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
- }
+ values[0] = CStringGetTextDatum(de->d_name);
+ nulls[0] = false;
- FreeDir(fctx->dirdesc);
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
- SRF_RETURN_DONE(funcctx);
+ FreeDir(dirdesc);
+ return (Datum) 0;
}
/*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 4ac8183f36a..281a9e0ddaf 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -243,72 +243,82 @@ pg_rotate_logfile(PG_FUNCTION_ARGS)
/* Function to find out which databases make use of a tablespace */
-typedef struct
-{
- char *location;
- DIR *dirdesc;
-} ts_db_fctx;
-
Datum
pg_tablespace_databases(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
+ Oid tablespaceOid = PG_GETARG_OID(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ bool randomAccess;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ char *location;
+ DIR *dirdesc;
struct dirent *de;
- ts_db_fctx *fctx;
+ MemoryContext oldcontext;
- if (SRF_IS_FIRSTCALL())
- {
- MemoryContext oldcontext;
- Oid tablespaceOid = PG_GETARG_OID(0);
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("materialize mode required, but it is not allowed in this context")));
- funcctx = SRF_FIRSTCALL_INIT();
- oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
+ oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- fctx = palloc(sizeof(ts_db_fctx));
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_tablespace_databases",
+ OIDOID, -1, 0);
- if (tablespaceOid == GLOBALTABLESPACE_OID)
- {
- fctx->dirdesc = NULL;
- ereport(WARNING,
- (errmsg("global tablespace never has databases")));
- }
- else
- {
- if (tablespaceOid == DEFAULTTABLESPACE_OID)
- fctx->location = psprintf("base");
- else
- fctx->location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
- TABLESPACE_VERSION_DIRECTORY);
-
- 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);
+ 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)
+ {
+ 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)
{
- char *subdir;
- DIR *dirdesc;
Oid datOid = atooid(de->d_name);
+ char *subdir;
+ DIR *dirdesc2;
+ Datum values[1];
+ bool nulls[1];
/* this test skips . and .., but is awfully weak */
if (!datOid)
@@ -316,24 +326,27 @@ 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);
- dirdesc = AllocateDir(subdir);
- while ((de = ReadDir(dirdesc, subdir)) != NULL)
+ subdir = psprintf("%s/%s", location, de->d_name);
+ dirdesc2 = AllocateDir(subdir);
+ while ((de = ReadDir(dirdesc2, subdir)) != NULL)
{
if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0)
break;
}
- FreeDir(dirdesc);
+ FreeDir(dirdesc2);
pfree(subdir);
if (!de)
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;
}
diff --git a/src/backend/utils/fmgr/README b/src/backend/utils/fmgr/README
index e7e7ae9c6e8..8eb189e5b81 100644
--- a/src/backend/utils/fmgr/README
+++ b/src/backend/utils/fmgr/README
@@ -388,8 +388,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
@@ -426,10 +424,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.
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index 1b62bc120cc..3e74e5d5d4a 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -238,7 +238,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)
@@ -275,6 +275,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().
+ *
*----------
*/